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,2 +1,2 @@
def check_build():
return
def check_build():
return
@@ -1,6 +1,6 @@
py.extension_module(
'_check_build',
cython_gen.process('_check_build.pyx'),
install: true,
subdir: 'sklearn/__check_build',
)
py.extension_module(
'_check_build',
cython_gen.process('_check_build.pyx'),
install: true,
subdir: 'sklearn/__check_build',
)
@@ -1,116 +1,116 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
# Uses the pool adjacent violators algorithm (PAVA), with the
# enhancement of searching for the longest decreasing subsequence to
# pool at each step.
import numpy as np
from cython cimport floating
def _inplace_contiguous_isotonic_regression(floating[::1] y, floating[::1] w):
cdef:
Py_ssize_t n = y.shape[0], i, k
floating prev_y, sum_wy, sum_w
Py_ssize_t[::1] target = np.arange(n, dtype=np.intp)
# target describes a list of blocks. At any time, if [i..j] (inclusive) is
# an active block, then target[i] := j and target[j] := i.
# For "active" indices (block starts):
# w[i] := sum{w_orig[j], j=[i..target[i]]}
# y[i] := sum{y_orig[j]*w_orig[j], j=[i..target[i]]} / w[i]
with nogil:
i = 0
while i < n:
k = target[i] + 1
if k == n:
break
if y[i] < y[k]:
i = k
continue
sum_wy = w[i] * y[i]
sum_w = w[i]
while True:
# We are within a decreasing subsequence.
prev_y = y[k]
sum_wy += w[k] * y[k]
sum_w += w[k]
k = target[k] + 1
if k == n or prev_y < y[k]:
# Non-singleton decreasing subsequence is finished,
# update first entry.
y[i] = sum_wy / sum_w
w[i] = sum_w
target[i] = k - 1
target[k - 1] = i
if i > 0:
# Backtrack if we can. This makes the algorithm
# single-pass and ensures O(n) complexity.
i = target[i - 1]
# Otherwise, restart from the same point.
break
# Reconstruct the solution.
i = 0
while i < n:
k = target[i] + 1
y[i + 1 : k] = y[i]
i = k
def _make_unique(const floating[::1] X,
const floating[::1] y,
const floating[::1] sample_weights):
"""Average targets for duplicate X, drop duplicates.
Aggregates duplicate X values into a single X value where
the target y is a (sample_weighted) average of the individual
targets.
Assumes that X is ordered, so that all duplicates follow each other.
"""
unique_values = len(np.unique(X))
if floating is float:
dtype = np.float32
else:
dtype = np.float64
cdef floating[::1] y_out = np.empty(unique_values, dtype=dtype)
cdef floating[::1] x_out = np.empty_like(y_out)
cdef floating[::1] weights_out = np.empty_like(y_out)
cdef floating current_x = X[0]
cdef floating current_y = 0
cdef floating current_weight = 0
cdef int i = 0
cdef int j
cdef floating x
cdef int n_samples = len(X)
cdef floating eps = np.finfo(dtype).resolution
for j in range(n_samples):
x = X[j]
if x - current_x >= eps:
# next unique value
x_out[i] = current_x
weights_out[i] = current_weight
y_out[i] = current_y / current_weight
i += 1
current_x = x
current_weight = sample_weights[j]
current_y = y[j] * sample_weights[j]
else:
current_weight += sample_weights[j]
current_y += y[j] * sample_weights[j]
x_out[i] = current_x
weights_out[i] = current_weight
y_out[i] = current_y / current_weight
return(
np.asarray(x_out[:i+1]),
np.asarray(y_out[:i+1]),
np.asarray(weights_out[:i+1]),
)
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
# Uses the pool adjacent violators algorithm (PAVA), with the
# enhancement of searching for the longest decreasing subsequence to
# pool at each step.
import numpy as np
from cython cimport floating
def _inplace_contiguous_isotonic_regression(floating[::1] y, floating[::1] w):
cdef:
Py_ssize_t n = y.shape[0], i, k
floating prev_y, sum_wy, sum_w
Py_ssize_t[::1] target = np.arange(n, dtype=np.intp)
# target describes a list of blocks. At any time, if [i..j] (inclusive) is
# an active block, then target[i] := j and target[j] := i.
# For "active" indices (block starts):
# w[i] := sum{w_orig[j], j=[i..target[i]]}
# y[i] := sum{y_orig[j]*w_orig[j], j=[i..target[i]]} / w[i]
with nogil:
i = 0
while i < n:
k = target[i] + 1
if k == n:
break
if y[i] < y[k]:
i = k
continue
sum_wy = w[i] * y[i]
sum_w = w[i]
while True:
# We are within a decreasing subsequence.
prev_y = y[k]
sum_wy += w[k] * y[k]
sum_w += w[k]
k = target[k] + 1
if k == n or prev_y < y[k]:
# Non-singleton decreasing subsequence is finished,
# update first entry.
y[i] = sum_wy / sum_w
w[i] = sum_w
target[i] = k - 1
target[k - 1] = i
if i > 0:
# Backtrack if we can. This makes the algorithm
# single-pass and ensures O(n) complexity.
i = target[i - 1]
# Otherwise, restart from the same point.
break
# Reconstruct the solution.
i = 0
while i < n:
k = target[i] + 1
y[i + 1 : k] = y[i]
i = k
def _make_unique(const floating[::1] X,
const floating[::1] y,
const floating[::1] sample_weights):
"""Average targets for duplicate X, drop duplicates.
Aggregates duplicate X values into a single X value where
the target y is a (sample_weighted) average of the individual
targets.
Assumes that X is ordered, so that all duplicates follow each other.
"""
unique_values = len(np.unique(X))
if floating is float:
dtype = np.float32
else:
dtype = np.float64
cdef floating[::1] y_out = np.empty(unique_values, dtype=dtype)
cdef floating[::1] x_out = np.empty_like(y_out)
cdef floating[::1] weights_out = np.empty_like(y_out)
cdef floating current_x = X[0]
cdef floating current_y = 0
cdef floating current_weight = 0
cdef int i = 0
cdef int j
cdef floating x
cdef int n_samples = len(X)
cdef floating eps = np.finfo(dtype).resolution
for j in range(n_samples):
x = X[j]
if x - current_x >= eps:
# next unique value
x_out[i] = current_x
weights_out[i] = current_weight
y_out[i] = current_y / current_weight
i += 1
current_x = x
current_weight = sample_weights[j]
current_y = y[j] * sample_weights[j]
else:
current_weight += sample_weights[j]
current_y += y[j] * sample_weights[j]
x_out[i] = current_x
weights_out[i] = current_weight
y_out[i] = current_y / current_weight
return(
np.asarray(x_out[:i+1]),
np.asarray(y_out[:i+1]),
np.asarray(weights_out[:i+1]),
)
@@ -1,101 +1,101 @@
# Fused types for input like y_true, raw_prediction, sample_weights.
ctypedef fused floating_in:
double
float
# Fused types for output like gradient and hessian
# We use a different fused types for input (floating_in) and output (floating_out), such
# that input and output can have different dtypes in the same function call. A single
# fused type can only take on one single value (type) for all arguments in one function
# call.
ctypedef fused floating_out:
double
float
# Struct to return 2 doubles
ctypedef struct double_pair:
double val1
double val2
# C base class for loss functions
cdef class CyLossFunction:
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHalfSquaredError(CyLossFunction):
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyAbsoluteError(CyLossFunction):
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyPinballLoss(CyLossFunction):
cdef readonly double quantile # readonly makes it accessible from Python
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHuberLoss(CyLossFunction):
cdef public double delta # public makes it accessible from Python
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHalfPoissonLoss(CyLossFunction):
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHalfGammaLoss(CyLossFunction):
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHalfTweedieLoss(CyLossFunction):
cdef readonly double power # readonly makes it accessible from Python
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHalfTweedieLossIdentity(CyLossFunction):
cdef readonly double power # readonly makes it accessible from Python
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHalfBinomialLoss(CyLossFunction):
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyExponentialLoss(CyLossFunction):
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHalfMultinomialLoss():
cdef void cy_gradient(
self,
const floating_in y_true,
const floating_in[::1] raw_prediction,
const floating_in sample_weight,
floating_out[::1] gradient_out,
) noexcept nogil
# Fused types for input like y_true, raw_prediction, sample_weights.
ctypedef fused floating_in:
double
float
# Fused types for output like gradient and hessian
# We use a different fused types for input (floating_in) and output (floating_out), such
# that input and output can have different dtypes in the same function call. A single
# fused type can only take on one single value (type) for all arguments in one function
# call.
ctypedef fused floating_out:
double
float
# Struct to return 2 doubles
ctypedef struct double_pair:
double val1
double val2
# C base class for loss functions
cdef class CyLossFunction:
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHalfSquaredError(CyLossFunction):
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyAbsoluteError(CyLossFunction):
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyPinballLoss(CyLossFunction):
cdef readonly double quantile # readonly makes it accessible from Python
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHuberLoss(CyLossFunction):
cdef public double delta # public makes it accessible from Python
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHalfPoissonLoss(CyLossFunction):
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHalfGammaLoss(CyLossFunction):
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHalfTweedieLoss(CyLossFunction):
cdef readonly double power # readonly makes it accessible from Python
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHalfTweedieLossIdentity(CyLossFunction):
cdef readonly double power # readonly makes it accessible from Python
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHalfBinomialLoss(CyLossFunction):
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyExponentialLoss(CyLossFunction):
cdef double cy_loss(self, double y_true, double raw_prediction) noexcept nogil
cdef double cy_gradient(self, double y_true, double raw_prediction) noexcept nogil
cdef double_pair cy_grad_hess(self, double y_true, double raw_prediction) noexcept nogil
cdef class CyHalfMultinomialLoss():
cdef void cy_gradient(
self,
const floating_in y_true,
const floating_in[::1] raw_prediction,
const floating_in sample_weight,
floating_out[::1] gradient_out,
) noexcept nogil
File diff suppressed because it is too large Load Diff
@@ -1,23 +1,23 @@
# .pyx is generated, so this is needed to make Cython compilation work
_loss_cython_tree = [
fs.copyfile('_loss.pxd')
]
_loss_pyx = custom_target(
'_loss_pyx',
output: '_loss.pyx',
input: '_loss.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: _loss_cython_tree,
)
py.extension_module(
'_loss',
cython_gen.process(_loss_pyx),
dependencies: [openmp_dep],
install: true,
subdir: 'sklearn/_loss',
)
# .pyx is generated, so this is needed to make Cython compilation work
_loss_cython_tree = [
fs.copyfile('_loss.pxd')
]
_loss_pyx = custom_target(
'_loss_pyx',
output: '_loss.pyx',
input: '_loss.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: _loss_cython_tree,
)
py.extension_module(
'_loss',
cython_gen.process(_loss_pyx),
dependencies: [openmp_dep],
install: true,
subdir: 'sklearn/_loss',
)
@@ -1,41 +1,41 @@
# Fast inner loop for DBSCAN.
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from libcpp.vector cimport vector
from ..utils._typedefs cimport uint8_t, intp_t
def dbscan_inner(const uint8_t[::1] is_core,
object[:] neighborhoods,
intp_t[::1] labels):
cdef intp_t i, label_num = 0, v
cdef intp_t[:] neighb
cdef vector[intp_t] stack
for i in range(labels.shape[0]):
if labels[i] != -1 or not is_core[i]:
continue
# Depth-first search starting from i, ending at the non-core points.
# This is very similar to the classic algorithm for computing connected
# components, the difference being that we label non-core points as
# part of a cluster (component), but don't expand their neighborhoods.
while True:
if labels[i] == -1:
labels[i] = label_num
if is_core[i]:
neighb = neighborhoods[i]
for i in range(neighb.shape[0]):
v = neighb[i]
if labels[v] == -1:
stack.push_back(v)
if stack.size() == 0:
break
i = stack.back()
stack.pop_back()
label_num += 1
# Fast inner loop for DBSCAN.
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from libcpp.vector cimport vector
from ..utils._typedefs cimport uint8_t, intp_t
def dbscan_inner(const uint8_t[::1] is_core,
object[:] neighborhoods,
intp_t[::1] labels):
cdef intp_t i, label_num = 0, v
cdef intp_t[:] neighb
cdef vector[intp_t] stack
for i in range(labels.shape[0]):
if labels[i] != -1 or not is_core[i]:
continue
# Depth-first search starting from i, ending at the non-core points.
# This is very similar to the classic algorithm for computing connected
# components, the difference being that we label non-core points as
# part of a cluster (component), but don't expand their neighborhoods.
while True:
if labels[i] == -1:
labels[i] = label_num
if is_core[i]:
neighb = neighborhoods[i]
for i in range(neighb.shape[0]):
v = neighb[i]
if labels[v] == -1:
stack.push_back(v)
if stack.size() == 0:
break
i = stack.back()
stack.pop_back()
label_num += 1
@@ -1,274 +1,274 @@
# Minimum spanning tree single linkage implementation for hdbscan
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
cimport numpy as cnp
from libc.float cimport DBL_MAX
import numpy as np
from ...metrics._dist_metrics cimport DistanceMetric64
from ...cluster._hierarchical_fast cimport UnionFind
from ...cluster._hdbscan._tree cimport HIERARCHY_t
from ...cluster._hdbscan._tree import HIERARCHY_dtype
from ...utils._typedefs cimport intp_t, float64_t, int64_t, uint8_t
cnp.import_array()
cdef extern from "numpy/arrayobject.h":
intp_t * PyArray_SHAPE(cnp.PyArrayObject *)
# Numpy structured dtype representing a single ordered edge in Prim's algorithm
MST_edge_dtype = np.dtype([
("current_node", np.int64),
("next_node", np.int64),
("distance", np.float64),
])
# Packed shouldn't make a difference since they're all 8-byte quantities,
# but it's included just to be safe.
ctypedef packed struct MST_edge_t:
int64_t current_node
int64_t next_node
float64_t distance
cpdef cnp.ndarray[MST_edge_t, ndim=1, mode='c'] mst_from_mutual_reachability(
cnp.ndarray[float64_t, ndim=2] mutual_reachability
):
"""Compute the Minimum Spanning Tree (MST) representation of the mutual-
reachability graph using Prim's algorithm.
Parameters
----------
mutual_reachability : ndarray of shape (n_samples, n_samples)
Array of mutual-reachabilities between samples.
Returns
-------
mst : ndarray of shape (n_samples - 1,), dtype=MST_edge_dtype
The MST representation of the mutual-reachability graph. The MST is
represented as a collection of edges.
"""
cdef:
# Note: we utilize ndarray's over memory-views to make use of numpy
# binary indexing and sub-selection below.
cnp.ndarray[int64_t, ndim=1, mode='c'] current_labels
cnp.ndarray[float64_t, ndim=1, mode='c'] min_reachability, left, right
cnp.ndarray[MST_edge_t, ndim=1, mode='c'] mst
cnp.ndarray[uint8_t, mode='c'] label_filter
int64_t n_samples = PyArray_SHAPE(<cnp.PyArrayObject*> mutual_reachability)[0]
int64_t current_node, new_node_index, new_node, i
mst = np.empty(n_samples - 1, dtype=MST_edge_dtype)
current_labels = np.arange(n_samples, dtype=np.int64)
current_node = 0
min_reachability = np.full(n_samples, fill_value=np.inf, dtype=np.float64)
for i in range(0, n_samples - 1):
label_filter = current_labels != current_node
current_labels = current_labels[label_filter]
left = min_reachability[label_filter]
right = mutual_reachability[current_node][current_labels]
min_reachability = np.minimum(left, right)
new_node_index = np.argmin(min_reachability)
new_node = current_labels[new_node_index]
mst[i].current_node = current_node
mst[i].next_node = new_node
mst[i].distance = min_reachability[new_node_index]
current_node = new_node
return mst
cpdef cnp.ndarray[MST_edge_t, ndim=1, mode='c'] mst_from_data_matrix(
const float64_t[:, ::1] raw_data,
const float64_t[::1] core_distances,
DistanceMetric64 dist_metric,
float64_t alpha=1.0
):
"""Compute the Minimum Spanning Tree (MST) representation of the mutual-
reachability graph generated from the provided `raw_data` and
`core_distances` using Prim's algorithm.
Parameters
----------
raw_data : ndarray of shape (n_samples, n_features)
Input array of data samples.
core_distances : ndarray of shape (n_samples,)
An array containing the core-distance calculated for each corresponding
sample.
dist_metric : DistanceMetric
The distance metric to use when calculating pairwise distances for
determining mutual-reachability.
Returns
-------
mst : ndarray of shape (n_samples - 1,), dtype=MST_edge_dtype
The MST representation of the mutual-reachability graph. The MST is
represented as a collection of edges.
"""
cdef:
uint8_t[::1] in_tree
float64_t[::1] min_reachability
int64_t[::1] current_sources
cnp.ndarray[MST_edge_t, ndim=1, mode='c'] mst
int64_t current_node, source_node, new_node, next_node_source
int64_t i, j, n_samples, num_features
float64_t current_node_core_dist, new_reachability, mutual_reachability_distance
float64_t next_node_min_reach, pair_distance, next_node_core_dist
n_samples = raw_data.shape[0]
num_features = raw_data.shape[1]
mst = np.empty(n_samples - 1, dtype=MST_edge_dtype)
in_tree = np.zeros(n_samples, dtype=np.uint8)
min_reachability = np.full(n_samples, fill_value=np.inf, dtype=np.float64)
current_sources = np.ones(n_samples, dtype=np.int64)
current_node = 0
# The following loop dynamically updates minimum reachability node-by-node,
# avoiding unnecessary computation where possible.
for i in range(0, n_samples - 1):
in_tree[current_node] = 1
current_node_core_dist = core_distances[current_node]
new_reachability = DBL_MAX
source_node = 0
new_node = 0
for j in range(n_samples):
if in_tree[j]:
continue
next_node_min_reach = min_reachability[j]
next_node_source = current_sources[j]
pair_distance = dist_metric.dist(
&raw_data[current_node, 0],
&raw_data[j, 0],
num_features
)
pair_distance /= alpha
next_node_core_dist = core_distances[j]
mutual_reachability_distance = max(
current_node_core_dist,
next_node_core_dist,
pair_distance
)
# If MRD(i, j) is smaller than node j's min_reachability, we update
# node j's min_reachability for future reference.
if mutual_reachability_distance < next_node_min_reach:
min_reachability[j] = mutual_reachability_distance
current_sources[j] = current_node
# If MRD(i, j) is also smaller than node i's current
# min_reachability, we update and set their edge as the current
# MST edge candidate.
if mutual_reachability_distance < new_reachability:
new_reachability = mutual_reachability_distance
source_node = current_node
new_node = j
# If the node j is closer to another node already in the tree, we
# make their edge the current MST candidate edge.
elif next_node_min_reach < new_reachability:
new_reachability = next_node_min_reach
source_node = next_node_source
new_node = j
mst[i].current_node = source_node
mst[i].next_node = new_node
mst[i].distance = new_reachability
current_node = new_node
return mst
cpdef cnp.ndarray[HIERARCHY_t, ndim=1, mode="c"] make_single_linkage(const MST_edge_t[::1] mst):
"""Construct a single-linkage tree from an MST.
Parameters
----------
mst : ndarray of shape (n_samples - 1,), dtype=MST_edge_dtype
The MST representation of the mutual-reachability graph. The MST is
represented as a collection of edges.
Returns
-------
single_linkage : ndarray of shape (n_samples - 1,), dtype=HIERARCHY_dtype
The single-linkage tree tree (dendrogram) built from the MST. Each
of the array represents the following:
- left node/cluster
- right node/cluster
- distance
- new cluster size
"""
cdef:
cnp.ndarray[HIERARCHY_t, ndim=1, mode="c"] single_linkage
# Note mst.shape[0] is one fewer than the number of samples
int64_t n_samples = mst.shape[0] + 1
intp_t current_node_cluster, next_node_cluster
int64_t current_node, next_node, i
float64_t distance
UnionFind U = UnionFind(n_samples)
single_linkage = np.zeros(n_samples - 1, dtype=HIERARCHY_dtype)
for i in range(n_samples - 1):
current_node = mst[i].current_node
next_node = mst[i].next_node
distance = mst[i].distance
current_node_cluster = U.fast_find(current_node)
next_node_cluster = U.fast_find(next_node)
single_linkage[i].left_node = current_node_cluster
single_linkage[i].right_node = next_node_cluster
single_linkage[i].value = distance
single_linkage[i].cluster_size = U.size[current_node_cluster] + U.size[next_node_cluster]
U.union(current_node_cluster, next_node_cluster)
return single_linkage
# Minimum spanning tree single linkage implementation for hdbscan
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
cimport numpy as cnp
from libc.float cimport DBL_MAX
import numpy as np
from ...metrics._dist_metrics cimport DistanceMetric64
from ...cluster._hierarchical_fast cimport UnionFind
from ...cluster._hdbscan._tree cimport HIERARCHY_t
from ...cluster._hdbscan._tree import HIERARCHY_dtype
from ...utils._typedefs cimport intp_t, float64_t, int64_t, uint8_t
cnp.import_array()
cdef extern from "numpy/arrayobject.h":
intp_t * PyArray_SHAPE(cnp.PyArrayObject *)
# Numpy structured dtype representing a single ordered edge in Prim's algorithm
MST_edge_dtype = np.dtype([
("current_node", np.int64),
("next_node", np.int64),
("distance", np.float64),
])
# Packed shouldn't make a difference since they're all 8-byte quantities,
# but it's included just to be safe.
ctypedef packed struct MST_edge_t:
int64_t current_node
int64_t next_node
float64_t distance
cpdef cnp.ndarray[MST_edge_t, ndim=1, mode='c'] mst_from_mutual_reachability(
cnp.ndarray[float64_t, ndim=2] mutual_reachability
):
"""Compute the Minimum Spanning Tree (MST) representation of the mutual-
reachability graph using Prim's algorithm.
Parameters
----------
mutual_reachability : ndarray of shape (n_samples, n_samples)
Array of mutual-reachabilities between samples.
Returns
-------
mst : ndarray of shape (n_samples - 1,), dtype=MST_edge_dtype
The MST representation of the mutual-reachability graph. The MST is
represented as a collection of edges.
"""
cdef:
# Note: we utilize ndarray's over memory-views to make use of numpy
# binary indexing and sub-selection below.
cnp.ndarray[int64_t, ndim=1, mode='c'] current_labels
cnp.ndarray[float64_t, ndim=1, mode='c'] min_reachability, left, right
cnp.ndarray[MST_edge_t, ndim=1, mode='c'] mst
cnp.ndarray[uint8_t, mode='c'] label_filter
int64_t n_samples = PyArray_SHAPE(<cnp.PyArrayObject*> mutual_reachability)[0]
int64_t current_node, new_node_index, new_node, i
mst = np.empty(n_samples - 1, dtype=MST_edge_dtype)
current_labels = np.arange(n_samples, dtype=np.int64)
current_node = 0
min_reachability = np.full(n_samples, fill_value=np.inf, dtype=np.float64)
for i in range(0, n_samples - 1):
label_filter = current_labels != current_node
current_labels = current_labels[label_filter]
left = min_reachability[label_filter]
right = mutual_reachability[current_node][current_labels]
min_reachability = np.minimum(left, right)
new_node_index = np.argmin(min_reachability)
new_node = current_labels[new_node_index]
mst[i].current_node = current_node
mst[i].next_node = new_node
mst[i].distance = min_reachability[new_node_index]
current_node = new_node
return mst
cpdef cnp.ndarray[MST_edge_t, ndim=1, mode='c'] mst_from_data_matrix(
const float64_t[:, ::1] raw_data,
const float64_t[::1] core_distances,
DistanceMetric64 dist_metric,
float64_t alpha=1.0
):
"""Compute the Minimum Spanning Tree (MST) representation of the mutual-
reachability graph generated from the provided `raw_data` and
`core_distances` using Prim's algorithm.
Parameters
----------
raw_data : ndarray of shape (n_samples, n_features)
Input array of data samples.
core_distances : ndarray of shape (n_samples,)
An array containing the core-distance calculated for each corresponding
sample.
dist_metric : DistanceMetric
The distance metric to use when calculating pairwise distances for
determining mutual-reachability.
Returns
-------
mst : ndarray of shape (n_samples - 1,), dtype=MST_edge_dtype
The MST representation of the mutual-reachability graph. The MST is
represented as a collection of edges.
"""
cdef:
uint8_t[::1] in_tree
float64_t[::1] min_reachability
int64_t[::1] current_sources
cnp.ndarray[MST_edge_t, ndim=1, mode='c'] mst
int64_t current_node, source_node, new_node, next_node_source
int64_t i, j, n_samples, num_features
float64_t current_node_core_dist, new_reachability, mutual_reachability_distance
float64_t next_node_min_reach, pair_distance, next_node_core_dist
n_samples = raw_data.shape[0]
num_features = raw_data.shape[1]
mst = np.empty(n_samples - 1, dtype=MST_edge_dtype)
in_tree = np.zeros(n_samples, dtype=np.uint8)
min_reachability = np.full(n_samples, fill_value=np.inf, dtype=np.float64)
current_sources = np.ones(n_samples, dtype=np.int64)
current_node = 0
# The following loop dynamically updates minimum reachability node-by-node,
# avoiding unnecessary computation where possible.
for i in range(0, n_samples - 1):
in_tree[current_node] = 1
current_node_core_dist = core_distances[current_node]
new_reachability = DBL_MAX
source_node = 0
new_node = 0
for j in range(n_samples):
if in_tree[j]:
continue
next_node_min_reach = min_reachability[j]
next_node_source = current_sources[j]
pair_distance = dist_metric.dist(
&raw_data[current_node, 0],
&raw_data[j, 0],
num_features
)
pair_distance /= alpha
next_node_core_dist = core_distances[j]
mutual_reachability_distance = max(
current_node_core_dist,
next_node_core_dist,
pair_distance
)
# If MRD(i, j) is smaller than node j's min_reachability, we update
# node j's min_reachability for future reference.
if mutual_reachability_distance < next_node_min_reach:
min_reachability[j] = mutual_reachability_distance
current_sources[j] = current_node
# If MRD(i, j) is also smaller than node i's current
# min_reachability, we update and set their edge as the current
# MST edge candidate.
if mutual_reachability_distance < new_reachability:
new_reachability = mutual_reachability_distance
source_node = current_node
new_node = j
# If the node j is closer to another node already in the tree, we
# make their edge the current MST candidate edge.
elif next_node_min_reach < new_reachability:
new_reachability = next_node_min_reach
source_node = next_node_source
new_node = j
mst[i].current_node = source_node
mst[i].next_node = new_node
mst[i].distance = new_reachability
current_node = new_node
return mst
cpdef cnp.ndarray[HIERARCHY_t, ndim=1, mode="c"] make_single_linkage(const MST_edge_t[::1] mst):
"""Construct a single-linkage tree from an MST.
Parameters
----------
mst : ndarray of shape (n_samples - 1,), dtype=MST_edge_dtype
The MST representation of the mutual-reachability graph. The MST is
represented as a collection of edges.
Returns
-------
single_linkage : ndarray of shape (n_samples - 1,), dtype=HIERARCHY_dtype
The single-linkage tree tree (dendrogram) built from the MST. Each
of the array represents the following:
- left node/cluster
- right node/cluster
- distance
- new cluster size
"""
cdef:
cnp.ndarray[HIERARCHY_t, ndim=1, mode="c"] single_linkage
# Note mst.shape[0] is one fewer than the number of samples
int64_t n_samples = mst.shape[0] + 1
intp_t current_node_cluster, next_node_cluster
int64_t current_node, next_node, i
float64_t distance
UnionFind U = UnionFind(n_samples)
single_linkage = np.zeros(n_samples - 1, dtype=HIERARCHY_dtype)
for i in range(n_samples - 1):
current_node = mst[i].current_node
next_node = mst[i].next_node
distance = mst[i].distance
current_node_cluster = U.fast_find(current_node)
next_node_cluster = U.fast_find(next_node)
single_linkage[i].left_node = current_node_cluster
single_linkage[i].right_node = next_node_cluster
single_linkage[i].value = distance
single_linkage[i].cluster_size = U.size[current_node_cluster] + U.size[next_node_cluster]
U.union(current_node_cluster, next_node_cluster)
return single_linkage
@@ -1,210 +1,210 @@
# mutual reachability distance computations
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
cimport numpy as cnp
import numpy as np
from scipy.sparse import issparse
from cython cimport floating, integral
from libc.math cimport isfinite, INFINITY
from ...utils._typedefs cimport intp_t
cnp.import_array()
def mutual_reachability_graph(
distance_matrix, min_samples=5, max_distance=0.0
):
"""Compute the weighted adjacency matrix of the mutual reachability graph.
The mutual reachability distance used to build the graph is defined as::
max(d_core(x_p), d_core(x_q), d(x_p, x_q))
and the core distance `d_core` is defined as the distance between a point
`x_p` and its k-th nearest neighbor.
Note that all computations are done in-place.
Parameters
----------
distance_matrix : {ndarray, sparse matrix} of shape (n_samples, n_samples)
Array of distances between samples. If sparse, the array must be in
`CSR` format.
min_samples : int, default=5
The parameter `k` used to calculate the distance between a point
`x_p` and its k-th nearest neighbor.
max_distance : float, default=0.0
The distance which `np.inf` is replaced with. When the true mutual-
reachability distance is measured to be infinite, it is instead
truncated to `max_dist`. Only used when `distance_matrix` is a sparse
matrix.
Returns
-------
mututal_reachability_graph: {ndarray, sparse matrix} of shape \
(n_samples, n_samples)
Weighted adjacency matrix of the mutual reachability graph.
References
----------
.. [1] Campello, R. J., Moulavi, D., & Sander, J. (2013, April).
Density-based clustering based on hierarchical density estimates.
In Pacific-Asia Conference on Knowledge Discovery and Data Mining
(pp. 160-172). Springer Berlin Heidelberg.
"""
further_neighbor_idx = min_samples - 1
if issparse(distance_matrix):
if distance_matrix.format != "csr":
raise ValueError(
"Only sparse CSR matrices are supported for `distance_matrix`."
)
_sparse_mutual_reachability_graph(
distance_matrix.data,
distance_matrix.indices,
distance_matrix.indptr,
distance_matrix.shape[0],
further_neighbor_idx=further_neighbor_idx,
max_distance=max_distance,
)
else:
_dense_mutual_reachability_graph(
distance_matrix, further_neighbor_idx=further_neighbor_idx
)
return distance_matrix
def _dense_mutual_reachability_graph(
floating[:, :] distance_matrix,
intp_t further_neighbor_idx,
):
"""Dense implementation of mutual reachability graph.
The computation is done in-place, i.e. the distance matrix is modified
directly.
Parameters
----------
distance_matrix : ndarray of shape (n_samples, n_samples)
Array of distances between samples.
further_neighbor_idx : int
The index of the furthest neighbor to use to define the core distances.
"""
cdef:
intp_t i, j, n_samples = distance_matrix.shape[0]
floating mutual_reachability_distance
floating[::1] core_distances
# We assume that the distance matrix is symmetric. We choose to sort every
# row to have the same implementation than the sparse case that requires
# CSR matrix.
core_distances = np.ascontiguousarray(
np.partition(
distance_matrix, further_neighbor_idx, axis=1
)[:, further_neighbor_idx]
)
with nogil:
# TODO: Update w/ prange with thread count based on
# _openmp_effective_n_threads
for i in range(n_samples):
for j in range(n_samples):
mutual_reachability_distance = max(
core_distances[i],
core_distances[j],
distance_matrix[i, j],
)
distance_matrix[i, j] = mutual_reachability_distance
def _sparse_mutual_reachability_graph(
cnp.ndarray[floating, ndim=1, mode="c"] data,
cnp.ndarray[integral, ndim=1, mode="c"] indices,
cnp.ndarray[integral, ndim=1, mode="c"] indptr,
intp_t n_samples,
intp_t further_neighbor_idx,
floating max_distance,
):
"""Sparse implementation of mutual reachability graph.
The computation is done in-place, i.e. the distance matrix is modified
directly. This implementation only accepts `CSR` format sparse matrices.
Parameters
----------
distance_matrix : sparse matrix of shape (n_samples, n_samples)
Sparse matrix of distances between samples. The sparse format should
be `CSR`.
further_neighbor_idx : int
The index of the furthest neighbor to use to define the core distances.
max_distance : float
The distance which `np.inf` is replaced with. When the true mutual-
reachability distance is measured to be infinite, it is instead
truncated to `max_dist`. Only used when `distance_matrix` is a sparse
matrix.
"""
cdef:
integral i, col_ind, row_ind
floating mutual_reachability_distance
floating[:] core_distances
floating[:] row_data
if floating is float:
dtype = np.float32
else:
dtype = np.float64
core_distances = np.empty(n_samples, dtype=dtype)
for i in range(n_samples):
row_data = data[indptr[i]:indptr[i + 1]]
if further_neighbor_idx < row_data.size:
core_distances[i] = np.partition(
row_data, further_neighbor_idx
)[further_neighbor_idx]
else:
core_distances[i] = INFINITY
with nogil:
for row_ind in range(n_samples):
for i in range(indptr[row_ind], indptr[row_ind + 1]):
col_ind = indices[i]
mutual_reachability_distance = max(
core_distances[row_ind], core_distances[col_ind], data[i]
)
if isfinite(mutual_reachability_distance):
data[i] = mutual_reachability_distance
elif max_distance > 0:
data[i] = max_distance
# mutual reachability distance computations
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
cimport numpy as cnp
import numpy as np
from scipy.sparse import issparse
from cython cimport floating, integral
from libc.math cimport isfinite, INFINITY
from ...utils._typedefs cimport intp_t
cnp.import_array()
def mutual_reachability_graph(
distance_matrix, min_samples=5, max_distance=0.0
):
"""Compute the weighted adjacency matrix of the mutual reachability graph.
The mutual reachability distance used to build the graph is defined as::
max(d_core(x_p), d_core(x_q), d(x_p, x_q))
and the core distance `d_core` is defined as the distance between a point
`x_p` and its k-th nearest neighbor.
Note that all computations are done in-place.
Parameters
----------
distance_matrix : {ndarray, sparse matrix} of shape (n_samples, n_samples)
Array of distances between samples. If sparse, the array must be in
`CSR` format.
min_samples : int, default=5
The parameter `k` used to calculate the distance between a point
`x_p` and its k-th nearest neighbor.
max_distance : float, default=0.0
The distance which `np.inf` is replaced with. When the true mutual-
reachability distance is measured to be infinite, it is instead
truncated to `max_dist`. Only used when `distance_matrix` is a sparse
matrix.
Returns
-------
mututal_reachability_graph: {ndarray, sparse matrix} of shape \
(n_samples, n_samples)
Weighted adjacency matrix of the mutual reachability graph.
References
----------
.. [1] Campello, R. J., Moulavi, D., & Sander, J. (2013, April).
Density-based clustering based on hierarchical density estimates.
In Pacific-Asia Conference on Knowledge Discovery and Data Mining
(pp. 160-172). Springer Berlin Heidelberg.
"""
further_neighbor_idx = min_samples - 1
if issparse(distance_matrix):
if distance_matrix.format != "csr":
raise ValueError(
"Only sparse CSR matrices are supported for `distance_matrix`."
)
_sparse_mutual_reachability_graph(
distance_matrix.data,
distance_matrix.indices,
distance_matrix.indptr,
distance_matrix.shape[0],
further_neighbor_idx=further_neighbor_idx,
max_distance=max_distance,
)
else:
_dense_mutual_reachability_graph(
distance_matrix, further_neighbor_idx=further_neighbor_idx
)
return distance_matrix
def _dense_mutual_reachability_graph(
floating[:, :] distance_matrix,
intp_t further_neighbor_idx,
):
"""Dense implementation of mutual reachability graph.
The computation is done in-place, i.e. the distance matrix is modified
directly.
Parameters
----------
distance_matrix : ndarray of shape (n_samples, n_samples)
Array of distances between samples.
further_neighbor_idx : int
The index of the furthest neighbor to use to define the core distances.
"""
cdef:
intp_t i, j, n_samples = distance_matrix.shape[0]
floating mutual_reachability_distance
floating[::1] core_distances
# We assume that the distance matrix is symmetric. We choose to sort every
# row to have the same implementation than the sparse case that requires
# CSR matrix.
core_distances = np.ascontiguousarray(
np.partition(
distance_matrix, further_neighbor_idx, axis=1
)[:, further_neighbor_idx]
)
with nogil:
# TODO: Update w/ prange with thread count based on
# _openmp_effective_n_threads
for i in range(n_samples):
for j in range(n_samples):
mutual_reachability_distance = max(
core_distances[i],
core_distances[j],
distance_matrix[i, j],
)
distance_matrix[i, j] = mutual_reachability_distance
def _sparse_mutual_reachability_graph(
cnp.ndarray[floating, ndim=1, mode="c"] data,
cnp.ndarray[integral, ndim=1, mode="c"] indices,
cnp.ndarray[integral, ndim=1, mode="c"] indptr,
intp_t n_samples,
intp_t further_neighbor_idx,
floating max_distance,
):
"""Sparse implementation of mutual reachability graph.
The computation is done in-place, i.e. the distance matrix is modified
directly. This implementation only accepts `CSR` format sparse matrices.
Parameters
----------
distance_matrix : sparse matrix of shape (n_samples, n_samples)
Sparse matrix of distances between samples. The sparse format should
be `CSR`.
further_neighbor_idx : int
The index of the furthest neighbor to use to define the core distances.
max_distance : float
The distance which `np.inf` is replaced with. When the true mutual-
reachability distance is measured to be infinite, it is instead
truncated to `max_dist`. Only used when `distance_matrix` is a sparse
matrix.
"""
cdef:
integral i, col_ind, row_ind
floating mutual_reachability_distance
floating[:] core_distances
floating[:] row_data
if floating is float:
dtype = np.float32
else:
dtype = np.float64
core_distances = np.empty(n_samples, dtype=dtype)
for i in range(n_samples):
row_data = data[indptr[i]:indptr[i + 1]]
if further_neighbor_idx < row_data.size:
core_distances[i] = np.partition(
row_data, further_neighbor_idx
)[further_neighbor_idx]
else:
core_distances[i] = INFINITY
with nogil:
for row_ind in range(n_samples):
for i in range(indptr[row_ind], indptr[row_ind + 1]):
col_ind = indices[i]
mutual_reachability_distance = max(
core_distances[row_ind], core_distances[col_ind], data[i]
)
if isfinite(mutual_reachability_distance):
data[i] = mutual_reachability_distance
elif max_distance > 0:
data[i] = max_distance
@@ -1,49 +1,49 @@
# Copyright (c) 2015, Leland McInnes
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from ...utils._typedefs cimport intp_t, float64_t, uint8_t
cimport numpy as cnp
# This corresponds to the scipy.cluster.hierarchy format
ctypedef packed struct HIERARCHY_t:
intp_t left_node
intp_t right_node
float64_t value
intp_t cluster_size
# Effectively an edgelist encoding a parent/child pair, along with a value and
# the corresponding cluster_size in each row providing a tree structure.
ctypedef packed struct CONDENSED_t:
intp_t parent
intp_t child
float64_t value
intp_t cluster_size
cdef extern from "numpy/arrayobject.h":
intp_t * PyArray_SHAPE(cnp.PyArrayObject *)
# Copyright (c) 2015, Leland McInnes
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from ...utils._typedefs cimport intp_t, float64_t, uint8_t
cimport numpy as cnp
# This corresponds to the scipy.cluster.hierarchy format
ctypedef packed struct HIERARCHY_t:
intp_t left_node
intp_t right_node
float64_t value
intp_t cluster_size
# Effectively an edgelist encoding a parent/child pair, along with a value and
# the corresponding cluster_size in each row providing a tree structure.
ctypedef packed struct CONDENSED_t:
intp_t parent
intp_t child
float64_t value
intp_t cluster_size
cdef extern from "numpy/arrayobject.h":
intp_t * PyArray_SHAPE(cnp.PyArrayObject *)
File diff suppressed because it is too large Load Diff
@@ -1,15 +1,15 @@
cluster_hdbscan_extension_metadata = {
'_linkage': {'sources': [cython_gen.process('_linkage.pyx'), metrics_cython_tree]},
'_reachability': {'sources': [cython_gen.process('_reachability.pyx')]},
'_tree': {'sources': [cython_gen.process('_tree.pyx')]}
}
foreach ext_name, ext_dict : cluster_hdbscan_extension_metadata
py.extension_module(
ext_name,
ext_dict.get('sources'),
dependencies: [np_dep],
subdir: 'sklearn/cluster/_hdbscan',
install: true
)
endforeach
cluster_hdbscan_extension_metadata = {
'_linkage': {'sources': [cython_gen.process('_linkage.pyx'), metrics_cython_tree]},
'_reachability': {'sources': [cython_gen.process('_reachability.pyx')]},
'_tree': {'sources': [cython_gen.process('_tree.pyx')]}
}
foreach ext_name, ext_dict : cluster_hdbscan_extension_metadata
py.extension_module(
ext_name,
ext_dict.get('sources'),
dependencies: [np_dep],
subdir: 'sklearn/cluster/_hdbscan',
install: true
)
endforeach
@@ -1,9 +1,9 @@
from ..utils._typedefs cimport intp_t
cdef class UnionFind:
cdef intp_t next_label
cdef intp_t[:] parent
cdef intp_t[:] size
cdef void union(self, intp_t m, intp_t n) noexcept
cdef intp_t fast_find(self, intp_t n) noexcept
from ..utils._typedefs cimport intp_t
cdef class UnionFind:
cdef intp_t next_label
cdef intp_t[:] parent
cdef intp_t[:] size
cdef void union(self, intp_t m, intp_t n) noexcept
cdef intp_t fast_find(self, intp_t n) noexcept
File diff suppressed because it is too large Load Diff
@@ -1,48 +1,48 @@
from cython cimport floating
cdef floating _euclidean_dense_dense(
const floating*,
const floating*,
int,
bint
) noexcept nogil
cdef floating _euclidean_sparse_dense(
const floating[::1],
const int[::1],
const floating[::1],
floating,
bint
) noexcept nogil
cpdef void _relocate_empty_clusters_dense(
const floating[:, ::1],
const floating[::1],
const floating[:, ::1],
floating[:, ::1],
floating[::1],
const int[::1]
)
cpdef void _relocate_empty_clusters_sparse(
const floating[::1],
const int[::1],
const int[::1],
const floating[::1],
const floating[:, ::1],
floating[:, ::1],
floating[::1],
const int[::1]
)
cdef void _average_centers(
floating[:, ::1],
const floating[::1]
)
cdef void _center_shift(
const floating[:, ::1],
const floating[:, ::1],
floating[::1]
)
from cython cimport floating
cdef floating _euclidean_dense_dense(
const floating*,
const floating*,
int,
bint
) noexcept nogil
cdef floating _euclidean_sparse_dense(
const floating[::1],
const int[::1],
const floating[::1],
floating,
bint
) noexcept nogil
cpdef void _relocate_empty_clusters_dense(
const floating[:, ::1],
const floating[::1],
const floating[:, ::1],
floating[:, ::1],
floating[::1],
const int[::1]
)
cpdef void _relocate_empty_clusters_sparse(
const floating[::1],
const int[::1],
const int[::1],
const floating[::1],
const floating[:, ::1],
floating[:, ::1],
floating[::1],
const int[::1]
)
cdef void _average_centers(
floating[:, ::1],
const floating[::1]
)
cdef void _center_shift(
const floating[:, ::1],
const floating[:, ::1],
floating[::1]
)
@@ -1,328 +1,328 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
import numpy as np
from cython cimport floating
from cython.parallel cimport prange
from libc.math cimport sqrt
from ..utils.extmath import row_norms
# Number of samples per data chunk defined as a global constant.
CHUNK_SIZE = 256
cdef floating _euclidean_dense_dense(
const floating* a, # IN
const floating* b, # IN
int n_features,
bint squared
) noexcept nogil:
"""Euclidean distance between a dense and b dense"""
cdef:
int i
int n = n_features // 4
int rem = n_features % 4
floating result = 0
# We manually unroll the loop for better cache optimization.
for i in range(n):
result += (
(a[0] - b[0]) * (a[0] - b[0]) +
(a[1] - b[1]) * (a[1] - b[1]) +
(a[2] - b[2]) * (a[2] - b[2]) +
(a[3] - b[3]) * (a[3] - b[3])
)
a += 4
b += 4
for i in range(rem):
result += (a[i] - b[i]) * (a[i] - b[i])
return result if squared else sqrt(result)
def _euclidean_dense_dense_wrapper(
const floating[::1] a,
const floating[::1] b,
bint squared
):
"""Wrapper of _euclidean_dense_dense for testing purpose"""
return _euclidean_dense_dense(&a[0], &b[0], a.shape[0], squared)
cdef floating _euclidean_sparse_dense(
const floating[::1] a_data, # IN
const int[::1] a_indices, # IN
const floating[::1] b, # IN
floating b_squared_norm,
bint squared
) noexcept nogil:
"""Euclidean distance between a sparse and b dense"""
cdef:
int nnz = a_indices.shape[0]
int i
floating tmp, bi
floating result = 0.0
for i in range(nnz):
bi = b[a_indices[i]]
tmp = a_data[i] - bi
result += tmp * tmp - bi * bi
result += b_squared_norm
if result < 0:
result = 0.0
return result if squared else sqrt(result)
def _euclidean_sparse_dense_wrapper(
const floating[::1] a_data,
const int[::1] a_indices,
const floating[::1] b,
floating b_squared_norm,
bint squared
):
"""Wrapper of _euclidean_sparse_dense for testing purpose"""
return _euclidean_sparse_dense(
a_data, a_indices, b, b_squared_norm, squared)
cpdef floating _inertia_dense(
const floating[:, ::1] X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers, # IN
const int[::1] labels, # IN
int n_threads,
int single_label=-1,
):
"""Compute inertia for dense input data
Sum of squared distance between each sample and its assigned center.
If single_label is >= 0, the inertia is computed only for that label.
"""
cdef:
int n_samples = X.shape[0]
int n_features = X.shape[1]
int i, j
floating sq_dist = 0.0
floating inertia = 0.0
for i in prange(n_samples, nogil=True, num_threads=n_threads,
schedule='static'):
j = labels[i]
if single_label < 0 or single_label == j:
sq_dist = _euclidean_dense_dense(&X[i, 0], &centers[j, 0],
n_features, True)
inertia += sq_dist * sample_weight[i]
return inertia
cpdef floating _inertia_sparse(
X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers, # IN
const int[::1] labels, # IN
int n_threads,
int single_label=-1,
):
"""Compute inertia for sparse input data
Sum of squared distance between each sample and its assigned center.
If single_label is >= 0, the inertia is computed only for that label.
"""
cdef:
floating[::1] X_data = X.data
int[::1] X_indices = X.indices
int[::1] X_indptr = X.indptr
int n_samples = X.shape[0]
int i, j
floating sq_dist = 0.0
floating inertia = 0.0
floating[::1] centers_squared_norms = row_norms(centers, squared=True)
for i in prange(n_samples, nogil=True, num_threads=n_threads,
schedule='static'):
j = labels[i]
if single_label < 0 or single_label == j:
sq_dist = _euclidean_sparse_dense(
X_data[X_indptr[i]: X_indptr[i + 1]],
X_indices[X_indptr[i]: X_indptr[i + 1]],
centers[j], centers_squared_norms[j], True)
inertia += sq_dist * sample_weight[i]
return inertia
cpdef void _relocate_empty_clusters_dense(
const floating[:, ::1] X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # INOUT
floating[::1] weight_in_clusters, # INOUT
const int[::1] labels # IN
):
"""Relocate centers which have no sample assigned to them."""
cdef:
int[::1] empty_clusters = np.where(np.equal(weight_in_clusters, 0))[0].astype(np.int32)
int n_empty = empty_clusters.shape[0]
if n_empty == 0:
return
cdef:
int n_features = X.shape[1]
floating[::1] distances = ((np.asarray(X) - np.asarray(centers_old)[labels])**2).sum(axis=1)
int[::1] far_from_centers = np.argpartition(distances, -n_empty)[:-n_empty-1:-1].astype(np.int32)
int new_cluster_id, old_cluster_id, far_idx, idx, k
floating weight
if np.max(distances) == 0:
# Happens when there are more clusters than non-duplicate samples. Relocating
# is pointless in this case.
return
for idx in range(n_empty):
new_cluster_id = empty_clusters[idx]
far_idx = far_from_centers[idx]
weight = sample_weight[far_idx]
old_cluster_id = labels[far_idx]
for k in range(n_features):
centers_new[old_cluster_id, k] -= X[far_idx, k] * weight
centers_new[new_cluster_id, k] = X[far_idx, k] * weight
weight_in_clusters[new_cluster_id] = weight
weight_in_clusters[old_cluster_id] -= weight
cpdef void _relocate_empty_clusters_sparse(
const floating[::1] X_data, # IN
const int[::1] X_indices, # IN
const int[::1] X_indptr, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # INOUT
floating[::1] weight_in_clusters, # INOUT
const int[::1] labels # IN
):
"""Relocate centers which have no sample assigned to them."""
cdef:
int[::1] empty_clusters = np.where(np.equal(weight_in_clusters, 0))[0].astype(np.int32)
int n_empty = empty_clusters.shape[0]
if n_empty == 0:
return
cdef:
int n_samples = X_indptr.shape[0] - 1
int i, j, k
floating[::1] distances = np.zeros(n_samples, dtype=X_data.base.dtype)
floating[::1] centers_squared_norms = row_norms(centers_old, squared=True)
for i in range(n_samples):
j = labels[i]
distances[i] = _euclidean_sparse_dense(
X_data[X_indptr[i]: X_indptr[i + 1]],
X_indices[X_indptr[i]: X_indptr[i + 1]],
centers_old[j], centers_squared_norms[j], True)
if np.max(distances) == 0:
# Happens when there are more clusters than non-duplicate samples. Relocating
# is pointless in this case.
return
cdef:
int[::1] far_from_centers = np.argpartition(distances, -n_empty)[:-n_empty-1:-1].astype(np.int32)
int new_cluster_id, old_cluster_id, far_idx, idx
floating weight
for idx in range(n_empty):
new_cluster_id = empty_clusters[idx]
far_idx = far_from_centers[idx]
weight = sample_weight[far_idx]
old_cluster_id = labels[far_idx]
for k in range(X_indptr[far_idx], X_indptr[far_idx + 1]):
centers_new[old_cluster_id, X_indices[k]] -= X_data[k] * weight
centers_new[new_cluster_id, X_indices[k]] = X_data[k] * weight
weight_in_clusters[new_cluster_id] = weight
weight_in_clusters[old_cluster_id] -= weight
cdef void _average_centers(
floating[:, ::1] centers, # INOUT
const floating[::1] weight_in_clusters # IN
):
"""Average new centers wrt weights."""
cdef:
int n_clusters = centers.shape[0]
int n_features = centers.shape[1]
int j, k
floating alpha
int argmax_weight = np.argmax(weight_in_clusters)
for j in range(n_clusters):
if weight_in_clusters[j] > 0:
alpha = 1.0 / weight_in_clusters[j]
for k in range(n_features):
centers[j, k] *= alpha
else:
# For convenience, we avoid setting empty clusters at the origin but place
# them at the location of the biggest cluster.
for k in range(n_features):
centers[j, k] = centers[argmax_weight, k]
cdef void _center_shift(
const floating[:, ::1] centers_old, # IN
const floating[:, ::1] centers_new, # IN
floating[::1] center_shift # OUT
):
"""Compute shift between old and new centers."""
cdef:
int n_clusters = centers_old.shape[0]
int n_features = centers_old.shape[1]
int j
for j in range(n_clusters):
center_shift[j] = _euclidean_dense_dense(
&centers_new[j, 0], &centers_old[j, 0], n_features, False)
def _is_same_clustering(
const int[::1] labels1,
const int[::1] labels2,
n_clusters
):
"""Check if two arrays of labels are the same up to a permutation of the labels"""
cdef int[::1] mapping = np.full(fill_value=-1, shape=(n_clusters,), dtype=np.int32)
cdef int i
for i in range(labels1.shape[0]):
if mapping[labels1[i]] == -1:
mapping[labels1[i]] = labels2[i]
elif mapping[labels1[i]] != labels2[i]:
return False
return True
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
import numpy as np
from cython cimport floating
from cython.parallel cimport prange
from libc.math cimport sqrt
from ..utils.extmath import row_norms
# Number of samples per data chunk defined as a global constant.
CHUNK_SIZE = 256
cdef floating _euclidean_dense_dense(
const floating* a, # IN
const floating* b, # IN
int n_features,
bint squared
) noexcept nogil:
"""Euclidean distance between a dense and b dense"""
cdef:
int i
int n = n_features // 4
int rem = n_features % 4
floating result = 0
# We manually unroll the loop for better cache optimization.
for i in range(n):
result += (
(a[0] - b[0]) * (a[0] - b[0]) +
(a[1] - b[1]) * (a[1] - b[1]) +
(a[2] - b[2]) * (a[2] - b[2]) +
(a[3] - b[3]) * (a[3] - b[3])
)
a += 4
b += 4
for i in range(rem):
result += (a[i] - b[i]) * (a[i] - b[i])
return result if squared else sqrt(result)
def _euclidean_dense_dense_wrapper(
const floating[::1] a,
const floating[::1] b,
bint squared
):
"""Wrapper of _euclidean_dense_dense for testing purpose"""
return _euclidean_dense_dense(&a[0], &b[0], a.shape[0], squared)
cdef floating _euclidean_sparse_dense(
const floating[::1] a_data, # IN
const int[::1] a_indices, # IN
const floating[::1] b, # IN
floating b_squared_norm,
bint squared
) noexcept nogil:
"""Euclidean distance between a sparse and b dense"""
cdef:
int nnz = a_indices.shape[0]
int i
floating tmp, bi
floating result = 0.0
for i in range(nnz):
bi = b[a_indices[i]]
tmp = a_data[i] - bi
result += tmp * tmp - bi * bi
result += b_squared_norm
if result < 0:
result = 0.0
return result if squared else sqrt(result)
def _euclidean_sparse_dense_wrapper(
const floating[::1] a_data,
const int[::1] a_indices,
const floating[::1] b,
floating b_squared_norm,
bint squared
):
"""Wrapper of _euclidean_sparse_dense for testing purpose"""
return _euclidean_sparse_dense(
a_data, a_indices, b, b_squared_norm, squared)
cpdef floating _inertia_dense(
const floating[:, ::1] X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers, # IN
const int[::1] labels, # IN
int n_threads,
int single_label=-1,
):
"""Compute inertia for dense input data
Sum of squared distance between each sample and its assigned center.
If single_label is >= 0, the inertia is computed only for that label.
"""
cdef:
int n_samples = X.shape[0]
int n_features = X.shape[1]
int i, j
floating sq_dist = 0.0
floating inertia = 0.0
for i in prange(n_samples, nogil=True, num_threads=n_threads,
schedule='static'):
j = labels[i]
if single_label < 0 or single_label == j:
sq_dist = _euclidean_dense_dense(&X[i, 0], &centers[j, 0],
n_features, True)
inertia += sq_dist * sample_weight[i]
return inertia
cpdef floating _inertia_sparse(
X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers, # IN
const int[::1] labels, # IN
int n_threads,
int single_label=-1,
):
"""Compute inertia for sparse input data
Sum of squared distance between each sample and its assigned center.
If single_label is >= 0, the inertia is computed only for that label.
"""
cdef:
floating[::1] X_data = X.data
int[::1] X_indices = X.indices
int[::1] X_indptr = X.indptr
int n_samples = X.shape[0]
int i, j
floating sq_dist = 0.0
floating inertia = 0.0
floating[::1] centers_squared_norms = row_norms(centers, squared=True)
for i in prange(n_samples, nogil=True, num_threads=n_threads,
schedule='static'):
j = labels[i]
if single_label < 0 or single_label == j:
sq_dist = _euclidean_sparse_dense(
X_data[X_indptr[i]: X_indptr[i + 1]],
X_indices[X_indptr[i]: X_indptr[i + 1]],
centers[j], centers_squared_norms[j], True)
inertia += sq_dist * sample_weight[i]
return inertia
cpdef void _relocate_empty_clusters_dense(
const floating[:, ::1] X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # INOUT
floating[::1] weight_in_clusters, # INOUT
const int[::1] labels # IN
):
"""Relocate centers which have no sample assigned to them."""
cdef:
int[::1] empty_clusters = np.where(np.equal(weight_in_clusters, 0))[0].astype(np.int32)
int n_empty = empty_clusters.shape[0]
if n_empty == 0:
return
cdef:
int n_features = X.shape[1]
floating[::1] distances = ((np.asarray(X) - np.asarray(centers_old)[labels])**2).sum(axis=1)
int[::1] far_from_centers = np.argpartition(distances, -n_empty)[:-n_empty-1:-1].astype(np.int32)
int new_cluster_id, old_cluster_id, far_idx, idx, k
floating weight
if np.max(distances) == 0:
# Happens when there are more clusters than non-duplicate samples. Relocating
# is pointless in this case.
return
for idx in range(n_empty):
new_cluster_id = empty_clusters[idx]
far_idx = far_from_centers[idx]
weight = sample_weight[far_idx]
old_cluster_id = labels[far_idx]
for k in range(n_features):
centers_new[old_cluster_id, k] -= X[far_idx, k] * weight
centers_new[new_cluster_id, k] = X[far_idx, k] * weight
weight_in_clusters[new_cluster_id] = weight
weight_in_clusters[old_cluster_id] -= weight
cpdef void _relocate_empty_clusters_sparse(
const floating[::1] X_data, # IN
const int[::1] X_indices, # IN
const int[::1] X_indptr, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # INOUT
floating[::1] weight_in_clusters, # INOUT
const int[::1] labels # IN
):
"""Relocate centers which have no sample assigned to them."""
cdef:
int[::1] empty_clusters = np.where(np.equal(weight_in_clusters, 0))[0].astype(np.int32)
int n_empty = empty_clusters.shape[0]
if n_empty == 0:
return
cdef:
int n_samples = X_indptr.shape[0] - 1
int i, j, k
floating[::1] distances = np.zeros(n_samples, dtype=X_data.base.dtype)
floating[::1] centers_squared_norms = row_norms(centers_old, squared=True)
for i in range(n_samples):
j = labels[i]
distances[i] = _euclidean_sparse_dense(
X_data[X_indptr[i]: X_indptr[i + 1]],
X_indices[X_indptr[i]: X_indptr[i + 1]],
centers_old[j], centers_squared_norms[j], True)
if np.max(distances) == 0:
# Happens when there are more clusters than non-duplicate samples. Relocating
# is pointless in this case.
return
cdef:
int[::1] far_from_centers = np.argpartition(distances, -n_empty)[:-n_empty-1:-1].astype(np.int32)
int new_cluster_id, old_cluster_id, far_idx, idx
floating weight
for idx in range(n_empty):
new_cluster_id = empty_clusters[idx]
far_idx = far_from_centers[idx]
weight = sample_weight[far_idx]
old_cluster_id = labels[far_idx]
for k in range(X_indptr[far_idx], X_indptr[far_idx + 1]):
centers_new[old_cluster_id, X_indices[k]] -= X_data[k] * weight
centers_new[new_cluster_id, X_indices[k]] = X_data[k] * weight
weight_in_clusters[new_cluster_id] = weight
weight_in_clusters[old_cluster_id] -= weight
cdef void _average_centers(
floating[:, ::1] centers, # INOUT
const floating[::1] weight_in_clusters # IN
):
"""Average new centers wrt weights."""
cdef:
int n_clusters = centers.shape[0]
int n_features = centers.shape[1]
int j, k
floating alpha
int argmax_weight = np.argmax(weight_in_clusters)
for j in range(n_clusters):
if weight_in_clusters[j] > 0:
alpha = 1.0 / weight_in_clusters[j]
for k in range(n_features):
centers[j, k] *= alpha
else:
# For convenience, we avoid setting empty clusters at the origin but place
# them at the location of the biggest cluster.
for k in range(n_features):
centers[j, k] = centers[argmax_weight, k]
cdef void _center_shift(
const floating[:, ::1] centers_old, # IN
const floating[:, ::1] centers_new, # IN
floating[::1] center_shift # OUT
):
"""Compute shift between old and new centers."""
cdef:
int n_clusters = centers_old.shape[0]
int n_features = centers_old.shape[1]
int j
for j in range(n_clusters):
center_shift[j] = _euclidean_dense_dense(
&centers_new[j, 0], &centers_old[j, 0], n_features, False)
def _is_same_clustering(
const int[::1] labels1,
const int[::1] labels2,
n_clusters
):
"""Check if two arrays of labels are the same up to a permutation of the labels"""
cdef int[::1] mapping = np.full(fill_value=-1, shape=(n_clusters,), dtype=np.int32)
cdef int i
for i in range(labels1.shape[0]):
if mapping[labels1[i]] == -1:
mapping[labels1[i]] = labels2[i]
elif mapping[labels1[i]] != labels2[i]:
return False
return True
File diff suppressed because it is too large Load Diff
@@ -1,420 +1,420 @@
# Licence: BSD 3 clause
from cython cimport floating
from cython.parallel import prange, parallel
from libc.stdlib cimport malloc, calloc, free
from libc.string cimport memset
from libc.float cimport DBL_MAX, FLT_MAX
from ..utils._openmp_helpers cimport omp_lock_t
from ..utils._openmp_helpers cimport omp_init_lock
from ..utils._openmp_helpers cimport omp_destroy_lock
from ..utils._openmp_helpers cimport omp_set_lock
from ..utils._openmp_helpers cimport omp_unset_lock
from ..utils.extmath import row_norms
from ..utils._cython_blas cimport _gemm
from ..utils._cython_blas cimport RowMajor, Trans, NoTrans
from ._k_means_common import CHUNK_SIZE
from ._k_means_common cimport _relocate_empty_clusters_dense
from ._k_means_common cimport _relocate_empty_clusters_sparse
from ._k_means_common cimport _average_centers, _center_shift
def lloyd_iter_chunked_dense(
const floating[:, ::1] X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # OUT
floating[::1] weight_in_clusters, # OUT
int[::1] labels, # OUT
floating[::1] center_shift, # OUT
int n_threads,
bint update_centers=True):
"""Single iteration of K-means lloyd algorithm with dense input.
Update labels and centers (inplace), for one iteration, distributed
over data chunks.
Parameters
----------
X : ndarray of shape (n_samples, n_features), dtype=floating
The observations to cluster.
sample_weight : ndarray of shape (n_samples,), dtype=floating
The weights for each observation in X.
centers_old : ndarray of shape (n_clusters, n_features), dtype=floating
Centers before previous iteration, placeholder for the centers after
previous iteration.
centers_new : ndarray of shape (n_clusters, n_features), dtype=floating
Centers after previous iteration, placeholder for the new centers
computed during this iteration. `centers_new` can be `None` if
`update_centers` is False.
weight_in_clusters : ndarray of shape (n_clusters,), dtype=floating
Placeholder for the sums of the weights of every observation assigned
to each center. `weight_in_clusters` can be `None` if `update_centers`
is False.
labels : ndarray of shape (n_samples,), dtype=int
labels assignment.
center_shift : ndarray of shape (n_clusters,), dtype=floating
Distance between old and new centers.
n_threads : int
The number of threads to be used by openmp.
update_centers : bool
- If True, the labels and the new centers will be computed, i.e. runs
the E-step and the M-step of the algorithm.
- If False, only the labels will be computed, i.e runs the E-step of
the algorithm. This is useful especially when calling predict on a
fitted model.
"""
cdef:
int n_samples = X.shape[0]
int n_features = X.shape[1]
int n_clusters = centers_old.shape[0]
if n_samples == 0:
# An empty array was passed, do nothing and return early (before
# attempting to compute n_chunks). This can typically happen when
# calling the prediction function of a bisecting k-means model with a
# large fraction of outliers.
return
cdef:
# hard-coded number of samples per chunk. Appeared to be close to
# optimal in all situations.
int n_samples_chunk = CHUNK_SIZE if n_samples > CHUNK_SIZE else n_samples
int n_chunks = n_samples // n_samples_chunk
int n_samples_rem = n_samples % n_samples_chunk
int chunk_idx
int start, end
int j, k
floating[::1] centers_squared_norms = row_norms(centers_old, squared=True)
floating *centers_new_chunk
floating *weight_in_clusters_chunk
floating *pairwise_distances_chunk
omp_lock_t lock
# count remainder chunk in total number of chunks
n_chunks += n_samples != n_chunks * n_samples_chunk
# number of threads should not be bigger than number of chunks
n_threads = min(n_threads, n_chunks)
if update_centers:
memset(&centers_new[0, 0], 0, n_clusters * n_features * sizeof(floating))
memset(&weight_in_clusters[0], 0, n_clusters * sizeof(floating))
omp_init_lock(&lock)
with nogil, parallel(num_threads=n_threads):
# thread local buffers
centers_new_chunk = <floating*> calloc(n_clusters * n_features, sizeof(floating))
weight_in_clusters_chunk = <floating*> calloc(n_clusters, sizeof(floating))
pairwise_distances_chunk = <floating*> malloc(n_samples_chunk * n_clusters * sizeof(floating))
for chunk_idx in prange(n_chunks, schedule='static'):
start = chunk_idx * n_samples_chunk
if chunk_idx == n_chunks - 1 and n_samples_rem > 0:
end = start + n_samples_rem
else:
end = start + n_samples_chunk
_update_chunk_dense(
X[start: end],
sample_weight[start: end],
centers_old,
centers_squared_norms,
labels[start: end],
centers_new_chunk,
weight_in_clusters_chunk,
pairwise_distances_chunk,
update_centers)
# reduction from local buffers.
if update_centers:
# The lock is necessary to avoid race conditions when aggregating
# info from different thread-local buffers.
omp_set_lock(&lock)
for j in range(n_clusters):
weight_in_clusters[j] += weight_in_clusters_chunk[j]
for k in range(n_features):
centers_new[j, k] += centers_new_chunk[j * n_features + k]
omp_unset_lock(&lock)
free(centers_new_chunk)
free(weight_in_clusters_chunk)
free(pairwise_distances_chunk)
if update_centers:
omp_destroy_lock(&lock)
_relocate_empty_clusters_dense(
X, sample_weight, centers_old, centers_new, weight_in_clusters, labels
)
_average_centers(centers_new, weight_in_clusters)
_center_shift(centers_old, centers_new, center_shift)
cdef void _update_chunk_dense(
const floating[:, ::1] X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
const floating[::1] centers_squared_norms, # IN
int[::1] labels, # OUT
floating *centers_new, # OUT
floating *weight_in_clusters, # OUT
floating *pairwise_distances, # OUT
bint update_centers) noexcept nogil:
"""K-means combined EM step for one dense data chunk.
Compute the partial contribution of a single data chunk to the labels and
centers.
"""
cdef:
int n_samples = labels.shape[0]
int n_clusters = centers_old.shape[0]
int n_features = centers_old.shape[1]
floating sq_dist, min_sq_dist
int i, j, k, label
# Instead of computing the full pairwise squared distances matrix,
# ||X - C||² = ||X||² - 2 X.C^T + ||C||², we only need to store
# the - 2 X.C^T + ||C||² term since the argmin for a given sample only
# depends on the centers.
# pairwise_distances = ||C||²
for i in range(n_samples):
for j in range(n_clusters):
pairwise_distances[i * n_clusters + j] = centers_squared_norms[j]
# pairwise_distances += -2 * X.dot(C.T)
_gemm(RowMajor, NoTrans, Trans, n_samples, n_clusters, n_features,
-2.0, &X[0, 0], n_features, &centers_old[0, 0], n_features,
1.0, pairwise_distances, n_clusters)
for i in range(n_samples):
min_sq_dist = pairwise_distances[i * n_clusters]
label = 0
for j in range(1, n_clusters):
sq_dist = pairwise_distances[i * n_clusters + j]
if sq_dist < min_sq_dist:
min_sq_dist = sq_dist
label = j
labels[i] = label
if update_centers:
weight_in_clusters[label] += sample_weight[i]
for k in range(n_features):
centers_new[label * n_features + k] += X[i, k] * sample_weight[i]
def lloyd_iter_chunked_sparse(
X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # OUT
floating[::1] weight_in_clusters, # OUT
int[::1] labels, # OUT
floating[::1] center_shift, # OUT
int n_threads,
bint update_centers=True):
"""Single iteration of K-means lloyd algorithm with sparse input.
Update labels and centers (inplace), for one iteration, distributed
over data chunks.
Parameters
----------
X : sparse matrix of shape (n_samples, n_features), dtype=floating
The observations to cluster. Must be in CSR format.
sample_weight : ndarray of shape (n_samples,), dtype=floating
The weights for each observation in X.
centers_old : ndarray of shape (n_clusters, n_features), dtype=floating
Centers before previous iteration, placeholder for the centers after
previous iteration.
centers_new : ndarray of shape (n_clusters, n_features), dtype=floating
Centers after previous iteration, placeholder for the new centers
computed during this iteration. `centers_new` can be `None` if
`update_centers` is False.
weight_in_clusters : ndarray of shape (n_clusters,), dtype=floating
Placeholder for the sums of the weights of every observation assigned
to each center. `weight_in_clusters` can be `None` if `update_centers`
is False.
labels : ndarray of shape (n_samples,), dtype=int
labels assignment.
center_shift : ndarray of shape (n_clusters,), dtype=floating
Distance between old and new centers.
n_threads : int
The number of threads to be used by openmp.
update_centers : bool
- If True, the labels and the new centers will be computed, i.e. runs
the E-step and the M-step of the algorithm.
- If False, only the labels will be computed, i.e runs the E-step of
the algorithm. This is useful especially when calling predict on a
fitted model.
"""
cdef:
int n_samples = X.shape[0]
int n_features = X.shape[1]
int n_clusters = centers_old.shape[0]
if n_samples == 0:
# An empty array was passed, do nothing and return early (before
# attempting to compute n_chunks). This can typically happen when
# calling the prediction function of a bisecting k-means model with a
# large fraction of outliers.
return
cdef:
# Choose same as for dense. Does not have the same impact since with
# sparse data the pairwise distances matrix is not precomputed.
# However, splitting in chunks is necessary to get parallelism.
int n_samples_chunk = CHUNK_SIZE if n_samples > CHUNK_SIZE else n_samples
int n_chunks = n_samples // n_samples_chunk
int n_samples_rem = n_samples % n_samples_chunk
int chunk_idx
int start = 0, end = 0
int j, k
floating[::1] X_data = X.data
int[::1] X_indices = X.indices
int[::1] X_indptr = X.indptr
floating[::1] centers_squared_norms = row_norms(centers_old, squared=True)
floating *centers_new_chunk
floating *weight_in_clusters_chunk
omp_lock_t lock
# count remainder chunk in total number of chunks
n_chunks += n_samples != n_chunks * n_samples_chunk
# number of threads should not be bigger than number of chunks
n_threads = min(n_threads, n_chunks)
if update_centers:
memset(&centers_new[0, 0], 0, n_clusters * n_features * sizeof(floating))
memset(&weight_in_clusters[0], 0, n_clusters * sizeof(floating))
omp_init_lock(&lock)
with nogil, parallel(num_threads=n_threads):
# thread local buffers
centers_new_chunk = <floating*> calloc(n_clusters * n_features, sizeof(floating))
weight_in_clusters_chunk = <floating*> calloc(n_clusters, sizeof(floating))
for chunk_idx in prange(n_chunks, schedule='static'):
start = chunk_idx * n_samples_chunk
if chunk_idx == n_chunks - 1 and n_samples_rem > 0:
end = start + n_samples_rem
else:
end = start + n_samples_chunk
_update_chunk_sparse(
X_data[X_indptr[start]: X_indptr[end]],
X_indices[X_indptr[start]: X_indptr[end]],
X_indptr[start: end+1],
sample_weight[start: end],
centers_old,
centers_squared_norms,
labels[start: end],
centers_new_chunk,
weight_in_clusters_chunk,
update_centers)
# reduction from local buffers.
if update_centers:
# The lock is necessary to avoid race conditions when aggregating
# info from different thread-local buffers.
omp_set_lock(&lock)
for j in range(n_clusters):
weight_in_clusters[j] += weight_in_clusters_chunk[j]
for k in range(n_features):
centers_new[j, k] += centers_new_chunk[j * n_features + k]
omp_unset_lock(&lock)
free(centers_new_chunk)
free(weight_in_clusters_chunk)
if update_centers:
omp_destroy_lock(&lock)
_relocate_empty_clusters_sparse(
X_data, X_indices, X_indptr, sample_weight,
centers_old, centers_new, weight_in_clusters, labels)
_average_centers(centers_new, weight_in_clusters)
_center_shift(centers_old, centers_new, center_shift)
cdef void _update_chunk_sparse(
const floating[::1] X_data, # IN
const int[::1] X_indices, # IN
const int[::1] X_indptr, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
const floating[::1] centers_squared_norms, # IN
int[::1] labels, # OUT
floating *centers_new, # OUT
floating *weight_in_clusters, # OUT
bint update_centers) noexcept nogil:
"""K-means combined EM step for one sparse data chunk.
Compute the partial contribution of a single data chunk to the labels and
centers.
"""
cdef:
int n_samples = labels.shape[0]
int n_clusters = centers_old.shape[0]
int n_features = centers_old.shape[1]
floating sq_dist, min_sq_dist
int i, j, k, label
floating max_floating = FLT_MAX if floating is float else DBL_MAX
int s = X_indptr[0]
# XXX Precompute the pairwise distances matrix is not worth for sparse
# currently. Should be tested when BLAS (sparse x dense) matrix
# multiplication is available.
for i in range(n_samples):
min_sq_dist = max_floating
label = 0
for j in range(n_clusters):
sq_dist = 0.0
for k in range(X_indptr[i] - s, X_indptr[i + 1] - s):
sq_dist += centers_old[j, X_indices[k]] * X_data[k]
# Instead of computing the full squared distance with each cluster,
# ||X - C||² = ||X||² - 2 X.C^T + ||C||², we only need to compute
# the - 2 X.C^T + ||C||² term since the argmin for a given sample
# only depends on the centers C.
sq_dist = centers_squared_norms[j] -2 * sq_dist
if sq_dist < min_sq_dist:
min_sq_dist = sq_dist
label = j
labels[i] = label
if update_centers:
weight_in_clusters[label] += sample_weight[i]
for k in range(X_indptr[i] - s, X_indptr[i + 1] - s):
centers_new[label * n_features + X_indices[k]] += X_data[k] * sample_weight[i]
# Licence: BSD 3 clause
from cython cimport floating
from cython.parallel import prange, parallel
from libc.stdlib cimport malloc, calloc, free
from libc.string cimport memset
from libc.float cimport DBL_MAX, FLT_MAX
from ..utils._openmp_helpers cimport omp_lock_t
from ..utils._openmp_helpers cimport omp_init_lock
from ..utils._openmp_helpers cimport omp_destroy_lock
from ..utils._openmp_helpers cimport omp_set_lock
from ..utils._openmp_helpers cimport omp_unset_lock
from ..utils.extmath import row_norms
from ..utils._cython_blas cimport _gemm
from ..utils._cython_blas cimport RowMajor, Trans, NoTrans
from ._k_means_common import CHUNK_SIZE
from ._k_means_common cimport _relocate_empty_clusters_dense
from ._k_means_common cimport _relocate_empty_clusters_sparse
from ._k_means_common cimport _average_centers, _center_shift
def lloyd_iter_chunked_dense(
const floating[:, ::1] X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # OUT
floating[::1] weight_in_clusters, # OUT
int[::1] labels, # OUT
floating[::1] center_shift, # OUT
int n_threads,
bint update_centers=True):
"""Single iteration of K-means lloyd algorithm with dense input.
Update labels and centers (inplace), for one iteration, distributed
over data chunks.
Parameters
----------
X : ndarray of shape (n_samples, n_features), dtype=floating
The observations to cluster.
sample_weight : ndarray of shape (n_samples,), dtype=floating
The weights for each observation in X.
centers_old : ndarray of shape (n_clusters, n_features), dtype=floating
Centers before previous iteration, placeholder for the centers after
previous iteration.
centers_new : ndarray of shape (n_clusters, n_features), dtype=floating
Centers after previous iteration, placeholder for the new centers
computed during this iteration. `centers_new` can be `None` if
`update_centers` is False.
weight_in_clusters : ndarray of shape (n_clusters,), dtype=floating
Placeholder for the sums of the weights of every observation assigned
to each center. `weight_in_clusters` can be `None` if `update_centers`
is False.
labels : ndarray of shape (n_samples,), dtype=int
labels assignment.
center_shift : ndarray of shape (n_clusters,), dtype=floating
Distance between old and new centers.
n_threads : int
The number of threads to be used by openmp.
update_centers : bool
- If True, the labels and the new centers will be computed, i.e. runs
the E-step and the M-step of the algorithm.
- If False, only the labels will be computed, i.e runs the E-step of
the algorithm. This is useful especially when calling predict on a
fitted model.
"""
cdef:
int n_samples = X.shape[0]
int n_features = X.shape[1]
int n_clusters = centers_old.shape[0]
if n_samples == 0:
# An empty array was passed, do nothing and return early (before
# attempting to compute n_chunks). This can typically happen when
# calling the prediction function of a bisecting k-means model with a
# large fraction of outliers.
return
cdef:
# hard-coded number of samples per chunk. Appeared to be close to
# optimal in all situations.
int n_samples_chunk = CHUNK_SIZE if n_samples > CHUNK_SIZE else n_samples
int n_chunks = n_samples // n_samples_chunk
int n_samples_rem = n_samples % n_samples_chunk
int chunk_idx
int start, end
int j, k
floating[::1] centers_squared_norms = row_norms(centers_old, squared=True)
floating *centers_new_chunk
floating *weight_in_clusters_chunk
floating *pairwise_distances_chunk
omp_lock_t lock
# count remainder chunk in total number of chunks
n_chunks += n_samples != n_chunks * n_samples_chunk
# number of threads should not be bigger than number of chunks
n_threads = min(n_threads, n_chunks)
if update_centers:
memset(&centers_new[0, 0], 0, n_clusters * n_features * sizeof(floating))
memset(&weight_in_clusters[0], 0, n_clusters * sizeof(floating))
omp_init_lock(&lock)
with nogil, parallel(num_threads=n_threads):
# thread local buffers
centers_new_chunk = <floating*> calloc(n_clusters * n_features, sizeof(floating))
weight_in_clusters_chunk = <floating*> calloc(n_clusters, sizeof(floating))
pairwise_distances_chunk = <floating*> malloc(n_samples_chunk * n_clusters * sizeof(floating))
for chunk_idx in prange(n_chunks, schedule='static'):
start = chunk_idx * n_samples_chunk
if chunk_idx == n_chunks - 1 and n_samples_rem > 0:
end = start + n_samples_rem
else:
end = start + n_samples_chunk
_update_chunk_dense(
X[start: end],
sample_weight[start: end],
centers_old,
centers_squared_norms,
labels[start: end],
centers_new_chunk,
weight_in_clusters_chunk,
pairwise_distances_chunk,
update_centers)
# reduction from local buffers.
if update_centers:
# The lock is necessary to avoid race conditions when aggregating
# info from different thread-local buffers.
omp_set_lock(&lock)
for j in range(n_clusters):
weight_in_clusters[j] += weight_in_clusters_chunk[j]
for k in range(n_features):
centers_new[j, k] += centers_new_chunk[j * n_features + k]
omp_unset_lock(&lock)
free(centers_new_chunk)
free(weight_in_clusters_chunk)
free(pairwise_distances_chunk)
if update_centers:
omp_destroy_lock(&lock)
_relocate_empty_clusters_dense(
X, sample_weight, centers_old, centers_new, weight_in_clusters, labels
)
_average_centers(centers_new, weight_in_clusters)
_center_shift(centers_old, centers_new, center_shift)
cdef void _update_chunk_dense(
const floating[:, ::1] X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
const floating[::1] centers_squared_norms, # IN
int[::1] labels, # OUT
floating *centers_new, # OUT
floating *weight_in_clusters, # OUT
floating *pairwise_distances, # OUT
bint update_centers) noexcept nogil:
"""K-means combined EM step for one dense data chunk.
Compute the partial contribution of a single data chunk to the labels and
centers.
"""
cdef:
int n_samples = labels.shape[0]
int n_clusters = centers_old.shape[0]
int n_features = centers_old.shape[1]
floating sq_dist, min_sq_dist
int i, j, k, label
# Instead of computing the full pairwise squared distances matrix,
# ||X - C||² = ||X||² - 2 X.C^T + ||C||², we only need to store
# the - 2 X.C^T + ||C||² term since the argmin for a given sample only
# depends on the centers.
# pairwise_distances = ||C||²
for i in range(n_samples):
for j in range(n_clusters):
pairwise_distances[i * n_clusters + j] = centers_squared_norms[j]
# pairwise_distances += -2 * X.dot(C.T)
_gemm(RowMajor, NoTrans, Trans, n_samples, n_clusters, n_features,
-2.0, &X[0, 0], n_features, &centers_old[0, 0], n_features,
1.0, pairwise_distances, n_clusters)
for i in range(n_samples):
min_sq_dist = pairwise_distances[i * n_clusters]
label = 0
for j in range(1, n_clusters):
sq_dist = pairwise_distances[i * n_clusters + j]
if sq_dist < min_sq_dist:
min_sq_dist = sq_dist
label = j
labels[i] = label
if update_centers:
weight_in_clusters[label] += sample_weight[i]
for k in range(n_features):
centers_new[label * n_features + k] += X[i, k] * sample_weight[i]
def lloyd_iter_chunked_sparse(
X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # OUT
floating[::1] weight_in_clusters, # OUT
int[::1] labels, # OUT
floating[::1] center_shift, # OUT
int n_threads,
bint update_centers=True):
"""Single iteration of K-means lloyd algorithm with sparse input.
Update labels and centers (inplace), for one iteration, distributed
over data chunks.
Parameters
----------
X : sparse matrix of shape (n_samples, n_features), dtype=floating
The observations to cluster. Must be in CSR format.
sample_weight : ndarray of shape (n_samples,), dtype=floating
The weights for each observation in X.
centers_old : ndarray of shape (n_clusters, n_features), dtype=floating
Centers before previous iteration, placeholder for the centers after
previous iteration.
centers_new : ndarray of shape (n_clusters, n_features), dtype=floating
Centers after previous iteration, placeholder for the new centers
computed during this iteration. `centers_new` can be `None` if
`update_centers` is False.
weight_in_clusters : ndarray of shape (n_clusters,), dtype=floating
Placeholder for the sums of the weights of every observation assigned
to each center. `weight_in_clusters` can be `None` if `update_centers`
is False.
labels : ndarray of shape (n_samples,), dtype=int
labels assignment.
center_shift : ndarray of shape (n_clusters,), dtype=floating
Distance between old and new centers.
n_threads : int
The number of threads to be used by openmp.
update_centers : bool
- If True, the labels and the new centers will be computed, i.e. runs
the E-step and the M-step of the algorithm.
- If False, only the labels will be computed, i.e runs the E-step of
the algorithm. This is useful especially when calling predict on a
fitted model.
"""
cdef:
int n_samples = X.shape[0]
int n_features = X.shape[1]
int n_clusters = centers_old.shape[0]
if n_samples == 0:
# An empty array was passed, do nothing and return early (before
# attempting to compute n_chunks). This can typically happen when
# calling the prediction function of a bisecting k-means model with a
# large fraction of outliers.
return
cdef:
# Choose same as for dense. Does not have the same impact since with
# sparse data the pairwise distances matrix is not precomputed.
# However, splitting in chunks is necessary to get parallelism.
int n_samples_chunk = CHUNK_SIZE if n_samples > CHUNK_SIZE else n_samples
int n_chunks = n_samples // n_samples_chunk
int n_samples_rem = n_samples % n_samples_chunk
int chunk_idx
int start = 0, end = 0
int j, k
floating[::1] X_data = X.data
int[::1] X_indices = X.indices
int[::1] X_indptr = X.indptr
floating[::1] centers_squared_norms = row_norms(centers_old, squared=True)
floating *centers_new_chunk
floating *weight_in_clusters_chunk
omp_lock_t lock
# count remainder chunk in total number of chunks
n_chunks += n_samples != n_chunks * n_samples_chunk
# number of threads should not be bigger than number of chunks
n_threads = min(n_threads, n_chunks)
if update_centers:
memset(&centers_new[0, 0], 0, n_clusters * n_features * sizeof(floating))
memset(&weight_in_clusters[0], 0, n_clusters * sizeof(floating))
omp_init_lock(&lock)
with nogil, parallel(num_threads=n_threads):
# thread local buffers
centers_new_chunk = <floating*> calloc(n_clusters * n_features, sizeof(floating))
weight_in_clusters_chunk = <floating*> calloc(n_clusters, sizeof(floating))
for chunk_idx in prange(n_chunks, schedule='static'):
start = chunk_idx * n_samples_chunk
if chunk_idx == n_chunks - 1 and n_samples_rem > 0:
end = start + n_samples_rem
else:
end = start + n_samples_chunk
_update_chunk_sparse(
X_data[X_indptr[start]: X_indptr[end]],
X_indices[X_indptr[start]: X_indptr[end]],
X_indptr[start: end+1],
sample_weight[start: end],
centers_old,
centers_squared_norms,
labels[start: end],
centers_new_chunk,
weight_in_clusters_chunk,
update_centers)
# reduction from local buffers.
if update_centers:
# The lock is necessary to avoid race conditions when aggregating
# info from different thread-local buffers.
omp_set_lock(&lock)
for j in range(n_clusters):
weight_in_clusters[j] += weight_in_clusters_chunk[j]
for k in range(n_features):
centers_new[j, k] += centers_new_chunk[j * n_features + k]
omp_unset_lock(&lock)
free(centers_new_chunk)
free(weight_in_clusters_chunk)
if update_centers:
omp_destroy_lock(&lock)
_relocate_empty_clusters_sparse(
X_data, X_indices, X_indptr, sample_weight,
centers_old, centers_new, weight_in_clusters, labels)
_average_centers(centers_new, weight_in_clusters)
_center_shift(centers_old, centers_new, center_shift)
cdef void _update_chunk_sparse(
const floating[::1] X_data, # IN
const int[::1] X_indices, # IN
const int[::1] X_indptr, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
const floating[::1] centers_squared_norms, # IN
int[::1] labels, # OUT
floating *centers_new, # OUT
floating *weight_in_clusters, # OUT
bint update_centers) noexcept nogil:
"""K-means combined EM step for one sparse data chunk.
Compute the partial contribution of a single data chunk to the labels and
centers.
"""
cdef:
int n_samples = labels.shape[0]
int n_clusters = centers_old.shape[0]
int n_features = centers_old.shape[1]
floating sq_dist, min_sq_dist
int i, j, k, label
floating max_floating = FLT_MAX if floating is float else DBL_MAX
int s = X_indptr[0]
# XXX Precompute the pairwise distances matrix is not worth for sparse
# currently. Should be tested when BLAS (sparse x dense) matrix
# multiplication is available.
for i in range(n_samples):
min_sq_dist = max_floating
label = 0
for j in range(n_clusters):
sq_dist = 0.0
for k in range(X_indptr[i] - s, X_indptr[i + 1] - s):
sq_dist += centers_old[j, X_indices[k]] * X_data[k]
# Instead of computing the full squared distance with each cluster,
# ||X - C||² = ||X||² - 2 X.C^T + ||C||², we only need to compute
# the - 2 X.C^T + ||C||² term since the argmin for a given sample
# only depends on the centers C.
sq_dist = centers_squared_norms[j] -2 * sq_dist
if sq_dist < min_sq_dist:
min_sq_dist = sq_dist
label = j
labels[i] = label
if update_centers:
weight_in_clusters[label] += sample_weight[i]
for k in range(X_indptr[i] - s, X_indptr[i + 1] - s):
centers_new[label * n_features + X_indices[k]] += X_data[k] * sample_weight[i]
@@ -1,218 +1,218 @@
from cython cimport floating
from cython.parallel cimport parallel, prange
from libc.stdlib cimport malloc, free
def _minibatch_update_dense(
const floating[:, ::1] X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # OUT
floating[::1] weight_sums, # INOUT
const int[::1] labels, # IN
int n_threads):
"""Update of the centers for dense MiniBatchKMeans.
Parameters
----------
X : ndarray of shape (n_samples, n_features), dtype=floating
The observations to cluster.
sample_weight : ndarray of shape (n_samples,), dtype=floating
The weights for each observation in X.
centers_old : ndarray of shape (n_clusters, n_features), dtype=floating
Centers before previous iteration, placeholder for the centers after
previous iteration.
centers_new : ndarray of shape (n_clusters, n_features), dtype=floating
Centers after previous iteration, placeholder for the new centers
computed during this iteration.
weight_sums : ndarray of shape (n_clusters,), dtype=floating
Current sums of the accumulated weights for each center.
labels : ndarray of shape (n_samples,), dtype=int
labels assignment.
n_threads : int
The number of threads to be used by openmp.
"""
cdef:
int n_samples = X.shape[0]
int n_clusters = centers_old.shape[0]
int cluster_idx
int *indices
with nogil, parallel(num_threads=n_threads):
indices = <int*> malloc(n_samples * sizeof(int))
for cluster_idx in prange(n_clusters, schedule="static"):
update_center_dense(cluster_idx, X, sample_weight,
centers_old, centers_new, weight_sums, labels,
indices)
free(indices)
cdef void update_center_dense(
int cluster_idx,
const floating[:, ::1] X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # OUT
floating[::1] weight_sums, # INOUT
const int[::1] labels, # IN
int *indices) noexcept nogil: # TMP
"""Update of a single center for dense MinibatchKMeans"""
cdef:
int n_samples = sample_weight.shape[0]
int n_features = centers_old.shape[1]
floating alpha
int n_indices
int k, sample_idx, feature_idx
floating wsum = 0
# indices = np.where(labels == cluster_idx)[0]
k = 0
for sample_idx in range(n_samples):
if labels[sample_idx] == cluster_idx:
indices[k] = sample_idx
wsum += sample_weight[sample_idx]
k += 1
n_indices = k
if wsum > 0:
# Undo the previous count-based scaling for this cluster center
for feature_idx in range(n_features):
centers_new[cluster_idx, feature_idx] = centers_old[cluster_idx, feature_idx] * weight_sums[cluster_idx]
# Update cluster with new point members
for k in range(n_indices):
sample_idx = indices[k]
for feature_idx in range(n_features):
centers_new[cluster_idx, feature_idx] += X[sample_idx, feature_idx] * sample_weight[sample_idx]
# Update the count statistics for this center
weight_sums[cluster_idx] += wsum
# Rescale to compute mean of all points (old and new)
alpha = 1 / weight_sums[cluster_idx]
for feature_idx in range(n_features):
centers_new[cluster_idx, feature_idx] *= alpha
else:
# No sample was assigned to this cluster in this batch of data
for feature_idx in range(n_features):
centers_new[cluster_idx, feature_idx] = centers_old[cluster_idx, feature_idx]
def _minibatch_update_sparse(
X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # OUT
floating[::1] weight_sums, # INOUT
const int[::1] labels, # IN
int n_threads):
"""Update of the centers for sparse MiniBatchKMeans.
Parameters
----------
X : sparse matrix of shape (n_samples, n_features), dtype=floating
The observations to cluster. Must be in CSR format.
sample_weight : ndarray of shape (n_samples,), dtype=floating
The weights for each observation in X.
centers_old : ndarray of shape (n_clusters, n_features), dtype=floating
Centers before previous iteration, placeholder for the centers after
previous iteration.
centers_new : ndarray of shape (n_clusters, n_features), dtype=floating
Centers after previous iteration, placeholder for the new centers
computed during this iteration.
weight_sums : ndarray of shape (n_clusters,), dtype=floating
Current sums of the accumulated weights for each center.
labels : ndarray of shape (n_samples,), dtype=int
labels assignment.
n_threads : int
The number of threads to be used by openmp.
"""
cdef:
floating[::1] X_data = X.data
int[::1] X_indices = X.indices
int[::1] X_indptr = X.indptr
int n_samples = X.shape[0]
int n_clusters = centers_old.shape[0]
int cluster_idx
int *indices
with nogil, parallel(num_threads=n_threads):
indices = <int*> malloc(n_samples * sizeof(int))
for cluster_idx in prange(n_clusters, schedule="static"):
update_center_sparse(cluster_idx, X_data, X_indices, X_indptr,
sample_weight, centers_old, centers_new,
weight_sums, labels, indices)
free(indices)
cdef void update_center_sparse(
int cluster_idx,
const floating[::1] X_data, # IN
const int[::1] X_indices, # IN
const int[::1] X_indptr, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # OUT
floating[::1] weight_sums, # INOUT
const int[::1] labels, # IN
int *indices) noexcept nogil: # TMP
"""Update of a single center for sparse MinibatchKMeans"""
cdef:
int n_samples = sample_weight.shape[0]
int n_features = centers_old.shape[1]
floating alpha
int n_indices
int k, sample_idx, feature_idx
floating wsum = 0
# indices = np.where(labels == cluster_idx)[0]
k = 0
for sample_idx in range(n_samples):
if labels[sample_idx] == cluster_idx:
indices[k] = sample_idx
wsum += sample_weight[sample_idx]
k += 1
n_indices = k
if wsum > 0:
# Undo the previous count-based scaling for this cluster center:
for feature_idx in range(n_features):
centers_new[cluster_idx, feature_idx] = centers_old[cluster_idx, feature_idx] * weight_sums[cluster_idx]
# Update cluster with new point members
for k in range(n_indices):
sample_idx = indices[k]
for feature_idx in range(X_indptr[sample_idx], X_indptr[sample_idx + 1]):
centers_new[cluster_idx, X_indices[feature_idx]] += X_data[feature_idx] * sample_weight[sample_idx]
# Update the count statistics for this center
weight_sums[cluster_idx] += wsum
# Rescale to compute mean of all points (old and new)
alpha = 1 / weight_sums[cluster_idx]
for feature_idx in range(n_features):
centers_new[cluster_idx, feature_idx] *= alpha
else:
# No sample was assigned to this cluster in this batch of data
for feature_idx in range(n_features):
centers_new[cluster_idx, feature_idx] = centers_old[cluster_idx, feature_idx]
from cython cimport floating
from cython.parallel cimport parallel, prange
from libc.stdlib cimport malloc, free
def _minibatch_update_dense(
const floating[:, ::1] X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # OUT
floating[::1] weight_sums, # INOUT
const int[::1] labels, # IN
int n_threads):
"""Update of the centers for dense MiniBatchKMeans.
Parameters
----------
X : ndarray of shape (n_samples, n_features), dtype=floating
The observations to cluster.
sample_weight : ndarray of shape (n_samples,), dtype=floating
The weights for each observation in X.
centers_old : ndarray of shape (n_clusters, n_features), dtype=floating
Centers before previous iteration, placeholder for the centers after
previous iteration.
centers_new : ndarray of shape (n_clusters, n_features), dtype=floating
Centers after previous iteration, placeholder for the new centers
computed during this iteration.
weight_sums : ndarray of shape (n_clusters,), dtype=floating
Current sums of the accumulated weights for each center.
labels : ndarray of shape (n_samples,), dtype=int
labels assignment.
n_threads : int
The number of threads to be used by openmp.
"""
cdef:
int n_samples = X.shape[0]
int n_clusters = centers_old.shape[0]
int cluster_idx
int *indices
with nogil, parallel(num_threads=n_threads):
indices = <int*> malloc(n_samples * sizeof(int))
for cluster_idx in prange(n_clusters, schedule="static"):
update_center_dense(cluster_idx, X, sample_weight,
centers_old, centers_new, weight_sums, labels,
indices)
free(indices)
cdef void update_center_dense(
int cluster_idx,
const floating[:, ::1] X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # OUT
floating[::1] weight_sums, # INOUT
const int[::1] labels, # IN
int *indices) noexcept nogil: # TMP
"""Update of a single center for dense MinibatchKMeans"""
cdef:
int n_samples = sample_weight.shape[0]
int n_features = centers_old.shape[1]
floating alpha
int n_indices
int k, sample_idx, feature_idx
floating wsum = 0
# indices = np.where(labels == cluster_idx)[0]
k = 0
for sample_idx in range(n_samples):
if labels[sample_idx] == cluster_idx:
indices[k] = sample_idx
wsum += sample_weight[sample_idx]
k += 1
n_indices = k
if wsum > 0:
# Undo the previous count-based scaling for this cluster center
for feature_idx in range(n_features):
centers_new[cluster_idx, feature_idx] = centers_old[cluster_idx, feature_idx] * weight_sums[cluster_idx]
# Update cluster with new point members
for k in range(n_indices):
sample_idx = indices[k]
for feature_idx in range(n_features):
centers_new[cluster_idx, feature_idx] += X[sample_idx, feature_idx] * sample_weight[sample_idx]
# Update the count statistics for this center
weight_sums[cluster_idx] += wsum
# Rescale to compute mean of all points (old and new)
alpha = 1 / weight_sums[cluster_idx]
for feature_idx in range(n_features):
centers_new[cluster_idx, feature_idx] *= alpha
else:
# No sample was assigned to this cluster in this batch of data
for feature_idx in range(n_features):
centers_new[cluster_idx, feature_idx] = centers_old[cluster_idx, feature_idx]
def _minibatch_update_sparse(
X, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # OUT
floating[::1] weight_sums, # INOUT
const int[::1] labels, # IN
int n_threads):
"""Update of the centers for sparse MiniBatchKMeans.
Parameters
----------
X : sparse matrix of shape (n_samples, n_features), dtype=floating
The observations to cluster. Must be in CSR format.
sample_weight : ndarray of shape (n_samples,), dtype=floating
The weights for each observation in X.
centers_old : ndarray of shape (n_clusters, n_features), dtype=floating
Centers before previous iteration, placeholder for the centers after
previous iteration.
centers_new : ndarray of shape (n_clusters, n_features), dtype=floating
Centers after previous iteration, placeholder for the new centers
computed during this iteration.
weight_sums : ndarray of shape (n_clusters,), dtype=floating
Current sums of the accumulated weights for each center.
labels : ndarray of shape (n_samples,), dtype=int
labels assignment.
n_threads : int
The number of threads to be used by openmp.
"""
cdef:
floating[::1] X_data = X.data
int[::1] X_indices = X.indices
int[::1] X_indptr = X.indptr
int n_samples = X.shape[0]
int n_clusters = centers_old.shape[0]
int cluster_idx
int *indices
with nogil, parallel(num_threads=n_threads):
indices = <int*> malloc(n_samples * sizeof(int))
for cluster_idx in prange(n_clusters, schedule="static"):
update_center_sparse(cluster_idx, X_data, X_indices, X_indptr,
sample_weight, centers_old, centers_new,
weight_sums, labels, indices)
free(indices)
cdef void update_center_sparse(
int cluster_idx,
const floating[::1] X_data, # IN
const int[::1] X_indices, # IN
const int[::1] X_indptr, # IN
const floating[::1] sample_weight, # IN
const floating[:, ::1] centers_old, # IN
floating[:, ::1] centers_new, # OUT
floating[::1] weight_sums, # INOUT
const int[::1] labels, # IN
int *indices) noexcept nogil: # TMP
"""Update of a single center for sparse MinibatchKMeans"""
cdef:
int n_samples = sample_weight.shape[0]
int n_features = centers_old.shape[1]
floating alpha
int n_indices
int k, sample_idx, feature_idx
floating wsum = 0
# indices = np.where(labels == cluster_idx)[0]
k = 0
for sample_idx in range(n_samples):
if labels[sample_idx] == cluster_idx:
indices[k] = sample_idx
wsum += sample_weight[sample_idx]
k += 1
n_indices = k
if wsum > 0:
# Undo the previous count-based scaling for this cluster center:
for feature_idx in range(n_features):
centers_new[cluster_idx, feature_idx] = centers_old[cluster_idx, feature_idx] * weight_sums[cluster_idx]
# Update cluster with new point members
for k in range(n_indices):
sample_idx = indices[k]
for feature_idx in range(X_indptr[sample_idx], X_indptr[sample_idx + 1]):
centers_new[cluster_idx, X_indices[feature_idx]] += X_data[feature_idx] * sample_weight[sample_idx]
# Update the count statistics for this center
weight_sums[cluster_idx] += wsum
# Rescale to compute mean of all points (old and new)
alpha = 1 / weight_sums[cluster_idx]
for feature_idx in range(n_features):
centers_new[cluster_idx, feature_idx] *= alpha
else:
# No sample was assigned to this cluster in this batch of data
for feature_idx in range(n_features):
centers_new[cluster_idx, feature_idx] = centers_old[cluster_idx, feature_idx]
@@ -1,26 +1,26 @@
cluster_extension_metadata = {
'_dbscan_inner':
{'sources': [cython_gen_cpp.process('_dbscan_inner.pyx')]},
'_hierarchical_fast':
{'sources': [cython_gen_cpp.process('_hierarchical_fast.pyx'), metrics_cython_tree]},
'_k_means_common':
{'sources': [cython_gen.process('_k_means_common.pyx')], 'dependencies': [openmp_dep]},
'_k_means_lloyd':
{'sources': [cython_gen.process('_k_means_lloyd.pyx')], 'dependencies': [openmp_dep]},
'_k_means_elkan':
{'sources': [cython_gen.process('_k_means_elkan.pyx')], 'dependencies': [openmp_dep]},
'_k_means_minibatch':
{'sources': [cython_gen.process('_k_means_minibatch.pyx')], 'dependencies': [openmp_dep]},
}
foreach ext_name, ext_dict : cluster_extension_metadata
py.extension_module(
ext_name,
[ext_dict.get('sources'), utils_cython_tree],
dependencies: [np_dep] + ext_dict.get('dependencies', []),
subdir: 'sklearn/cluster',
install: true
)
endforeach
subdir('_hdbscan')
cluster_extension_metadata = {
'_dbscan_inner':
{'sources': [cython_gen_cpp.process('_dbscan_inner.pyx')]},
'_hierarchical_fast':
{'sources': [cython_gen_cpp.process('_hierarchical_fast.pyx'), metrics_cython_tree]},
'_k_means_common':
{'sources': [cython_gen.process('_k_means_common.pyx')], 'dependencies': [openmp_dep]},
'_k_means_lloyd':
{'sources': [cython_gen.process('_k_means_lloyd.pyx')], 'dependencies': [openmp_dep]},
'_k_means_elkan':
{'sources': [cython_gen.process('_k_means_elkan.pyx')], 'dependencies': [openmp_dep]},
'_k_means_minibatch':
{'sources': [cython_gen.process('_k_means_minibatch.pyx')], 'dependencies': [openmp_dep]},
}
foreach ext_name, ext_dict : cluster_extension_metadata
py.extension_module(
ext_name,
[ext_dict.get('sources'), utils_cython_tree],
dependencies: [np_dep] + ext_dict.get('dependencies', []),
subdir: 'sklearn/cluster',
install: true
)
endforeach
subdir('_hdbscan')
@@ -1,252 +1,252 @@
# Optimized inner loop of load_svmlight_file.
#
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
import array
from cpython cimport array
cimport cython
from libc.string cimport strchr
import numpy as np
cdef bytes COMMA = u','.encode('ascii')
cdef bytes COLON = u':'.encode('ascii')
def _load_svmlight_file(f, dtype, bint multilabel, bint zero_based,
bint query_id, long long offset, long long length):
cdef array.array data, indices, indptr
cdef bytes line
cdef char *hash_ptr
cdef char *line_cstr
cdef int idx, prev_idx
cdef Py_ssize_t i
cdef bytes qid_prefix = b'qid'
cdef Py_ssize_t n_features
cdef long long offset_max = offset + length if length > 0 else -1
# Special-case float32 but use float64 for everything else;
# the Python code will do further conversions.
if dtype == np.float32:
data = array.array("f")
else:
dtype = np.float64
data = array.array("d")
indices = array.array("q")
indptr = array.array("q", [0])
query = np.arange(0, dtype=np.int64)
if multilabel:
labels = []
else:
labels = array.array("d")
if offset > 0:
f.seek(offset)
# drop the current line that might be truncated and is to be
# fetched by another call
f.readline()
for line in f:
# skip comments
line_cstr = line
hash_ptr = strchr(line_cstr, 35) # ASCII value of '#' is 35
if hash_ptr != NULL:
line = line[:hash_ptr - line_cstr]
line_parts = line.split()
if len(line_parts) == 0:
continue
target, features = line_parts[0], line_parts[1:]
if multilabel:
if COLON in target:
target, features = [], line_parts[0:]
else:
target = [float(y) for y in target.split(COMMA)]
target.sort()
labels.append(tuple(target))
else:
array.resize_smart(labels, len(labels) + 1)
labels[len(labels) - 1] = float(target)
prev_idx = -1
n_features = len(features)
if n_features and features[0].startswith(qid_prefix):
_, value = features[0].split(COLON, 1)
if query_id:
query.resize(len(query) + 1)
query[len(query) - 1] = np.int64(value)
features.pop(0)
n_features -= 1
for i in range(0, n_features):
idx_s, value = features[i].split(COLON, 1)
idx = int(idx_s)
if idx < 0 or not zero_based and idx == 0:
raise ValueError(
"Invalid index %d in SVMlight/LibSVM data file." % idx)
if idx <= prev_idx:
raise ValueError("Feature indices in SVMlight/LibSVM data "
"file should be sorted and unique.")
array.resize_smart(indices, len(indices) + 1)
indices[len(indices) - 1] = idx
array.resize_smart(data, len(data) + 1)
data[len(data) - 1] = float(value)
prev_idx = idx
# increment index pointer array size
array.resize_smart(indptr, len(indptr) + 1)
indptr[len(indptr) - 1] = len(data)
if offset_max != -1 and f.tell() > offset_max:
# Stop here and let another call deal with the following.
break
return (dtype, data, indices, indptr, labels, query)
# Two fused types are defined to be able to
# use all possible combinations of parameters.
ctypedef fused int_or_float:
cython.integral
cython.floating
signed long long
ctypedef fused double_or_longlong:
double
signed long long
ctypedef fused int_or_longlong:
cython.integral
signed long long
def get_dense_row_string(
const int_or_float[:, :] X,
Py_ssize_t[:] x_inds,
double_or_longlong[:] x_vals,
Py_ssize_t row,
str value_pattern,
bint one_based,
):
cdef:
Py_ssize_t row_length = X.shape[1]
Py_ssize_t x_nz_used = 0
Py_ssize_t k
int_or_float val
for k in range(row_length):
val = X[row, k]
if val == 0:
continue
x_inds[x_nz_used] = k
x_vals[x_nz_used] = <double_or_longlong> val
x_nz_used += 1
reprs = [
value_pattern % (x_inds[i] + one_based, x_vals[i])
for i in range(x_nz_used)
]
return " ".join(reprs)
def get_sparse_row_string(
int_or_float[:] X_data,
int[:] X_indptr,
int[:] X_indices,
Py_ssize_t row,
str value_pattern,
bint one_based,
):
cdef:
Py_ssize_t row_start = X_indptr[row]
Py_ssize_t row_end = X_indptr[row+1]
reprs = [
value_pattern % (X_indices[i] + one_based, X_data[i])
for i in range(row_start, row_end)
]
return " ".join(reprs)
def _dump_svmlight_file(
X,
y,
f,
bint multilabel,
bint one_based,
int_or_longlong[:] query_id,
bint X_is_sp,
bint y_is_sp,
):
cdef bint X_is_integral
cdef bint query_id_is_not_empty = query_id.size > 0
X_is_integral = X.dtype.kind == "i"
if X_is_integral:
value_pattern = "%d:%d"
else:
value_pattern = "%d:%.16g"
if y.dtype.kind == "i":
label_pattern = "%d"
else:
label_pattern = "%.16g"
line_pattern = "%s"
if query_id_is_not_empty:
line_pattern += " qid:%d"
line_pattern += " %s\n"
cdef:
Py_ssize_t num_labels = y.shape[1]
Py_ssize_t x_len = X.shape[0]
Py_ssize_t row_length = X.shape[1]
Py_ssize_t i
Py_ssize_t j
Py_ssize_t col_start
Py_ssize_t col_end
Py_ssize_t[:] x_inds = np.empty(row_length, dtype=np.intp)
signed long long[:] x_vals_int
double[:] x_vals_float
if not X_is_sp:
if X_is_integral:
x_vals_int = np.zeros(row_length, dtype=np.longlong)
else:
x_vals_float = np.zeros(row_length, dtype=np.float64)
for i in range(x_len):
if not X_is_sp:
if X_is_integral:
s = get_dense_row_string(X, x_inds, x_vals_int, i, value_pattern, one_based)
else:
s = get_dense_row_string(X, x_inds, x_vals_float, i, value_pattern, one_based)
else:
s = get_sparse_row_string(X.data, X.indptr, X.indices, i, value_pattern, one_based)
if multilabel:
if y_is_sp:
col_start = y.indptr[i]
col_end = y.indptr[i+1]
labels_str = ','.join(tuple(label_pattern % y.indices[j] for j in range(col_start, col_end) if y.data[j] != 0))
else:
labels_str = ','.join(label_pattern % j for j in range(num_labels) if y[i, j] != 0)
else:
if y_is_sp:
labels_str = label_pattern % y.data[i]
else:
labels_str = label_pattern % y[i, 0]
if query_id_is_not_empty:
feat = (labels_str, query_id[i], s)
else:
feat = (labels_str, s)
f.write((line_pattern % feat).encode("utf-8"))
# Optimized inner loop of load_svmlight_file.
#
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
import array
from cpython cimport array
cimport cython
from libc.string cimport strchr
import numpy as np
cdef bytes COMMA = u','.encode('ascii')
cdef bytes COLON = u':'.encode('ascii')
def _load_svmlight_file(f, dtype, bint multilabel, bint zero_based,
bint query_id, long long offset, long long length):
cdef array.array data, indices, indptr
cdef bytes line
cdef char *hash_ptr
cdef char *line_cstr
cdef int idx, prev_idx
cdef Py_ssize_t i
cdef bytes qid_prefix = b'qid'
cdef Py_ssize_t n_features
cdef long long offset_max = offset + length if length > 0 else -1
# Special-case float32 but use float64 for everything else;
# the Python code will do further conversions.
if dtype == np.float32:
data = array.array("f")
else:
dtype = np.float64
data = array.array("d")
indices = array.array("q")
indptr = array.array("q", [0])
query = np.arange(0, dtype=np.int64)
if multilabel:
labels = []
else:
labels = array.array("d")
if offset > 0:
f.seek(offset)
# drop the current line that might be truncated and is to be
# fetched by another call
f.readline()
for line in f:
# skip comments
line_cstr = line
hash_ptr = strchr(line_cstr, 35) # ASCII value of '#' is 35
if hash_ptr != NULL:
line = line[:hash_ptr - line_cstr]
line_parts = line.split()
if len(line_parts) == 0:
continue
target, features = line_parts[0], line_parts[1:]
if multilabel:
if COLON in target:
target, features = [], line_parts[0:]
else:
target = [float(y) for y in target.split(COMMA)]
target.sort()
labels.append(tuple(target))
else:
array.resize_smart(labels, len(labels) + 1)
labels[len(labels) - 1] = float(target)
prev_idx = -1
n_features = len(features)
if n_features and features[0].startswith(qid_prefix):
_, value = features[0].split(COLON, 1)
if query_id:
query.resize(len(query) + 1)
query[len(query) - 1] = np.int64(value)
features.pop(0)
n_features -= 1
for i in range(0, n_features):
idx_s, value = features[i].split(COLON, 1)
idx = int(idx_s)
if idx < 0 or not zero_based and idx == 0:
raise ValueError(
"Invalid index %d in SVMlight/LibSVM data file." % idx)
if idx <= prev_idx:
raise ValueError("Feature indices in SVMlight/LibSVM data "
"file should be sorted and unique.")
array.resize_smart(indices, len(indices) + 1)
indices[len(indices) - 1] = idx
array.resize_smart(data, len(data) + 1)
data[len(data) - 1] = float(value)
prev_idx = idx
# increment index pointer array size
array.resize_smart(indptr, len(indptr) + 1)
indptr[len(indptr) - 1] = len(data)
if offset_max != -1 and f.tell() > offset_max:
# Stop here and let another call deal with the following.
break
return (dtype, data, indices, indptr, labels, query)
# Two fused types are defined to be able to
# use all possible combinations of parameters.
ctypedef fused int_or_float:
cython.integral
cython.floating
signed long long
ctypedef fused double_or_longlong:
double
signed long long
ctypedef fused int_or_longlong:
cython.integral
signed long long
def get_dense_row_string(
const int_or_float[:, :] X,
Py_ssize_t[:] x_inds,
double_or_longlong[:] x_vals,
Py_ssize_t row,
str value_pattern,
bint one_based,
):
cdef:
Py_ssize_t row_length = X.shape[1]
Py_ssize_t x_nz_used = 0
Py_ssize_t k
int_or_float val
for k in range(row_length):
val = X[row, k]
if val == 0:
continue
x_inds[x_nz_used] = k
x_vals[x_nz_used] = <double_or_longlong> val
x_nz_used += 1
reprs = [
value_pattern % (x_inds[i] + one_based, x_vals[i])
for i in range(x_nz_used)
]
return " ".join(reprs)
def get_sparse_row_string(
int_or_float[:] X_data,
int[:] X_indptr,
int[:] X_indices,
Py_ssize_t row,
str value_pattern,
bint one_based,
):
cdef:
Py_ssize_t row_start = X_indptr[row]
Py_ssize_t row_end = X_indptr[row+1]
reprs = [
value_pattern % (X_indices[i] + one_based, X_data[i])
for i in range(row_start, row_end)
]
return " ".join(reprs)
def _dump_svmlight_file(
X,
y,
f,
bint multilabel,
bint one_based,
int_or_longlong[:] query_id,
bint X_is_sp,
bint y_is_sp,
):
cdef bint X_is_integral
cdef bint query_id_is_not_empty = query_id.size > 0
X_is_integral = X.dtype.kind == "i"
if X_is_integral:
value_pattern = "%d:%d"
else:
value_pattern = "%d:%.16g"
if y.dtype.kind == "i":
label_pattern = "%d"
else:
label_pattern = "%.16g"
line_pattern = "%s"
if query_id_is_not_empty:
line_pattern += " qid:%d"
line_pattern += " %s\n"
cdef:
Py_ssize_t num_labels = y.shape[1]
Py_ssize_t x_len = X.shape[0]
Py_ssize_t row_length = X.shape[1]
Py_ssize_t i
Py_ssize_t j
Py_ssize_t col_start
Py_ssize_t col_end
Py_ssize_t[:] x_inds = np.empty(row_length, dtype=np.intp)
signed long long[:] x_vals_int
double[:] x_vals_float
if not X_is_sp:
if X_is_integral:
x_vals_int = np.zeros(row_length, dtype=np.longlong)
else:
x_vals_float = np.zeros(row_length, dtype=np.float64)
for i in range(x_len):
if not X_is_sp:
if X_is_integral:
s = get_dense_row_string(X, x_inds, x_vals_int, i, value_pattern, one_based)
else:
s = get_dense_row_string(X, x_inds, x_vals_float, i, value_pattern, one_based)
else:
s = get_sparse_row_string(X.data, X.indptr, X.indices, i, value_pattern, one_based)
if multilabel:
if y_is_sp:
col_start = y.indptr[i]
col_end = y.indptr[i+1]
labels_str = ','.join(tuple(label_pattern % y.indices[j] for j in range(col_start, col_end) if y.data[j] != 0))
else:
labels_str = ','.join(label_pattern % j for j in range(num_labels) if y[i, j] != 0)
else:
if y_is_sp:
labels_str = label_pattern % y.data[i]
else:
labels_str = label_pattern % y[i, 0]
if query_id_is_not_empty:
feat = (labels_str, query_id[i], s)
else:
feat = (labels_str, s)
f.write((line_pattern % feat).encode("utf-8"))
File diff suppressed because it is too large Load Diff
@@ -1,151 +1,151 @@
150,4,setosa,versicolor,virginica
5.1,3.5,1.4,0.2,0
4.9,3.0,1.4,0.2,0
4.7,3.2,1.3,0.2,0
4.6,3.1,1.5,0.2,0
5.0,3.6,1.4,0.2,0
5.4,3.9,1.7,0.4,0
4.6,3.4,1.4,0.3,0
5.0,3.4,1.5,0.2,0
4.4,2.9,1.4,0.2,0
4.9,3.1,1.5,0.1,0
5.4,3.7,1.5,0.2,0
4.8,3.4,1.6,0.2,0
4.8,3.0,1.4,0.1,0
4.3,3.0,1.1,0.1,0
5.8,4.0,1.2,0.2,0
5.7,4.4,1.5,0.4,0
5.4,3.9,1.3,0.4,0
5.1,3.5,1.4,0.3,0
5.7,3.8,1.7,0.3,0
5.1,3.8,1.5,0.3,0
5.4,3.4,1.7,0.2,0
5.1,3.7,1.5,0.4,0
4.6,3.6,1.0,0.2,0
5.1,3.3,1.7,0.5,0
4.8,3.4,1.9,0.2,0
5.0,3.0,1.6,0.2,0
5.0,3.4,1.6,0.4,0
5.2,3.5,1.5,0.2,0
5.2,3.4,1.4,0.2,0
4.7,3.2,1.6,0.2,0
4.8,3.1,1.6,0.2,0
5.4,3.4,1.5,0.4,0
5.2,4.1,1.5,0.1,0
5.5,4.2,1.4,0.2,0
4.9,3.1,1.5,0.2,0
5.0,3.2,1.2,0.2,0
5.5,3.5,1.3,0.2,0
4.9,3.6,1.4,0.1,0
4.4,3.0,1.3,0.2,0
5.1,3.4,1.5,0.2,0
5.0,3.5,1.3,0.3,0
4.5,2.3,1.3,0.3,0
4.4,3.2,1.3,0.2,0
5.0,3.5,1.6,0.6,0
5.1,3.8,1.9,0.4,0
4.8,3.0,1.4,0.3,0
5.1,3.8,1.6,0.2,0
4.6,3.2,1.4,0.2,0
5.3,3.7,1.5,0.2,0
5.0,3.3,1.4,0.2,0
7.0,3.2,4.7,1.4,1
6.4,3.2,4.5,1.5,1
6.9,3.1,4.9,1.5,1
5.5,2.3,4.0,1.3,1
6.5,2.8,4.6,1.5,1
5.7,2.8,4.5,1.3,1
6.3,3.3,4.7,1.6,1
4.9,2.4,3.3,1.0,1
6.6,2.9,4.6,1.3,1
5.2,2.7,3.9,1.4,1
5.0,2.0,3.5,1.0,1
5.9,3.0,4.2,1.5,1
6.0,2.2,4.0,1.0,1
6.1,2.9,4.7,1.4,1
5.6,2.9,3.6,1.3,1
6.7,3.1,4.4,1.4,1
5.6,3.0,4.5,1.5,1
5.8,2.7,4.1,1.0,1
6.2,2.2,4.5,1.5,1
5.6,2.5,3.9,1.1,1
5.9,3.2,4.8,1.8,1
6.1,2.8,4.0,1.3,1
6.3,2.5,4.9,1.5,1
6.1,2.8,4.7,1.2,1
6.4,2.9,4.3,1.3,1
6.6,3.0,4.4,1.4,1
6.8,2.8,4.8,1.4,1
6.7,3.0,5.0,1.7,1
6.0,2.9,4.5,1.5,1
5.7,2.6,3.5,1.0,1
5.5,2.4,3.8,1.1,1
5.5,2.4,3.7,1.0,1
5.8,2.7,3.9,1.2,1
6.0,2.7,5.1,1.6,1
5.4,3.0,4.5,1.5,1
6.0,3.4,4.5,1.6,1
6.7,3.1,4.7,1.5,1
6.3,2.3,4.4,1.3,1
5.6,3.0,4.1,1.3,1
5.5,2.5,4.0,1.3,1
5.5,2.6,4.4,1.2,1
6.1,3.0,4.6,1.4,1
5.8,2.6,4.0,1.2,1
5.0,2.3,3.3,1.0,1
5.6,2.7,4.2,1.3,1
5.7,3.0,4.2,1.2,1
5.7,2.9,4.2,1.3,1
6.2,2.9,4.3,1.3,1
5.1,2.5,3.0,1.1,1
5.7,2.8,4.1,1.3,1
6.3,3.3,6.0,2.5,2
5.8,2.7,5.1,1.9,2
7.1,3.0,5.9,2.1,2
6.3,2.9,5.6,1.8,2
6.5,3.0,5.8,2.2,2
7.6,3.0,6.6,2.1,2
4.9,2.5,4.5,1.7,2
7.3,2.9,6.3,1.8,2
6.7,2.5,5.8,1.8,2
7.2,3.6,6.1,2.5,2
6.5,3.2,5.1,2.0,2
6.4,2.7,5.3,1.9,2
6.8,3.0,5.5,2.1,2
5.7,2.5,5.0,2.0,2
5.8,2.8,5.1,2.4,2
6.4,3.2,5.3,2.3,2
6.5,3.0,5.5,1.8,2
7.7,3.8,6.7,2.2,2
7.7,2.6,6.9,2.3,2
6.0,2.2,5.0,1.5,2
6.9,3.2,5.7,2.3,2
5.6,2.8,4.9,2.0,2
7.7,2.8,6.7,2.0,2
6.3,2.7,4.9,1.8,2
6.7,3.3,5.7,2.1,2
7.2,3.2,6.0,1.8,2
6.2,2.8,4.8,1.8,2
6.1,3.0,4.9,1.8,2
6.4,2.8,5.6,2.1,2
7.2,3.0,5.8,1.6,2
7.4,2.8,6.1,1.9,2
7.9,3.8,6.4,2.0,2
6.4,2.8,5.6,2.2,2
6.3,2.8,5.1,1.5,2
6.1,2.6,5.6,1.4,2
7.7,3.0,6.1,2.3,2
6.3,3.4,5.6,2.4,2
6.4,3.1,5.5,1.8,2
6.0,3.0,4.8,1.8,2
6.9,3.1,5.4,2.1,2
6.7,3.1,5.6,2.4,2
6.9,3.1,5.1,2.3,2
5.8,2.7,5.1,1.9,2
6.8,3.2,5.9,2.3,2
6.7,3.3,5.7,2.5,2
6.7,3.0,5.2,2.3,2
6.3,2.5,5.0,1.9,2
6.5,3.0,5.2,2.0,2
6.2,3.4,5.4,2.3,2
5.9,3.0,5.1,1.8,2
150,4,setosa,versicolor,virginica
5.1,3.5,1.4,0.2,0
4.9,3.0,1.4,0.2,0
4.7,3.2,1.3,0.2,0
4.6,3.1,1.5,0.2,0
5.0,3.6,1.4,0.2,0
5.4,3.9,1.7,0.4,0
4.6,3.4,1.4,0.3,0
5.0,3.4,1.5,0.2,0
4.4,2.9,1.4,0.2,0
4.9,3.1,1.5,0.1,0
5.4,3.7,1.5,0.2,0
4.8,3.4,1.6,0.2,0
4.8,3.0,1.4,0.1,0
4.3,3.0,1.1,0.1,0
5.8,4.0,1.2,0.2,0
5.7,4.4,1.5,0.4,0
5.4,3.9,1.3,0.4,0
5.1,3.5,1.4,0.3,0
5.7,3.8,1.7,0.3,0
5.1,3.8,1.5,0.3,0
5.4,3.4,1.7,0.2,0
5.1,3.7,1.5,0.4,0
4.6,3.6,1.0,0.2,0
5.1,3.3,1.7,0.5,0
4.8,3.4,1.9,0.2,0
5.0,3.0,1.6,0.2,0
5.0,3.4,1.6,0.4,0
5.2,3.5,1.5,0.2,0
5.2,3.4,1.4,0.2,0
4.7,3.2,1.6,0.2,0
4.8,3.1,1.6,0.2,0
5.4,3.4,1.5,0.4,0
5.2,4.1,1.5,0.1,0
5.5,4.2,1.4,0.2,0
4.9,3.1,1.5,0.2,0
5.0,3.2,1.2,0.2,0
5.5,3.5,1.3,0.2,0
4.9,3.6,1.4,0.1,0
4.4,3.0,1.3,0.2,0
5.1,3.4,1.5,0.2,0
5.0,3.5,1.3,0.3,0
4.5,2.3,1.3,0.3,0
4.4,3.2,1.3,0.2,0
5.0,3.5,1.6,0.6,0
5.1,3.8,1.9,0.4,0
4.8,3.0,1.4,0.3,0
5.1,3.8,1.6,0.2,0
4.6,3.2,1.4,0.2,0
5.3,3.7,1.5,0.2,0
5.0,3.3,1.4,0.2,0
7.0,3.2,4.7,1.4,1
6.4,3.2,4.5,1.5,1
6.9,3.1,4.9,1.5,1
5.5,2.3,4.0,1.3,1
6.5,2.8,4.6,1.5,1
5.7,2.8,4.5,1.3,1
6.3,3.3,4.7,1.6,1
4.9,2.4,3.3,1.0,1
6.6,2.9,4.6,1.3,1
5.2,2.7,3.9,1.4,1
5.0,2.0,3.5,1.0,1
5.9,3.0,4.2,1.5,1
6.0,2.2,4.0,1.0,1
6.1,2.9,4.7,1.4,1
5.6,2.9,3.6,1.3,1
6.7,3.1,4.4,1.4,1
5.6,3.0,4.5,1.5,1
5.8,2.7,4.1,1.0,1
6.2,2.2,4.5,1.5,1
5.6,2.5,3.9,1.1,1
5.9,3.2,4.8,1.8,1
6.1,2.8,4.0,1.3,1
6.3,2.5,4.9,1.5,1
6.1,2.8,4.7,1.2,1
6.4,2.9,4.3,1.3,1
6.6,3.0,4.4,1.4,1
6.8,2.8,4.8,1.4,1
6.7,3.0,5.0,1.7,1
6.0,2.9,4.5,1.5,1
5.7,2.6,3.5,1.0,1
5.5,2.4,3.8,1.1,1
5.5,2.4,3.7,1.0,1
5.8,2.7,3.9,1.2,1
6.0,2.7,5.1,1.6,1
5.4,3.0,4.5,1.5,1
6.0,3.4,4.5,1.6,1
6.7,3.1,4.7,1.5,1
6.3,2.3,4.4,1.3,1
5.6,3.0,4.1,1.3,1
5.5,2.5,4.0,1.3,1
5.5,2.6,4.4,1.2,1
6.1,3.0,4.6,1.4,1
5.8,2.6,4.0,1.2,1
5.0,2.3,3.3,1.0,1
5.6,2.7,4.2,1.3,1
5.7,3.0,4.2,1.2,1
5.7,2.9,4.2,1.3,1
6.2,2.9,4.3,1.3,1
5.1,2.5,3.0,1.1,1
5.7,2.8,4.1,1.3,1
6.3,3.3,6.0,2.5,2
5.8,2.7,5.1,1.9,2
7.1,3.0,5.9,2.1,2
6.3,2.9,5.6,1.8,2
6.5,3.0,5.8,2.2,2
7.6,3.0,6.6,2.1,2
4.9,2.5,4.5,1.7,2
7.3,2.9,6.3,1.8,2
6.7,2.5,5.8,1.8,2
7.2,3.6,6.1,2.5,2
6.5,3.2,5.1,2.0,2
6.4,2.7,5.3,1.9,2
6.8,3.0,5.5,2.1,2
5.7,2.5,5.0,2.0,2
5.8,2.8,5.1,2.4,2
6.4,3.2,5.3,2.3,2
6.5,3.0,5.5,1.8,2
7.7,3.8,6.7,2.2,2
7.7,2.6,6.9,2.3,2
6.0,2.2,5.0,1.5,2
6.9,3.2,5.7,2.3,2
5.6,2.8,4.9,2.0,2
7.7,2.8,6.7,2.0,2
6.3,2.7,4.9,1.8,2
6.7,3.3,5.7,2.1,2
7.2,3.2,6.0,1.8,2
6.2,2.8,4.8,1.8,2
6.1,3.0,4.9,1.8,2
6.4,2.8,5.6,2.1,2
7.2,3.0,5.8,1.6,2
7.4,2.8,6.1,1.9,2
7.9,3.8,6.4,2.0,2
6.4,2.8,5.6,2.2,2
6.3,2.8,5.1,1.5,2
6.1,2.6,5.6,1.4,2
7.7,3.0,6.1,2.3,2
6.3,3.4,5.6,2.4,2
6.4,3.1,5.5,1.8,2
6.0,3.0,4.8,1.8,2
6.9,3.1,5.4,2.1,2
6.7,3.1,5.6,2.4,2
6.9,3.1,5.1,2.3,2
5.8,2.7,5.1,1.9,2
6.8,3.2,5.9,2.3,2
6.7,3.3,5.7,2.5,2
6.7,3.0,5.2,2.3,2
6.3,2.5,5.0,1.9,2
6.5,3.0,5.2,2.0,2
6.2,3.4,5.4,2.3,2
5.9,3.0,5.1,1.8,2
1 150 4 setosa versicolor virginica
2 5.1 3.5 1.4 0.2 0
3 4.9 3.0 1.4 0.2 0
4 4.7 3.2 1.3 0.2 0
5 4.6 3.1 1.5 0.2 0
6 5.0 3.6 1.4 0.2 0
7 5.4 3.9 1.7 0.4 0
8 4.6 3.4 1.4 0.3 0
9 5.0 3.4 1.5 0.2 0
10 4.4 2.9 1.4 0.2 0
11 4.9 3.1 1.5 0.1 0
12 5.4 3.7 1.5 0.2 0
13 4.8 3.4 1.6 0.2 0
14 4.8 3.0 1.4 0.1 0
15 4.3 3.0 1.1 0.1 0
16 5.8 4.0 1.2 0.2 0
17 5.7 4.4 1.5 0.4 0
18 5.4 3.9 1.3 0.4 0
19 5.1 3.5 1.4 0.3 0
20 5.7 3.8 1.7 0.3 0
21 5.1 3.8 1.5 0.3 0
22 5.4 3.4 1.7 0.2 0
23 5.1 3.7 1.5 0.4 0
24 4.6 3.6 1.0 0.2 0
25 5.1 3.3 1.7 0.5 0
26 4.8 3.4 1.9 0.2 0
27 5.0 3.0 1.6 0.2 0
28 5.0 3.4 1.6 0.4 0
29 5.2 3.5 1.5 0.2 0
30 5.2 3.4 1.4 0.2 0
31 4.7 3.2 1.6 0.2 0
32 4.8 3.1 1.6 0.2 0
33 5.4 3.4 1.5 0.4 0
34 5.2 4.1 1.5 0.1 0
35 5.5 4.2 1.4 0.2 0
36 4.9 3.1 1.5 0.2 0
37 5.0 3.2 1.2 0.2 0
38 5.5 3.5 1.3 0.2 0
39 4.9 3.6 1.4 0.1 0
40 4.4 3.0 1.3 0.2 0
41 5.1 3.4 1.5 0.2 0
42 5.0 3.5 1.3 0.3 0
43 4.5 2.3 1.3 0.3 0
44 4.4 3.2 1.3 0.2 0
45 5.0 3.5 1.6 0.6 0
46 5.1 3.8 1.9 0.4 0
47 4.8 3.0 1.4 0.3 0
48 5.1 3.8 1.6 0.2 0
49 4.6 3.2 1.4 0.2 0
50 5.3 3.7 1.5 0.2 0
51 5.0 3.3 1.4 0.2 0
52 7.0 3.2 4.7 1.4 1
53 6.4 3.2 4.5 1.5 1
54 6.9 3.1 4.9 1.5 1
55 5.5 2.3 4.0 1.3 1
56 6.5 2.8 4.6 1.5 1
57 5.7 2.8 4.5 1.3 1
58 6.3 3.3 4.7 1.6 1
59 4.9 2.4 3.3 1.0 1
60 6.6 2.9 4.6 1.3 1
61 5.2 2.7 3.9 1.4 1
62 5.0 2.0 3.5 1.0 1
63 5.9 3.0 4.2 1.5 1
64 6.0 2.2 4.0 1.0 1
65 6.1 2.9 4.7 1.4 1
66 5.6 2.9 3.6 1.3 1
67 6.7 3.1 4.4 1.4 1
68 5.6 3.0 4.5 1.5 1
69 5.8 2.7 4.1 1.0 1
70 6.2 2.2 4.5 1.5 1
71 5.6 2.5 3.9 1.1 1
72 5.9 3.2 4.8 1.8 1
73 6.1 2.8 4.0 1.3 1
74 6.3 2.5 4.9 1.5 1
75 6.1 2.8 4.7 1.2 1
76 6.4 2.9 4.3 1.3 1
77 6.6 3.0 4.4 1.4 1
78 6.8 2.8 4.8 1.4 1
79 6.7 3.0 5.0 1.7 1
80 6.0 2.9 4.5 1.5 1
81 5.7 2.6 3.5 1.0 1
82 5.5 2.4 3.8 1.1 1
83 5.5 2.4 3.7 1.0 1
84 5.8 2.7 3.9 1.2 1
85 6.0 2.7 5.1 1.6 1
86 5.4 3.0 4.5 1.5 1
87 6.0 3.4 4.5 1.6 1
88 6.7 3.1 4.7 1.5 1
89 6.3 2.3 4.4 1.3 1
90 5.6 3.0 4.1 1.3 1
91 5.5 2.5 4.0 1.3 1
92 5.5 2.6 4.4 1.2 1
93 6.1 3.0 4.6 1.4 1
94 5.8 2.6 4.0 1.2 1
95 5.0 2.3 3.3 1.0 1
96 5.6 2.7 4.2 1.3 1
97 5.7 3.0 4.2 1.2 1
98 5.7 2.9 4.2 1.3 1
99 6.2 2.9 4.3 1.3 1
100 5.1 2.5 3.0 1.1 1
101 5.7 2.8 4.1 1.3 1
102 6.3 3.3 6.0 2.5 2
103 5.8 2.7 5.1 1.9 2
104 7.1 3.0 5.9 2.1 2
105 6.3 2.9 5.6 1.8 2
106 6.5 3.0 5.8 2.2 2
107 7.6 3.0 6.6 2.1 2
108 4.9 2.5 4.5 1.7 2
109 7.3 2.9 6.3 1.8 2
110 6.7 2.5 5.8 1.8 2
111 7.2 3.6 6.1 2.5 2
112 6.5 3.2 5.1 2.0 2
113 6.4 2.7 5.3 1.9 2
114 6.8 3.0 5.5 2.1 2
115 5.7 2.5 5.0 2.0 2
116 5.8 2.8 5.1 2.4 2
117 6.4 3.2 5.3 2.3 2
118 6.5 3.0 5.5 1.8 2
119 7.7 3.8 6.7 2.2 2
120 7.7 2.6 6.9 2.3 2
121 6.0 2.2 5.0 1.5 2
122 6.9 3.2 5.7 2.3 2
123 5.6 2.8 4.9 2.0 2
124 7.7 2.8 6.7 2.0 2
125 6.3 2.7 4.9 1.8 2
126 6.7 3.3 5.7 2.1 2
127 7.2 3.2 6.0 1.8 2
128 6.2 2.8 4.8 1.8 2
129 6.1 3.0 4.9 1.8 2
130 6.4 2.8 5.6 2.1 2
131 7.2 3.0 5.8 1.6 2
132 7.4 2.8 6.1 1.9 2
133 7.9 3.8 6.4 2.0 2
134 6.4 2.8 5.6 2.2 2
135 6.3 2.8 5.1 1.5 2
136 6.1 2.6 5.6 1.4 2
137 7.7 3.0 6.1 2.3 2
138 6.3 3.4 5.6 2.4 2
139 6.4 3.1 5.5 1.8 2
140 6.0 3.0 4.8 1.8 2
141 6.9 3.1 5.4 2.1 2
142 6.7 3.1 5.6 2.4 2
143 6.9 3.1 5.1 2.3 2
144 5.8 2.7 5.1 1.9 2
145 6.8 3.2 5.9 2.3 2
146 6.7 3.3 5.7 2.5 2
147 6.7 3.0 5.2 2.3 2
148 6.3 2.5 5.0 1.9 2
149 6.5 3.0 5.2 2.0 2
150 6.2 3.4 5.4 2.3 2
151 5.9 3.0 5.1 1.8 2
@@ -1,21 +1,21 @@
Chins Situps Jumps
5 162 60
2 110 60
12 101 101
12 105 37
13 155 58
4 101 42
8 101 38
6 125 40
15 200 40
17 251 250
17 120 38
13 210 115
14 215 105
1 50 50
6 70 31
12 210 120
4 60 25
11 230 80
15 225 73
2 110 43
Chins Situps Jumps
5 162 60
2 110 60
12 101 101
12 105 37
13 155 58
4 101 42
8 101 38
6 125 40
15 200 40
17 251 250
17 120 38
13 210 115
14 215 105
1 50 50
6 70 31
12 210 120
4 60 25
11 230 80
15 225 73
2 110 43
1 Chins Situps Jumps
2 5 162 60
3 2 110 60
4 12 101 101
5 12 105 37
6 13 155 58
7 4 101 42
8 8 101 38
9 6 125 40
10 15 200 40
11 17 251 250
12 17 120 38
13 13 210 115
14 14 215 105
15 1 50 50
16 6 70 31
17 12 210 120
18 4 60 25
19 11 230 80
20 15 225 73
21 2 110 43
@@ -1,21 +1,21 @@
Weight Waist Pulse
191 36 50
189 37 52
193 38 58
162 35 62
189 35 46
182 36 56
211 38 56
167 34 60
176 31 74
154 33 56
169 34 50
166 33 52
154 34 64
247 46 50
193 36 46
202 37 62
176 37 54
157 32 52
156 33 54
138 33 68
Weight Waist Pulse
191 36 50
189 37 52
193 38 58
162 35 62
189 35 46
182 36 56
211 38 56
167 34 60
176 31 74
154 33 56
169 34 50
166 33 52
154 34 64
247 46 50
193 36 46
202 37 62
176 37 54
157 32 52
156 33 54
138 33 68
1 Weight Waist Pulse
2 191 36 50
3 189 37 52
4 193 38 58
5 162 35 62
6 189 35 46
7 182 36 56
8 211 38 56
9 167 34 60
10 176 31 74
11 154 33 56
12 169 34 50
13 166 33 52
14 154 34 64
15 247 46 50
16 193 36 46
17 202 37 62
18 176 37 54
19 157 32 52
20 156 33 54
21 138 33 68
@@ -1,179 +1,179 @@
178,13,class_0,class_1,class_2
14.23,1.71,2.43,15.6,127,2.8,3.06,0.28,2.29,5.64,1.04,3.92,1065,0
13.2,1.78,2.14,11.2,100,2.65,2.76,0.26,1.28,4.38,1.05,3.4,1050,0
13.16,2.36,2.67,18.6,101,2.8,3.24,0.3,2.81,5.68,1.03,3.17,1185,0
14.37,1.95,2.5,16.8,113,3.85,3.49,0.24,2.18,7.8,0.86,3.45,1480,0
13.24,2.59,2.87,21,118,2.8,2.69,0.39,1.82,4.32,1.04,2.93,735,0
14.2,1.76,2.45,15.2,112,3.27,3.39,0.34,1.97,6.75,1.05,2.85,1450,0
14.39,1.87,2.45,14.6,96,2.5,2.52,0.3,1.98,5.25,1.02,3.58,1290,0
14.06,2.15,2.61,17.6,121,2.6,2.51,0.31,1.25,5.05,1.06,3.58,1295,0
14.83,1.64,2.17,14,97,2.8,2.98,0.29,1.98,5.2,1.08,2.85,1045,0
13.86,1.35,2.27,16,98,2.98,3.15,0.22,1.85,7.22,1.01,3.55,1045,0
14.1,2.16,2.3,18,105,2.95,3.32,0.22,2.38,5.75,1.25,3.17,1510,0
14.12,1.48,2.32,16.8,95,2.2,2.43,0.26,1.57,5,1.17,2.82,1280,0
13.75,1.73,2.41,16,89,2.6,2.76,0.29,1.81,5.6,1.15,2.9,1320,0
14.75,1.73,2.39,11.4,91,3.1,3.69,0.43,2.81,5.4,1.25,2.73,1150,0
14.38,1.87,2.38,12,102,3.3,3.64,0.29,2.96,7.5,1.2,3,1547,0
13.63,1.81,2.7,17.2,112,2.85,2.91,0.3,1.46,7.3,1.28,2.88,1310,0
14.3,1.92,2.72,20,120,2.8,3.14,0.33,1.97,6.2,1.07,2.65,1280,0
13.83,1.57,2.62,20,115,2.95,3.4,0.4,1.72,6.6,1.13,2.57,1130,0
14.19,1.59,2.48,16.5,108,3.3,3.93,0.32,1.86,8.7,1.23,2.82,1680,0
13.64,3.1,2.56,15.2,116,2.7,3.03,0.17,1.66,5.1,0.96,3.36,845,0
14.06,1.63,2.28,16,126,3,3.17,0.24,2.1,5.65,1.09,3.71,780,0
12.93,3.8,2.65,18.6,102,2.41,2.41,0.25,1.98,4.5,1.03,3.52,770,0
13.71,1.86,2.36,16.6,101,2.61,2.88,0.27,1.69,3.8,1.11,4,1035,0
12.85,1.6,2.52,17.8,95,2.48,2.37,0.26,1.46,3.93,1.09,3.63,1015,0
13.5,1.81,2.61,20,96,2.53,2.61,0.28,1.66,3.52,1.12,3.82,845,0
13.05,2.05,3.22,25,124,2.63,2.68,0.47,1.92,3.58,1.13,3.2,830,0
13.39,1.77,2.62,16.1,93,2.85,2.94,0.34,1.45,4.8,0.92,3.22,1195,0
13.3,1.72,2.14,17,94,2.4,2.19,0.27,1.35,3.95,1.02,2.77,1285,0
13.87,1.9,2.8,19.4,107,2.95,2.97,0.37,1.76,4.5,1.25,3.4,915,0
14.02,1.68,2.21,16,96,2.65,2.33,0.26,1.98,4.7,1.04,3.59,1035,0
13.73,1.5,2.7,22.5,101,3,3.25,0.29,2.38,5.7,1.19,2.71,1285,0
13.58,1.66,2.36,19.1,106,2.86,3.19,0.22,1.95,6.9,1.09,2.88,1515,0
13.68,1.83,2.36,17.2,104,2.42,2.69,0.42,1.97,3.84,1.23,2.87,990,0
13.76,1.53,2.7,19.5,132,2.95,2.74,0.5,1.35,5.4,1.25,3,1235,0
13.51,1.8,2.65,19,110,2.35,2.53,0.29,1.54,4.2,1.1,2.87,1095,0
13.48,1.81,2.41,20.5,100,2.7,2.98,0.26,1.86,5.1,1.04,3.47,920,0
13.28,1.64,2.84,15.5,110,2.6,2.68,0.34,1.36,4.6,1.09,2.78,880,0
13.05,1.65,2.55,18,98,2.45,2.43,0.29,1.44,4.25,1.12,2.51,1105,0
13.07,1.5,2.1,15.5,98,2.4,2.64,0.28,1.37,3.7,1.18,2.69,1020,0
14.22,3.99,2.51,13.2,128,3,3.04,0.2,2.08,5.1,0.89,3.53,760,0
13.56,1.71,2.31,16.2,117,3.15,3.29,0.34,2.34,6.13,0.95,3.38,795,0
13.41,3.84,2.12,18.8,90,2.45,2.68,0.27,1.48,4.28,0.91,3,1035,0
13.88,1.89,2.59,15,101,3.25,3.56,0.17,1.7,5.43,0.88,3.56,1095,0
13.24,3.98,2.29,17.5,103,2.64,2.63,0.32,1.66,4.36,0.82,3,680,0
13.05,1.77,2.1,17,107,3,3,0.28,2.03,5.04,0.88,3.35,885,0
14.21,4.04,2.44,18.9,111,2.85,2.65,0.3,1.25,5.24,0.87,3.33,1080,0
14.38,3.59,2.28,16,102,3.25,3.17,0.27,2.19,4.9,1.04,3.44,1065,0
13.9,1.68,2.12,16,101,3.1,3.39,0.21,2.14,6.1,0.91,3.33,985,0
14.1,2.02,2.4,18.8,103,2.75,2.92,0.32,2.38,6.2,1.07,2.75,1060,0
13.94,1.73,2.27,17.4,108,2.88,3.54,0.32,2.08,8.9,1.12,3.1,1260,0
13.05,1.73,2.04,12.4,92,2.72,3.27,0.17,2.91,7.2,1.12,2.91,1150,0
13.83,1.65,2.6,17.2,94,2.45,2.99,0.22,2.29,5.6,1.24,3.37,1265,0
13.82,1.75,2.42,14,111,3.88,3.74,0.32,1.87,7.05,1.01,3.26,1190,0
13.77,1.9,2.68,17.1,115,3,2.79,0.39,1.68,6.3,1.13,2.93,1375,0
13.74,1.67,2.25,16.4,118,2.6,2.9,0.21,1.62,5.85,0.92,3.2,1060,0
13.56,1.73,2.46,20.5,116,2.96,2.78,0.2,2.45,6.25,0.98,3.03,1120,0
14.22,1.7,2.3,16.3,118,3.2,3,0.26,2.03,6.38,0.94,3.31,970,0
13.29,1.97,2.68,16.8,102,3,3.23,0.31,1.66,6,1.07,2.84,1270,0
13.72,1.43,2.5,16.7,108,3.4,3.67,0.19,2.04,6.8,0.89,2.87,1285,0
12.37,0.94,1.36,10.6,88,1.98,0.57,0.28,0.42,1.95,1.05,1.82,520,1
12.33,1.1,2.28,16,101,2.05,1.09,0.63,0.41,3.27,1.25,1.67,680,1
12.64,1.36,2.02,16.8,100,2.02,1.41,0.53,0.62,5.75,0.98,1.59,450,1
13.67,1.25,1.92,18,94,2.1,1.79,0.32,0.73,3.8,1.23,2.46,630,1
12.37,1.13,2.16,19,87,3.5,3.1,0.19,1.87,4.45,1.22,2.87,420,1
12.17,1.45,2.53,19,104,1.89,1.75,0.45,1.03,2.95,1.45,2.23,355,1
12.37,1.21,2.56,18.1,98,2.42,2.65,0.37,2.08,4.6,1.19,2.3,678,1
13.11,1.01,1.7,15,78,2.98,3.18,0.26,2.28,5.3,1.12,3.18,502,1
12.37,1.17,1.92,19.6,78,2.11,2,0.27,1.04,4.68,1.12,3.48,510,1
13.34,0.94,2.36,17,110,2.53,1.3,0.55,0.42,3.17,1.02,1.93,750,1
12.21,1.19,1.75,16.8,151,1.85,1.28,0.14,2.5,2.85,1.28,3.07,718,1
12.29,1.61,2.21,20.4,103,1.1,1.02,0.37,1.46,3.05,0.906,1.82,870,1
13.86,1.51,2.67,25,86,2.95,2.86,0.21,1.87,3.38,1.36,3.16,410,1
13.49,1.66,2.24,24,87,1.88,1.84,0.27,1.03,3.74,0.98,2.78,472,1
12.99,1.67,2.6,30,139,3.3,2.89,0.21,1.96,3.35,1.31,3.5,985,1
11.96,1.09,2.3,21,101,3.38,2.14,0.13,1.65,3.21,0.99,3.13,886,1
11.66,1.88,1.92,16,97,1.61,1.57,0.34,1.15,3.8,1.23,2.14,428,1
13.03,0.9,1.71,16,86,1.95,2.03,0.24,1.46,4.6,1.19,2.48,392,1
11.84,2.89,2.23,18,112,1.72,1.32,0.43,0.95,2.65,0.96,2.52,500,1
12.33,0.99,1.95,14.8,136,1.9,1.85,0.35,2.76,3.4,1.06,2.31,750,1
12.7,3.87,2.4,23,101,2.83,2.55,0.43,1.95,2.57,1.19,3.13,463,1
12,0.92,2,19,86,2.42,2.26,0.3,1.43,2.5,1.38,3.12,278,1
12.72,1.81,2.2,18.8,86,2.2,2.53,0.26,1.77,3.9,1.16,3.14,714,1
12.08,1.13,2.51,24,78,2,1.58,0.4,1.4,2.2,1.31,2.72,630,1
13.05,3.86,2.32,22.5,85,1.65,1.59,0.61,1.62,4.8,0.84,2.01,515,1
11.84,0.89,2.58,18,94,2.2,2.21,0.22,2.35,3.05,0.79,3.08,520,1
12.67,0.98,2.24,18,99,2.2,1.94,0.3,1.46,2.62,1.23,3.16,450,1
12.16,1.61,2.31,22.8,90,1.78,1.69,0.43,1.56,2.45,1.33,2.26,495,1
11.65,1.67,2.62,26,88,1.92,1.61,0.4,1.34,2.6,1.36,3.21,562,1
11.64,2.06,2.46,21.6,84,1.95,1.69,0.48,1.35,2.8,1,2.75,680,1
12.08,1.33,2.3,23.6,70,2.2,1.59,0.42,1.38,1.74,1.07,3.21,625,1
12.08,1.83,2.32,18.5,81,1.6,1.5,0.52,1.64,2.4,1.08,2.27,480,1
12,1.51,2.42,22,86,1.45,1.25,0.5,1.63,3.6,1.05,2.65,450,1
12.69,1.53,2.26,20.7,80,1.38,1.46,0.58,1.62,3.05,0.96,2.06,495,1
12.29,2.83,2.22,18,88,2.45,2.25,0.25,1.99,2.15,1.15,3.3,290,1
11.62,1.99,2.28,18,98,3.02,2.26,0.17,1.35,3.25,1.16,2.96,345,1
12.47,1.52,2.2,19,162,2.5,2.27,0.32,3.28,2.6,1.16,2.63,937,1
11.81,2.12,2.74,21.5,134,1.6,0.99,0.14,1.56,2.5,0.95,2.26,625,1
12.29,1.41,1.98,16,85,2.55,2.5,0.29,1.77,2.9,1.23,2.74,428,1
12.37,1.07,2.1,18.5,88,3.52,3.75,0.24,1.95,4.5,1.04,2.77,660,1
12.29,3.17,2.21,18,88,2.85,2.99,0.45,2.81,2.3,1.42,2.83,406,1
12.08,2.08,1.7,17.5,97,2.23,2.17,0.26,1.4,3.3,1.27,2.96,710,1
12.6,1.34,1.9,18.5,88,1.45,1.36,0.29,1.35,2.45,1.04,2.77,562,1
12.34,2.45,2.46,21,98,2.56,2.11,0.34,1.31,2.8,0.8,3.38,438,1
11.82,1.72,1.88,19.5,86,2.5,1.64,0.37,1.42,2.06,0.94,2.44,415,1
12.51,1.73,1.98,20.5,85,2.2,1.92,0.32,1.48,2.94,1.04,3.57,672,1
12.42,2.55,2.27,22,90,1.68,1.84,0.66,1.42,2.7,0.86,3.3,315,1
12.25,1.73,2.12,19,80,1.65,2.03,0.37,1.63,3.4,1,3.17,510,1
12.72,1.75,2.28,22.5,84,1.38,1.76,0.48,1.63,3.3,0.88,2.42,488,1
12.22,1.29,1.94,19,92,2.36,2.04,0.39,2.08,2.7,0.86,3.02,312,1
11.61,1.35,2.7,20,94,2.74,2.92,0.29,2.49,2.65,0.96,3.26,680,1
11.46,3.74,1.82,19.5,107,3.18,2.58,0.24,3.58,2.9,0.75,2.81,562,1
12.52,2.43,2.17,21,88,2.55,2.27,0.26,1.22,2,0.9,2.78,325,1
11.76,2.68,2.92,20,103,1.75,2.03,0.6,1.05,3.8,1.23,2.5,607,1
11.41,0.74,2.5,21,88,2.48,2.01,0.42,1.44,3.08,1.1,2.31,434,1
12.08,1.39,2.5,22.5,84,2.56,2.29,0.43,1.04,2.9,0.93,3.19,385,1
11.03,1.51,2.2,21.5,85,2.46,2.17,0.52,2.01,1.9,1.71,2.87,407,1
11.82,1.47,1.99,20.8,86,1.98,1.6,0.3,1.53,1.95,0.95,3.33,495,1
12.42,1.61,2.19,22.5,108,2,2.09,0.34,1.61,2.06,1.06,2.96,345,1
12.77,3.43,1.98,16,80,1.63,1.25,0.43,0.83,3.4,0.7,2.12,372,1
12,3.43,2,19,87,2,1.64,0.37,1.87,1.28,0.93,3.05,564,1
11.45,2.4,2.42,20,96,2.9,2.79,0.32,1.83,3.25,0.8,3.39,625,1
11.56,2.05,3.23,28.5,119,3.18,5.08,0.47,1.87,6,0.93,3.69,465,1
12.42,4.43,2.73,26.5,102,2.2,2.13,0.43,1.71,2.08,0.92,3.12,365,1
13.05,5.8,2.13,21.5,86,2.62,2.65,0.3,2.01,2.6,0.73,3.1,380,1
11.87,4.31,2.39,21,82,2.86,3.03,0.21,2.91,2.8,0.75,3.64,380,1
12.07,2.16,2.17,21,85,2.6,2.65,0.37,1.35,2.76,0.86,3.28,378,1
12.43,1.53,2.29,21.5,86,2.74,3.15,0.39,1.77,3.94,0.69,2.84,352,1
11.79,2.13,2.78,28.5,92,2.13,2.24,0.58,1.76,3,0.97,2.44,466,1
12.37,1.63,2.3,24.5,88,2.22,2.45,0.4,1.9,2.12,0.89,2.78,342,1
12.04,4.3,2.38,22,80,2.1,1.75,0.42,1.35,2.6,0.79,2.57,580,1
12.86,1.35,2.32,18,122,1.51,1.25,0.21,0.94,4.1,0.76,1.29,630,2
12.88,2.99,2.4,20,104,1.3,1.22,0.24,0.83,5.4,0.74,1.42,530,2
12.81,2.31,2.4,24,98,1.15,1.09,0.27,0.83,5.7,0.66,1.36,560,2
12.7,3.55,2.36,21.5,106,1.7,1.2,0.17,0.84,5,0.78,1.29,600,2
12.51,1.24,2.25,17.5,85,2,0.58,0.6,1.25,5.45,0.75,1.51,650,2
12.6,2.46,2.2,18.5,94,1.62,0.66,0.63,0.94,7.1,0.73,1.58,695,2
12.25,4.72,2.54,21,89,1.38,0.47,0.53,0.8,3.85,0.75,1.27,720,2
12.53,5.51,2.64,25,96,1.79,0.6,0.63,1.1,5,0.82,1.69,515,2
13.49,3.59,2.19,19.5,88,1.62,0.48,0.58,0.88,5.7,0.81,1.82,580,2
12.84,2.96,2.61,24,101,2.32,0.6,0.53,0.81,4.92,0.89,2.15,590,2
12.93,2.81,2.7,21,96,1.54,0.5,0.53,0.75,4.6,0.77,2.31,600,2
13.36,2.56,2.35,20,89,1.4,0.5,0.37,0.64,5.6,0.7,2.47,780,2
13.52,3.17,2.72,23.5,97,1.55,0.52,0.5,0.55,4.35,0.89,2.06,520,2
13.62,4.95,2.35,20,92,2,0.8,0.47,1.02,4.4,0.91,2.05,550,2
12.25,3.88,2.2,18.5,112,1.38,0.78,0.29,1.14,8.21,0.65,2,855,2
13.16,3.57,2.15,21,102,1.5,0.55,0.43,1.3,4,0.6,1.68,830,2
13.88,5.04,2.23,20,80,0.98,0.34,0.4,0.68,4.9,0.58,1.33,415,2
12.87,4.61,2.48,21.5,86,1.7,0.65,0.47,0.86,7.65,0.54,1.86,625,2
13.32,3.24,2.38,21.5,92,1.93,0.76,0.45,1.25,8.42,0.55,1.62,650,2
13.08,3.9,2.36,21.5,113,1.41,1.39,0.34,1.14,9.4,0.57,1.33,550,2
13.5,3.12,2.62,24,123,1.4,1.57,0.22,1.25,8.6,0.59,1.3,500,2
12.79,2.67,2.48,22,112,1.48,1.36,0.24,1.26,10.8,0.48,1.47,480,2
13.11,1.9,2.75,25.5,116,2.2,1.28,0.26,1.56,7.1,0.61,1.33,425,2
13.23,3.3,2.28,18.5,98,1.8,0.83,0.61,1.87,10.52,0.56,1.51,675,2
12.58,1.29,2.1,20,103,1.48,0.58,0.53,1.4,7.6,0.58,1.55,640,2
13.17,5.19,2.32,22,93,1.74,0.63,0.61,1.55,7.9,0.6,1.48,725,2
13.84,4.12,2.38,19.5,89,1.8,0.83,0.48,1.56,9.01,0.57,1.64,480,2
12.45,3.03,2.64,27,97,1.9,0.58,0.63,1.14,7.5,0.67,1.73,880,2
14.34,1.68,2.7,25,98,2.8,1.31,0.53,2.7,13,0.57,1.96,660,2
13.48,1.67,2.64,22.5,89,2.6,1.1,0.52,2.29,11.75,0.57,1.78,620,2
12.36,3.83,2.38,21,88,2.3,0.92,0.5,1.04,7.65,0.56,1.58,520,2
13.69,3.26,2.54,20,107,1.83,0.56,0.5,0.8,5.88,0.96,1.82,680,2
12.85,3.27,2.58,22,106,1.65,0.6,0.6,0.96,5.58,0.87,2.11,570,2
12.96,3.45,2.35,18.5,106,1.39,0.7,0.4,0.94,5.28,0.68,1.75,675,2
13.78,2.76,2.3,22,90,1.35,0.68,0.41,1.03,9.58,0.7,1.68,615,2
13.73,4.36,2.26,22.5,88,1.28,0.47,0.52,1.15,6.62,0.78,1.75,520,2
13.45,3.7,2.6,23,111,1.7,0.92,0.43,1.46,10.68,0.85,1.56,695,2
12.82,3.37,2.3,19.5,88,1.48,0.66,0.4,0.97,10.26,0.72,1.75,685,2
13.58,2.58,2.69,24.5,105,1.55,0.84,0.39,1.54,8.66,0.74,1.8,750,2
13.4,4.6,2.86,25,112,1.98,0.96,0.27,1.11,8.5,0.67,1.92,630,2
12.2,3.03,2.32,19,96,1.25,0.49,0.4,0.73,5.5,0.66,1.83,510,2
12.77,2.39,2.28,19.5,86,1.39,0.51,0.48,0.64,9.899999,0.57,1.63,470,2
14.16,2.51,2.48,20,91,1.68,0.7,0.44,1.24,9.7,0.62,1.71,660,2
13.71,5.65,2.45,20.5,95,1.68,0.61,0.52,1.06,7.7,0.64,1.74,740,2
13.4,3.91,2.48,23,102,1.8,0.75,0.43,1.41,7.3,0.7,1.56,750,2
13.27,4.28,2.26,20,120,1.59,0.69,0.43,1.35,10.2,0.59,1.56,835,2
13.17,2.59,2.37,20,120,1.65,0.68,0.53,1.46,9.3,0.6,1.62,840,2
14.13,4.1,2.74,24.5,96,2.05,0.76,0.56,1.35,9.2,0.61,1.6,560,2
178,13,class_0,class_1,class_2
14.23,1.71,2.43,15.6,127,2.8,3.06,0.28,2.29,5.64,1.04,3.92,1065,0
13.2,1.78,2.14,11.2,100,2.65,2.76,0.26,1.28,4.38,1.05,3.4,1050,0
13.16,2.36,2.67,18.6,101,2.8,3.24,0.3,2.81,5.68,1.03,3.17,1185,0
14.37,1.95,2.5,16.8,113,3.85,3.49,0.24,2.18,7.8,0.86,3.45,1480,0
13.24,2.59,2.87,21,118,2.8,2.69,0.39,1.82,4.32,1.04,2.93,735,0
14.2,1.76,2.45,15.2,112,3.27,3.39,0.34,1.97,6.75,1.05,2.85,1450,0
14.39,1.87,2.45,14.6,96,2.5,2.52,0.3,1.98,5.25,1.02,3.58,1290,0
14.06,2.15,2.61,17.6,121,2.6,2.51,0.31,1.25,5.05,1.06,3.58,1295,0
14.83,1.64,2.17,14,97,2.8,2.98,0.29,1.98,5.2,1.08,2.85,1045,0
13.86,1.35,2.27,16,98,2.98,3.15,0.22,1.85,7.22,1.01,3.55,1045,0
14.1,2.16,2.3,18,105,2.95,3.32,0.22,2.38,5.75,1.25,3.17,1510,0
14.12,1.48,2.32,16.8,95,2.2,2.43,0.26,1.57,5,1.17,2.82,1280,0
13.75,1.73,2.41,16,89,2.6,2.76,0.29,1.81,5.6,1.15,2.9,1320,0
14.75,1.73,2.39,11.4,91,3.1,3.69,0.43,2.81,5.4,1.25,2.73,1150,0
14.38,1.87,2.38,12,102,3.3,3.64,0.29,2.96,7.5,1.2,3,1547,0
13.63,1.81,2.7,17.2,112,2.85,2.91,0.3,1.46,7.3,1.28,2.88,1310,0
14.3,1.92,2.72,20,120,2.8,3.14,0.33,1.97,6.2,1.07,2.65,1280,0
13.83,1.57,2.62,20,115,2.95,3.4,0.4,1.72,6.6,1.13,2.57,1130,0
14.19,1.59,2.48,16.5,108,3.3,3.93,0.32,1.86,8.7,1.23,2.82,1680,0
13.64,3.1,2.56,15.2,116,2.7,3.03,0.17,1.66,5.1,0.96,3.36,845,0
14.06,1.63,2.28,16,126,3,3.17,0.24,2.1,5.65,1.09,3.71,780,0
12.93,3.8,2.65,18.6,102,2.41,2.41,0.25,1.98,4.5,1.03,3.52,770,0
13.71,1.86,2.36,16.6,101,2.61,2.88,0.27,1.69,3.8,1.11,4,1035,0
12.85,1.6,2.52,17.8,95,2.48,2.37,0.26,1.46,3.93,1.09,3.63,1015,0
13.5,1.81,2.61,20,96,2.53,2.61,0.28,1.66,3.52,1.12,3.82,845,0
13.05,2.05,3.22,25,124,2.63,2.68,0.47,1.92,3.58,1.13,3.2,830,0
13.39,1.77,2.62,16.1,93,2.85,2.94,0.34,1.45,4.8,0.92,3.22,1195,0
13.3,1.72,2.14,17,94,2.4,2.19,0.27,1.35,3.95,1.02,2.77,1285,0
13.87,1.9,2.8,19.4,107,2.95,2.97,0.37,1.76,4.5,1.25,3.4,915,0
14.02,1.68,2.21,16,96,2.65,2.33,0.26,1.98,4.7,1.04,3.59,1035,0
13.73,1.5,2.7,22.5,101,3,3.25,0.29,2.38,5.7,1.19,2.71,1285,0
13.58,1.66,2.36,19.1,106,2.86,3.19,0.22,1.95,6.9,1.09,2.88,1515,0
13.68,1.83,2.36,17.2,104,2.42,2.69,0.42,1.97,3.84,1.23,2.87,990,0
13.76,1.53,2.7,19.5,132,2.95,2.74,0.5,1.35,5.4,1.25,3,1235,0
13.51,1.8,2.65,19,110,2.35,2.53,0.29,1.54,4.2,1.1,2.87,1095,0
13.48,1.81,2.41,20.5,100,2.7,2.98,0.26,1.86,5.1,1.04,3.47,920,0
13.28,1.64,2.84,15.5,110,2.6,2.68,0.34,1.36,4.6,1.09,2.78,880,0
13.05,1.65,2.55,18,98,2.45,2.43,0.29,1.44,4.25,1.12,2.51,1105,0
13.07,1.5,2.1,15.5,98,2.4,2.64,0.28,1.37,3.7,1.18,2.69,1020,0
14.22,3.99,2.51,13.2,128,3,3.04,0.2,2.08,5.1,0.89,3.53,760,0
13.56,1.71,2.31,16.2,117,3.15,3.29,0.34,2.34,6.13,0.95,3.38,795,0
13.41,3.84,2.12,18.8,90,2.45,2.68,0.27,1.48,4.28,0.91,3,1035,0
13.88,1.89,2.59,15,101,3.25,3.56,0.17,1.7,5.43,0.88,3.56,1095,0
13.24,3.98,2.29,17.5,103,2.64,2.63,0.32,1.66,4.36,0.82,3,680,0
13.05,1.77,2.1,17,107,3,3,0.28,2.03,5.04,0.88,3.35,885,0
14.21,4.04,2.44,18.9,111,2.85,2.65,0.3,1.25,5.24,0.87,3.33,1080,0
14.38,3.59,2.28,16,102,3.25,3.17,0.27,2.19,4.9,1.04,3.44,1065,0
13.9,1.68,2.12,16,101,3.1,3.39,0.21,2.14,6.1,0.91,3.33,985,0
14.1,2.02,2.4,18.8,103,2.75,2.92,0.32,2.38,6.2,1.07,2.75,1060,0
13.94,1.73,2.27,17.4,108,2.88,3.54,0.32,2.08,8.9,1.12,3.1,1260,0
13.05,1.73,2.04,12.4,92,2.72,3.27,0.17,2.91,7.2,1.12,2.91,1150,0
13.83,1.65,2.6,17.2,94,2.45,2.99,0.22,2.29,5.6,1.24,3.37,1265,0
13.82,1.75,2.42,14,111,3.88,3.74,0.32,1.87,7.05,1.01,3.26,1190,0
13.77,1.9,2.68,17.1,115,3,2.79,0.39,1.68,6.3,1.13,2.93,1375,0
13.74,1.67,2.25,16.4,118,2.6,2.9,0.21,1.62,5.85,0.92,3.2,1060,0
13.56,1.73,2.46,20.5,116,2.96,2.78,0.2,2.45,6.25,0.98,3.03,1120,0
14.22,1.7,2.3,16.3,118,3.2,3,0.26,2.03,6.38,0.94,3.31,970,0
13.29,1.97,2.68,16.8,102,3,3.23,0.31,1.66,6,1.07,2.84,1270,0
13.72,1.43,2.5,16.7,108,3.4,3.67,0.19,2.04,6.8,0.89,2.87,1285,0
12.37,0.94,1.36,10.6,88,1.98,0.57,0.28,0.42,1.95,1.05,1.82,520,1
12.33,1.1,2.28,16,101,2.05,1.09,0.63,0.41,3.27,1.25,1.67,680,1
12.64,1.36,2.02,16.8,100,2.02,1.41,0.53,0.62,5.75,0.98,1.59,450,1
13.67,1.25,1.92,18,94,2.1,1.79,0.32,0.73,3.8,1.23,2.46,630,1
12.37,1.13,2.16,19,87,3.5,3.1,0.19,1.87,4.45,1.22,2.87,420,1
12.17,1.45,2.53,19,104,1.89,1.75,0.45,1.03,2.95,1.45,2.23,355,1
12.37,1.21,2.56,18.1,98,2.42,2.65,0.37,2.08,4.6,1.19,2.3,678,1
13.11,1.01,1.7,15,78,2.98,3.18,0.26,2.28,5.3,1.12,3.18,502,1
12.37,1.17,1.92,19.6,78,2.11,2,0.27,1.04,4.68,1.12,3.48,510,1
13.34,0.94,2.36,17,110,2.53,1.3,0.55,0.42,3.17,1.02,1.93,750,1
12.21,1.19,1.75,16.8,151,1.85,1.28,0.14,2.5,2.85,1.28,3.07,718,1
12.29,1.61,2.21,20.4,103,1.1,1.02,0.37,1.46,3.05,0.906,1.82,870,1
13.86,1.51,2.67,25,86,2.95,2.86,0.21,1.87,3.38,1.36,3.16,410,1
13.49,1.66,2.24,24,87,1.88,1.84,0.27,1.03,3.74,0.98,2.78,472,1
12.99,1.67,2.6,30,139,3.3,2.89,0.21,1.96,3.35,1.31,3.5,985,1
11.96,1.09,2.3,21,101,3.38,2.14,0.13,1.65,3.21,0.99,3.13,886,1
11.66,1.88,1.92,16,97,1.61,1.57,0.34,1.15,3.8,1.23,2.14,428,1
13.03,0.9,1.71,16,86,1.95,2.03,0.24,1.46,4.6,1.19,2.48,392,1
11.84,2.89,2.23,18,112,1.72,1.32,0.43,0.95,2.65,0.96,2.52,500,1
12.33,0.99,1.95,14.8,136,1.9,1.85,0.35,2.76,3.4,1.06,2.31,750,1
12.7,3.87,2.4,23,101,2.83,2.55,0.43,1.95,2.57,1.19,3.13,463,1
12,0.92,2,19,86,2.42,2.26,0.3,1.43,2.5,1.38,3.12,278,1
12.72,1.81,2.2,18.8,86,2.2,2.53,0.26,1.77,3.9,1.16,3.14,714,1
12.08,1.13,2.51,24,78,2,1.58,0.4,1.4,2.2,1.31,2.72,630,1
13.05,3.86,2.32,22.5,85,1.65,1.59,0.61,1.62,4.8,0.84,2.01,515,1
11.84,0.89,2.58,18,94,2.2,2.21,0.22,2.35,3.05,0.79,3.08,520,1
12.67,0.98,2.24,18,99,2.2,1.94,0.3,1.46,2.62,1.23,3.16,450,1
12.16,1.61,2.31,22.8,90,1.78,1.69,0.43,1.56,2.45,1.33,2.26,495,1
11.65,1.67,2.62,26,88,1.92,1.61,0.4,1.34,2.6,1.36,3.21,562,1
11.64,2.06,2.46,21.6,84,1.95,1.69,0.48,1.35,2.8,1,2.75,680,1
12.08,1.33,2.3,23.6,70,2.2,1.59,0.42,1.38,1.74,1.07,3.21,625,1
12.08,1.83,2.32,18.5,81,1.6,1.5,0.52,1.64,2.4,1.08,2.27,480,1
12,1.51,2.42,22,86,1.45,1.25,0.5,1.63,3.6,1.05,2.65,450,1
12.69,1.53,2.26,20.7,80,1.38,1.46,0.58,1.62,3.05,0.96,2.06,495,1
12.29,2.83,2.22,18,88,2.45,2.25,0.25,1.99,2.15,1.15,3.3,290,1
11.62,1.99,2.28,18,98,3.02,2.26,0.17,1.35,3.25,1.16,2.96,345,1
12.47,1.52,2.2,19,162,2.5,2.27,0.32,3.28,2.6,1.16,2.63,937,1
11.81,2.12,2.74,21.5,134,1.6,0.99,0.14,1.56,2.5,0.95,2.26,625,1
12.29,1.41,1.98,16,85,2.55,2.5,0.29,1.77,2.9,1.23,2.74,428,1
12.37,1.07,2.1,18.5,88,3.52,3.75,0.24,1.95,4.5,1.04,2.77,660,1
12.29,3.17,2.21,18,88,2.85,2.99,0.45,2.81,2.3,1.42,2.83,406,1
12.08,2.08,1.7,17.5,97,2.23,2.17,0.26,1.4,3.3,1.27,2.96,710,1
12.6,1.34,1.9,18.5,88,1.45,1.36,0.29,1.35,2.45,1.04,2.77,562,1
12.34,2.45,2.46,21,98,2.56,2.11,0.34,1.31,2.8,0.8,3.38,438,1
11.82,1.72,1.88,19.5,86,2.5,1.64,0.37,1.42,2.06,0.94,2.44,415,1
12.51,1.73,1.98,20.5,85,2.2,1.92,0.32,1.48,2.94,1.04,3.57,672,1
12.42,2.55,2.27,22,90,1.68,1.84,0.66,1.42,2.7,0.86,3.3,315,1
12.25,1.73,2.12,19,80,1.65,2.03,0.37,1.63,3.4,1,3.17,510,1
12.72,1.75,2.28,22.5,84,1.38,1.76,0.48,1.63,3.3,0.88,2.42,488,1
12.22,1.29,1.94,19,92,2.36,2.04,0.39,2.08,2.7,0.86,3.02,312,1
11.61,1.35,2.7,20,94,2.74,2.92,0.29,2.49,2.65,0.96,3.26,680,1
11.46,3.74,1.82,19.5,107,3.18,2.58,0.24,3.58,2.9,0.75,2.81,562,1
12.52,2.43,2.17,21,88,2.55,2.27,0.26,1.22,2,0.9,2.78,325,1
11.76,2.68,2.92,20,103,1.75,2.03,0.6,1.05,3.8,1.23,2.5,607,1
11.41,0.74,2.5,21,88,2.48,2.01,0.42,1.44,3.08,1.1,2.31,434,1
12.08,1.39,2.5,22.5,84,2.56,2.29,0.43,1.04,2.9,0.93,3.19,385,1
11.03,1.51,2.2,21.5,85,2.46,2.17,0.52,2.01,1.9,1.71,2.87,407,1
11.82,1.47,1.99,20.8,86,1.98,1.6,0.3,1.53,1.95,0.95,3.33,495,1
12.42,1.61,2.19,22.5,108,2,2.09,0.34,1.61,2.06,1.06,2.96,345,1
12.77,3.43,1.98,16,80,1.63,1.25,0.43,0.83,3.4,0.7,2.12,372,1
12,3.43,2,19,87,2,1.64,0.37,1.87,1.28,0.93,3.05,564,1
11.45,2.4,2.42,20,96,2.9,2.79,0.32,1.83,3.25,0.8,3.39,625,1
11.56,2.05,3.23,28.5,119,3.18,5.08,0.47,1.87,6,0.93,3.69,465,1
12.42,4.43,2.73,26.5,102,2.2,2.13,0.43,1.71,2.08,0.92,3.12,365,1
13.05,5.8,2.13,21.5,86,2.62,2.65,0.3,2.01,2.6,0.73,3.1,380,1
11.87,4.31,2.39,21,82,2.86,3.03,0.21,2.91,2.8,0.75,3.64,380,1
12.07,2.16,2.17,21,85,2.6,2.65,0.37,1.35,2.76,0.86,3.28,378,1
12.43,1.53,2.29,21.5,86,2.74,3.15,0.39,1.77,3.94,0.69,2.84,352,1
11.79,2.13,2.78,28.5,92,2.13,2.24,0.58,1.76,3,0.97,2.44,466,1
12.37,1.63,2.3,24.5,88,2.22,2.45,0.4,1.9,2.12,0.89,2.78,342,1
12.04,4.3,2.38,22,80,2.1,1.75,0.42,1.35,2.6,0.79,2.57,580,1
12.86,1.35,2.32,18,122,1.51,1.25,0.21,0.94,4.1,0.76,1.29,630,2
12.88,2.99,2.4,20,104,1.3,1.22,0.24,0.83,5.4,0.74,1.42,530,2
12.81,2.31,2.4,24,98,1.15,1.09,0.27,0.83,5.7,0.66,1.36,560,2
12.7,3.55,2.36,21.5,106,1.7,1.2,0.17,0.84,5,0.78,1.29,600,2
12.51,1.24,2.25,17.5,85,2,0.58,0.6,1.25,5.45,0.75,1.51,650,2
12.6,2.46,2.2,18.5,94,1.62,0.66,0.63,0.94,7.1,0.73,1.58,695,2
12.25,4.72,2.54,21,89,1.38,0.47,0.53,0.8,3.85,0.75,1.27,720,2
12.53,5.51,2.64,25,96,1.79,0.6,0.63,1.1,5,0.82,1.69,515,2
13.49,3.59,2.19,19.5,88,1.62,0.48,0.58,0.88,5.7,0.81,1.82,580,2
12.84,2.96,2.61,24,101,2.32,0.6,0.53,0.81,4.92,0.89,2.15,590,2
12.93,2.81,2.7,21,96,1.54,0.5,0.53,0.75,4.6,0.77,2.31,600,2
13.36,2.56,2.35,20,89,1.4,0.5,0.37,0.64,5.6,0.7,2.47,780,2
13.52,3.17,2.72,23.5,97,1.55,0.52,0.5,0.55,4.35,0.89,2.06,520,2
13.62,4.95,2.35,20,92,2,0.8,0.47,1.02,4.4,0.91,2.05,550,2
12.25,3.88,2.2,18.5,112,1.38,0.78,0.29,1.14,8.21,0.65,2,855,2
13.16,3.57,2.15,21,102,1.5,0.55,0.43,1.3,4,0.6,1.68,830,2
13.88,5.04,2.23,20,80,0.98,0.34,0.4,0.68,4.9,0.58,1.33,415,2
12.87,4.61,2.48,21.5,86,1.7,0.65,0.47,0.86,7.65,0.54,1.86,625,2
13.32,3.24,2.38,21.5,92,1.93,0.76,0.45,1.25,8.42,0.55,1.62,650,2
13.08,3.9,2.36,21.5,113,1.41,1.39,0.34,1.14,9.4,0.57,1.33,550,2
13.5,3.12,2.62,24,123,1.4,1.57,0.22,1.25,8.6,0.59,1.3,500,2
12.79,2.67,2.48,22,112,1.48,1.36,0.24,1.26,10.8,0.48,1.47,480,2
13.11,1.9,2.75,25.5,116,2.2,1.28,0.26,1.56,7.1,0.61,1.33,425,2
13.23,3.3,2.28,18.5,98,1.8,0.83,0.61,1.87,10.52,0.56,1.51,675,2
12.58,1.29,2.1,20,103,1.48,0.58,0.53,1.4,7.6,0.58,1.55,640,2
13.17,5.19,2.32,22,93,1.74,0.63,0.61,1.55,7.9,0.6,1.48,725,2
13.84,4.12,2.38,19.5,89,1.8,0.83,0.48,1.56,9.01,0.57,1.64,480,2
12.45,3.03,2.64,27,97,1.9,0.58,0.63,1.14,7.5,0.67,1.73,880,2
14.34,1.68,2.7,25,98,2.8,1.31,0.53,2.7,13,0.57,1.96,660,2
13.48,1.67,2.64,22.5,89,2.6,1.1,0.52,2.29,11.75,0.57,1.78,620,2
12.36,3.83,2.38,21,88,2.3,0.92,0.5,1.04,7.65,0.56,1.58,520,2
13.69,3.26,2.54,20,107,1.83,0.56,0.5,0.8,5.88,0.96,1.82,680,2
12.85,3.27,2.58,22,106,1.65,0.6,0.6,0.96,5.58,0.87,2.11,570,2
12.96,3.45,2.35,18.5,106,1.39,0.7,0.4,0.94,5.28,0.68,1.75,675,2
13.78,2.76,2.3,22,90,1.35,0.68,0.41,1.03,9.58,0.7,1.68,615,2
13.73,4.36,2.26,22.5,88,1.28,0.47,0.52,1.15,6.62,0.78,1.75,520,2
13.45,3.7,2.6,23,111,1.7,0.92,0.43,1.46,10.68,0.85,1.56,695,2
12.82,3.37,2.3,19.5,88,1.48,0.66,0.4,0.97,10.26,0.72,1.75,685,2
13.58,2.58,2.69,24.5,105,1.55,0.84,0.39,1.54,8.66,0.74,1.8,750,2
13.4,4.6,2.86,25,112,1.98,0.96,0.27,1.11,8.5,0.67,1.92,630,2
12.2,3.03,2.32,19,96,1.25,0.49,0.4,0.73,5.5,0.66,1.83,510,2
12.77,2.39,2.28,19.5,86,1.39,0.51,0.48,0.64,9.899999,0.57,1.63,470,2
14.16,2.51,2.48,20,91,1.68,0.7,0.44,1.24,9.7,0.62,1.71,660,2
13.71,5.65,2.45,20.5,95,1.68,0.61,0.52,1.06,7.7,0.64,1.74,740,2
13.4,3.91,2.48,23,102,1.8,0.75,0.43,1.41,7.3,0.7,1.56,750,2
13.27,4.28,2.26,20,120,1.59,0.69,0.43,1.35,10.2,0.59,1.56,835,2
13.17,2.59,2.37,20,120,1.65,0.68,0.53,1.46,9.3,0.6,1.62,840,2
14.13,4.1,2.74,24.5,96,2.05,0.76,0.56,1.35,9.2,0.61,1.6,560,2
1 178,13,class_0,class_1,class_2
2 14.23,1.71,2.43,15.6,127,2.8,3.06,0.28,2.29,5.64,1.04,3.92,1065,0
3 13.2,1.78,2.14,11.2,100,2.65,2.76,0.26,1.28,4.38,1.05,3.4,1050,0
4 13.16,2.36,2.67,18.6,101,2.8,3.24,0.3,2.81,5.68,1.03,3.17,1185,0
5 14.37,1.95,2.5,16.8,113,3.85,3.49,0.24,2.18,7.8,0.86,3.45,1480,0
6 13.24,2.59,2.87,21,118,2.8,2.69,0.39,1.82,4.32,1.04,2.93,735,0
7 14.2,1.76,2.45,15.2,112,3.27,3.39,0.34,1.97,6.75,1.05,2.85,1450,0
8 14.39,1.87,2.45,14.6,96,2.5,2.52,0.3,1.98,5.25,1.02,3.58,1290,0
9 14.06,2.15,2.61,17.6,121,2.6,2.51,0.31,1.25,5.05,1.06,3.58,1295,0
10 14.83,1.64,2.17,14,97,2.8,2.98,0.29,1.98,5.2,1.08,2.85,1045,0
11 13.86,1.35,2.27,16,98,2.98,3.15,0.22,1.85,7.22,1.01,3.55,1045,0
12 14.1,2.16,2.3,18,105,2.95,3.32,0.22,2.38,5.75,1.25,3.17,1510,0
13 14.12,1.48,2.32,16.8,95,2.2,2.43,0.26,1.57,5,1.17,2.82,1280,0
14 13.75,1.73,2.41,16,89,2.6,2.76,0.29,1.81,5.6,1.15,2.9,1320,0
15 14.75,1.73,2.39,11.4,91,3.1,3.69,0.43,2.81,5.4,1.25,2.73,1150,0
16 14.38,1.87,2.38,12,102,3.3,3.64,0.29,2.96,7.5,1.2,3,1547,0
17 13.63,1.81,2.7,17.2,112,2.85,2.91,0.3,1.46,7.3,1.28,2.88,1310,0
18 14.3,1.92,2.72,20,120,2.8,3.14,0.33,1.97,6.2,1.07,2.65,1280,0
19 13.83,1.57,2.62,20,115,2.95,3.4,0.4,1.72,6.6,1.13,2.57,1130,0
20 14.19,1.59,2.48,16.5,108,3.3,3.93,0.32,1.86,8.7,1.23,2.82,1680,0
21 13.64,3.1,2.56,15.2,116,2.7,3.03,0.17,1.66,5.1,0.96,3.36,845,0
22 14.06,1.63,2.28,16,126,3,3.17,0.24,2.1,5.65,1.09,3.71,780,0
23 12.93,3.8,2.65,18.6,102,2.41,2.41,0.25,1.98,4.5,1.03,3.52,770,0
24 13.71,1.86,2.36,16.6,101,2.61,2.88,0.27,1.69,3.8,1.11,4,1035,0
25 12.85,1.6,2.52,17.8,95,2.48,2.37,0.26,1.46,3.93,1.09,3.63,1015,0
26 13.5,1.81,2.61,20,96,2.53,2.61,0.28,1.66,3.52,1.12,3.82,845,0
27 13.05,2.05,3.22,25,124,2.63,2.68,0.47,1.92,3.58,1.13,3.2,830,0
28 13.39,1.77,2.62,16.1,93,2.85,2.94,0.34,1.45,4.8,0.92,3.22,1195,0
29 13.3,1.72,2.14,17,94,2.4,2.19,0.27,1.35,3.95,1.02,2.77,1285,0
30 13.87,1.9,2.8,19.4,107,2.95,2.97,0.37,1.76,4.5,1.25,3.4,915,0
31 14.02,1.68,2.21,16,96,2.65,2.33,0.26,1.98,4.7,1.04,3.59,1035,0
32 13.73,1.5,2.7,22.5,101,3,3.25,0.29,2.38,5.7,1.19,2.71,1285,0
33 13.58,1.66,2.36,19.1,106,2.86,3.19,0.22,1.95,6.9,1.09,2.88,1515,0
34 13.68,1.83,2.36,17.2,104,2.42,2.69,0.42,1.97,3.84,1.23,2.87,990,0
35 13.76,1.53,2.7,19.5,132,2.95,2.74,0.5,1.35,5.4,1.25,3,1235,0
36 13.51,1.8,2.65,19,110,2.35,2.53,0.29,1.54,4.2,1.1,2.87,1095,0
37 13.48,1.81,2.41,20.5,100,2.7,2.98,0.26,1.86,5.1,1.04,3.47,920,0
38 13.28,1.64,2.84,15.5,110,2.6,2.68,0.34,1.36,4.6,1.09,2.78,880,0
39 13.05,1.65,2.55,18,98,2.45,2.43,0.29,1.44,4.25,1.12,2.51,1105,0
40 13.07,1.5,2.1,15.5,98,2.4,2.64,0.28,1.37,3.7,1.18,2.69,1020,0
41 14.22,3.99,2.51,13.2,128,3,3.04,0.2,2.08,5.1,0.89,3.53,760,0
42 13.56,1.71,2.31,16.2,117,3.15,3.29,0.34,2.34,6.13,0.95,3.38,795,0
43 13.41,3.84,2.12,18.8,90,2.45,2.68,0.27,1.48,4.28,0.91,3,1035,0
44 13.88,1.89,2.59,15,101,3.25,3.56,0.17,1.7,5.43,0.88,3.56,1095,0
45 13.24,3.98,2.29,17.5,103,2.64,2.63,0.32,1.66,4.36,0.82,3,680,0
46 13.05,1.77,2.1,17,107,3,3,0.28,2.03,5.04,0.88,3.35,885,0
47 14.21,4.04,2.44,18.9,111,2.85,2.65,0.3,1.25,5.24,0.87,3.33,1080,0
48 14.38,3.59,2.28,16,102,3.25,3.17,0.27,2.19,4.9,1.04,3.44,1065,0
49 13.9,1.68,2.12,16,101,3.1,3.39,0.21,2.14,6.1,0.91,3.33,985,0
50 14.1,2.02,2.4,18.8,103,2.75,2.92,0.32,2.38,6.2,1.07,2.75,1060,0
51 13.94,1.73,2.27,17.4,108,2.88,3.54,0.32,2.08,8.9,1.12,3.1,1260,0
52 13.05,1.73,2.04,12.4,92,2.72,3.27,0.17,2.91,7.2,1.12,2.91,1150,0
53 13.83,1.65,2.6,17.2,94,2.45,2.99,0.22,2.29,5.6,1.24,3.37,1265,0
54 13.82,1.75,2.42,14,111,3.88,3.74,0.32,1.87,7.05,1.01,3.26,1190,0
55 13.77,1.9,2.68,17.1,115,3,2.79,0.39,1.68,6.3,1.13,2.93,1375,0
56 13.74,1.67,2.25,16.4,118,2.6,2.9,0.21,1.62,5.85,0.92,3.2,1060,0
57 13.56,1.73,2.46,20.5,116,2.96,2.78,0.2,2.45,6.25,0.98,3.03,1120,0
58 14.22,1.7,2.3,16.3,118,3.2,3,0.26,2.03,6.38,0.94,3.31,970,0
59 13.29,1.97,2.68,16.8,102,3,3.23,0.31,1.66,6,1.07,2.84,1270,0
60 13.72,1.43,2.5,16.7,108,3.4,3.67,0.19,2.04,6.8,0.89,2.87,1285,0
61 12.37,0.94,1.36,10.6,88,1.98,0.57,0.28,0.42,1.95,1.05,1.82,520,1
62 12.33,1.1,2.28,16,101,2.05,1.09,0.63,0.41,3.27,1.25,1.67,680,1
63 12.64,1.36,2.02,16.8,100,2.02,1.41,0.53,0.62,5.75,0.98,1.59,450,1
64 13.67,1.25,1.92,18,94,2.1,1.79,0.32,0.73,3.8,1.23,2.46,630,1
65 12.37,1.13,2.16,19,87,3.5,3.1,0.19,1.87,4.45,1.22,2.87,420,1
66 12.17,1.45,2.53,19,104,1.89,1.75,0.45,1.03,2.95,1.45,2.23,355,1
67 12.37,1.21,2.56,18.1,98,2.42,2.65,0.37,2.08,4.6,1.19,2.3,678,1
68 13.11,1.01,1.7,15,78,2.98,3.18,0.26,2.28,5.3,1.12,3.18,502,1
69 12.37,1.17,1.92,19.6,78,2.11,2,0.27,1.04,4.68,1.12,3.48,510,1
70 13.34,0.94,2.36,17,110,2.53,1.3,0.55,0.42,3.17,1.02,1.93,750,1
71 12.21,1.19,1.75,16.8,151,1.85,1.28,0.14,2.5,2.85,1.28,3.07,718,1
72 12.29,1.61,2.21,20.4,103,1.1,1.02,0.37,1.46,3.05,0.906,1.82,870,1
73 13.86,1.51,2.67,25,86,2.95,2.86,0.21,1.87,3.38,1.36,3.16,410,1
74 13.49,1.66,2.24,24,87,1.88,1.84,0.27,1.03,3.74,0.98,2.78,472,1
75 12.99,1.67,2.6,30,139,3.3,2.89,0.21,1.96,3.35,1.31,3.5,985,1
76 11.96,1.09,2.3,21,101,3.38,2.14,0.13,1.65,3.21,0.99,3.13,886,1
77 11.66,1.88,1.92,16,97,1.61,1.57,0.34,1.15,3.8,1.23,2.14,428,1
78 13.03,0.9,1.71,16,86,1.95,2.03,0.24,1.46,4.6,1.19,2.48,392,1
79 11.84,2.89,2.23,18,112,1.72,1.32,0.43,0.95,2.65,0.96,2.52,500,1
80 12.33,0.99,1.95,14.8,136,1.9,1.85,0.35,2.76,3.4,1.06,2.31,750,1
81 12.7,3.87,2.4,23,101,2.83,2.55,0.43,1.95,2.57,1.19,3.13,463,1
82 12,0.92,2,19,86,2.42,2.26,0.3,1.43,2.5,1.38,3.12,278,1
83 12.72,1.81,2.2,18.8,86,2.2,2.53,0.26,1.77,3.9,1.16,3.14,714,1
84 12.08,1.13,2.51,24,78,2,1.58,0.4,1.4,2.2,1.31,2.72,630,1
85 13.05,3.86,2.32,22.5,85,1.65,1.59,0.61,1.62,4.8,0.84,2.01,515,1
86 11.84,0.89,2.58,18,94,2.2,2.21,0.22,2.35,3.05,0.79,3.08,520,1
87 12.67,0.98,2.24,18,99,2.2,1.94,0.3,1.46,2.62,1.23,3.16,450,1
88 12.16,1.61,2.31,22.8,90,1.78,1.69,0.43,1.56,2.45,1.33,2.26,495,1
89 11.65,1.67,2.62,26,88,1.92,1.61,0.4,1.34,2.6,1.36,3.21,562,1
90 11.64,2.06,2.46,21.6,84,1.95,1.69,0.48,1.35,2.8,1,2.75,680,1
91 12.08,1.33,2.3,23.6,70,2.2,1.59,0.42,1.38,1.74,1.07,3.21,625,1
92 12.08,1.83,2.32,18.5,81,1.6,1.5,0.52,1.64,2.4,1.08,2.27,480,1
93 12,1.51,2.42,22,86,1.45,1.25,0.5,1.63,3.6,1.05,2.65,450,1
94 12.69,1.53,2.26,20.7,80,1.38,1.46,0.58,1.62,3.05,0.96,2.06,495,1
95 12.29,2.83,2.22,18,88,2.45,2.25,0.25,1.99,2.15,1.15,3.3,290,1
96 11.62,1.99,2.28,18,98,3.02,2.26,0.17,1.35,3.25,1.16,2.96,345,1
97 12.47,1.52,2.2,19,162,2.5,2.27,0.32,3.28,2.6,1.16,2.63,937,1
98 11.81,2.12,2.74,21.5,134,1.6,0.99,0.14,1.56,2.5,0.95,2.26,625,1
99 12.29,1.41,1.98,16,85,2.55,2.5,0.29,1.77,2.9,1.23,2.74,428,1
100 12.37,1.07,2.1,18.5,88,3.52,3.75,0.24,1.95,4.5,1.04,2.77,660,1
101 12.29,3.17,2.21,18,88,2.85,2.99,0.45,2.81,2.3,1.42,2.83,406,1
102 12.08,2.08,1.7,17.5,97,2.23,2.17,0.26,1.4,3.3,1.27,2.96,710,1
103 12.6,1.34,1.9,18.5,88,1.45,1.36,0.29,1.35,2.45,1.04,2.77,562,1
104 12.34,2.45,2.46,21,98,2.56,2.11,0.34,1.31,2.8,0.8,3.38,438,1
105 11.82,1.72,1.88,19.5,86,2.5,1.64,0.37,1.42,2.06,0.94,2.44,415,1
106 12.51,1.73,1.98,20.5,85,2.2,1.92,0.32,1.48,2.94,1.04,3.57,672,1
107 12.42,2.55,2.27,22,90,1.68,1.84,0.66,1.42,2.7,0.86,3.3,315,1
108 12.25,1.73,2.12,19,80,1.65,2.03,0.37,1.63,3.4,1,3.17,510,1
109 12.72,1.75,2.28,22.5,84,1.38,1.76,0.48,1.63,3.3,0.88,2.42,488,1
110 12.22,1.29,1.94,19,92,2.36,2.04,0.39,2.08,2.7,0.86,3.02,312,1
111 11.61,1.35,2.7,20,94,2.74,2.92,0.29,2.49,2.65,0.96,3.26,680,1
112 11.46,3.74,1.82,19.5,107,3.18,2.58,0.24,3.58,2.9,0.75,2.81,562,1
113 12.52,2.43,2.17,21,88,2.55,2.27,0.26,1.22,2,0.9,2.78,325,1
114 11.76,2.68,2.92,20,103,1.75,2.03,0.6,1.05,3.8,1.23,2.5,607,1
115 11.41,0.74,2.5,21,88,2.48,2.01,0.42,1.44,3.08,1.1,2.31,434,1
116 12.08,1.39,2.5,22.5,84,2.56,2.29,0.43,1.04,2.9,0.93,3.19,385,1
117 11.03,1.51,2.2,21.5,85,2.46,2.17,0.52,2.01,1.9,1.71,2.87,407,1
118 11.82,1.47,1.99,20.8,86,1.98,1.6,0.3,1.53,1.95,0.95,3.33,495,1
119 12.42,1.61,2.19,22.5,108,2,2.09,0.34,1.61,2.06,1.06,2.96,345,1
120 12.77,3.43,1.98,16,80,1.63,1.25,0.43,0.83,3.4,0.7,2.12,372,1
121 12,3.43,2,19,87,2,1.64,0.37,1.87,1.28,0.93,3.05,564,1
122 11.45,2.4,2.42,20,96,2.9,2.79,0.32,1.83,3.25,0.8,3.39,625,1
123 11.56,2.05,3.23,28.5,119,3.18,5.08,0.47,1.87,6,0.93,3.69,465,1
124 12.42,4.43,2.73,26.5,102,2.2,2.13,0.43,1.71,2.08,0.92,3.12,365,1
125 13.05,5.8,2.13,21.5,86,2.62,2.65,0.3,2.01,2.6,0.73,3.1,380,1
126 11.87,4.31,2.39,21,82,2.86,3.03,0.21,2.91,2.8,0.75,3.64,380,1
127 12.07,2.16,2.17,21,85,2.6,2.65,0.37,1.35,2.76,0.86,3.28,378,1
128 12.43,1.53,2.29,21.5,86,2.74,3.15,0.39,1.77,3.94,0.69,2.84,352,1
129 11.79,2.13,2.78,28.5,92,2.13,2.24,0.58,1.76,3,0.97,2.44,466,1
130 12.37,1.63,2.3,24.5,88,2.22,2.45,0.4,1.9,2.12,0.89,2.78,342,1
131 12.04,4.3,2.38,22,80,2.1,1.75,0.42,1.35,2.6,0.79,2.57,580,1
132 12.86,1.35,2.32,18,122,1.51,1.25,0.21,0.94,4.1,0.76,1.29,630,2
133 12.88,2.99,2.4,20,104,1.3,1.22,0.24,0.83,5.4,0.74,1.42,530,2
134 12.81,2.31,2.4,24,98,1.15,1.09,0.27,0.83,5.7,0.66,1.36,560,2
135 12.7,3.55,2.36,21.5,106,1.7,1.2,0.17,0.84,5,0.78,1.29,600,2
136 12.51,1.24,2.25,17.5,85,2,0.58,0.6,1.25,5.45,0.75,1.51,650,2
137 12.6,2.46,2.2,18.5,94,1.62,0.66,0.63,0.94,7.1,0.73,1.58,695,2
138 12.25,4.72,2.54,21,89,1.38,0.47,0.53,0.8,3.85,0.75,1.27,720,2
139 12.53,5.51,2.64,25,96,1.79,0.6,0.63,1.1,5,0.82,1.69,515,2
140 13.49,3.59,2.19,19.5,88,1.62,0.48,0.58,0.88,5.7,0.81,1.82,580,2
141 12.84,2.96,2.61,24,101,2.32,0.6,0.53,0.81,4.92,0.89,2.15,590,2
142 12.93,2.81,2.7,21,96,1.54,0.5,0.53,0.75,4.6,0.77,2.31,600,2
143 13.36,2.56,2.35,20,89,1.4,0.5,0.37,0.64,5.6,0.7,2.47,780,2
144 13.52,3.17,2.72,23.5,97,1.55,0.52,0.5,0.55,4.35,0.89,2.06,520,2
145 13.62,4.95,2.35,20,92,2,0.8,0.47,1.02,4.4,0.91,2.05,550,2
146 12.25,3.88,2.2,18.5,112,1.38,0.78,0.29,1.14,8.21,0.65,2,855,2
147 13.16,3.57,2.15,21,102,1.5,0.55,0.43,1.3,4,0.6,1.68,830,2
148 13.88,5.04,2.23,20,80,0.98,0.34,0.4,0.68,4.9,0.58,1.33,415,2
149 12.87,4.61,2.48,21.5,86,1.7,0.65,0.47,0.86,7.65,0.54,1.86,625,2
150 13.32,3.24,2.38,21.5,92,1.93,0.76,0.45,1.25,8.42,0.55,1.62,650,2
151 13.08,3.9,2.36,21.5,113,1.41,1.39,0.34,1.14,9.4,0.57,1.33,550,2
152 13.5,3.12,2.62,24,123,1.4,1.57,0.22,1.25,8.6,0.59,1.3,500,2
153 12.79,2.67,2.48,22,112,1.48,1.36,0.24,1.26,10.8,0.48,1.47,480,2
154 13.11,1.9,2.75,25.5,116,2.2,1.28,0.26,1.56,7.1,0.61,1.33,425,2
155 13.23,3.3,2.28,18.5,98,1.8,0.83,0.61,1.87,10.52,0.56,1.51,675,2
156 12.58,1.29,2.1,20,103,1.48,0.58,0.53,1.4,7.6,0.58,1.55,640,2
157 13.17,5.19,2.32,22,93,1.74,0.63,0.61,1.55,7.9,0.6,1.48,725,2
158 13.84,4.12,2.38,19.5,89,1.8,0.83,0.48,1.56,9.01,0.57,1.64,480,2
159 12.45,3.03,2.64,27,97,1.9,0.58,0.63,1.14,7.5,0.67,1.73,880,2
160 14.34,1.68,2.7,25,98,2.8,1.31,0.53,2.7,13,0.57,1.96,660,2
161 13.48,1.67,2.64,22.5,89,2.6,1.1,0.52,2.29,11.75,0.57,1.78,620,2
162 12.36,3.83,2.38,21,88,2.3,0.92,0.5,1.04,7.65,0.56,1.58,520,2
163 13.69,3.26,2.54,20,107,1.83,0.56,0.5,0.8,5.88,0.96,1.82,680,2
164 12.85,3.27,2.58,22,106,1.65,0.6,0.6,0.96,5.58,0.87,2.11,570,2
165 12.96,3.45,2.35,18.5,106,1.39,0.7,0.4,0.94,5.28,0.68,1.75,675,2
166 13.78,2.76,2.3,22,90,1.35,0.68,0.41,1.03,9.58,0.7,1.68,615,2
167 13.73,4.36,2.26,22.5,88,1.28,0.47,0.52,1.15,6.62,0.78,1.75,520,2
168 13.45,3.7,2.6,23,111,1.7,0.92,0.43,1.46,10.68,0.85,1.56,695,2
169 12.82,3.37,2.3,19.5,88,1.48,0.66,0.4,0.97,10.26,0.72,1.75,685,2
170 13.58,2.58,2.69,24.5,105,1.55,0.84,0.39,1.54,8.66,0.74,1.8,750,2
171 13.4,4.6,2.86,25,112,1.98,0.96,0.27,1.11,8.5,0.67,1.92,630,2
172 12.2,3.03,2.32,19,96,1.25,0.49,0.4,0.73,5.5,0.66,1.83,510,2
173 12.77,2.39,2.28,19.5,86,1.39,0.51,0.48,0.64,9.899999,0.57,1.63,470,2
174 14.16,2.51,2.48,20,91,1.68,0.7,0.44,1.24,9.7,0.62,1.71,660,2
175 13.71,5.65,2.45,20.5,95,1.68,0.61,0.52,1.06,7.7,0.64,1.74,740,2
176 13.4,3.91,2.48,23,102,1.8,0.75,0.43,1.41,7.3,0.7,1.56,750,2
177 13.27,4.28,2.26,20,120,1.59,0.69,0.43,1.35,10.2,0.59,1.56,835,2
178 13.17,2.59,2.37,20,120,1.65,0.68,0.53,1.46,9.3,0.6,1.62,840,2
179 14.13,4.1,2.74,24.5,96,2.05,0.76,0.56,1.35,9.2,0.61,1.6,560,2
@@ -1,118 +1,118 @@
.. _breast_cancer_dataset:
Breast cancer Wisconsin (diagnostic) dataset
--------------------------------------------
**Data Set Characteristics:**
:Number of Instances: 569
:Number of Attributes: 30 numeric, predictive attributes and the class
:Attribute Information:
- radius (mean of distances from center to points on the perimeter)
- texture (standard deviation of gray-scale values)
- perimeter
- area
- smoothness (local variation in radius lengths)
- compactness (perimeter^2 / area - 1.0)
- concavity (severity of concave portions of the contour)
- concave points (number of concave portions of the contour)
- symmetry
- fractal dimension ("coastline approximation" - 1)
The mean, standard error, and "worst" or largest (mean of the three
worst/largest values) of these features were computed for each image,
resulting in 30 features. For instance, field 0 is Mean Radius, field
10 is Radius SE, field 20 is Worst Radius.
- class:
- WDBC-Malignant
- WDBC-Benign
:Summary Statistics:
===================================== ====== ======
Min Max
===================================== ====== ======
radius (mean): 6.981 28.11
texture (mean): 9.71 39.28
perimeter (mean): 43.79 188.5
area (mean): 143.5 2501.0
smoothness (mean): 0.053 0.163
compactness (mean): 0.019 0.345
concavity (mean): 0.0 0.427
concave points (mean): 0.0 0.201
symmetry (mean): 0.106 0.304
fractal dimension (mean): 0.05 0.097
radius (standard error): 0.112 2.873
texture (standard error): 0.36 4.885
perimeter (standard error): 0.757 21.98
area (standard error): 6.802 542.2
smoothness (standard error): 0.002 0.031
compactness (standard error): 0.002 0.135
concavity (standard error): 0.0 0.396
concave points (standard error): 0.0 0.053
symmetry (standard error): 0.008 0.079
fractal dimension (standard error): 0.001 0.03
radius (worst): 7.93 36.04
texture (worst): 12.02 49.54
perimeter (worst): 50.41 251.2
area (worst): 185.2 4254.0
smoothness (worst): 0.071 0.223
compactness (worst): 0.027 1.058
concavity (worst): 0.0 1.252
concave points (worst): 0.0 0.291
symmetry (worst): 0.156 0.664
fractal dimension (worst): 0.055 0.208
===================================== ====== ======
:Missing Attribute Values: None
:Class Distribution: 212 - Malignant, 357 - Benign
:Creator: Dr. William H. Wolberg, W. Nick Street, Olvi L. Mangasarian
:Donor: Nick Street
:Date: November, 1995
This is a copy of UCI ML Breast Cancer Wisconsin (Diagnostic) datasets.
https://goo.gl/U2Uwz2
Features are computed from a digitized image of a fine needle
aspirate (FNA) of a breast mass. They describe
characteristics of the cell nuclei present in the image.
Separating plane described above was obtained using
Multisurface Method-Tree (MSM-T) [K. P. Bennett, "Decision Tree
Construction Via Linear Programming." Proceedings of the 4th
Midwest Artificial Intelligence and Cognitive Science Society,
pp. 97-101, 1992], a classification method which uses linear
programming to construct a decision tree. Relevant features
were selected using an exhaustive search in the space of 1-4
features and 1-3 separating planes.
The actual linear program used to obtain the separating plane
in the 3-dimensional space is that described in:
[K. P. Bennett and O. L. Mangasarian: "Robust Linear
Programming Discrimination of Two Linearly Inseparable Sets",
Optimization Methods and Software 1, 1992, 23-34].
This database is also available through the UW CS ftp server:
ftp ftp.cs.wisc.edu
cd math-prog/cpo-dataset/machine-learn/WDBC/
.. dropdown:: References
- W.N. Street, W.H. Wolberg and O.L. Mangasarian. Nuclear feature extraction
for breast tumor diagnosis. IS&T/SPIE 1993 International Symposium on
Electronic Imaging: Science and Technology, volume 1905, pages 861-870,
San Jose, CA, 1993.
- O.L. Mangasarian, W.N. Street and W.H. Wolberg. Breast cancer diagnosis and
prognosis via linear programming. Operations Research, 43(4), pages 570-577,
July-August 1995.
- W.H. Wolberg, W.N. Street, and O.L. Mangasarian. Machine learning techniques
to diagnose breast cancer from fine-needle aspirates. Cancer Letters 77 (1994)
163-171.
.. _breast_cancer_dataset:
Breast cancer Wisconsin (diagnostic) dataset
--------------------------------------------
**Data Set Characteristics:**
:Number of Instances: 569
:Number of Attributes: 30 numeric, predictive attributes and the class
:Attribute Information:
- radius (mean of distances from center to points on the perimeter)
- texture (standard deviation of gray-scale values)
- perimeter
- area
- smoothness (local variation in radius lengths)
- compactness (perimeter^2 / area - 1.0)
- concavity (severity of concave portions of the contour)
- concave points (number of concave portions of the contour)
- symmetry
- fractal dimension ("coastline approximation" - 1)
The mean, standard error, and "worst" or largest (mean of the three
worst/largest values) of these features were computed for each image,
resulting in 30 features. For instance, field 0 is Mean Radius, field
10 is Radius SE, field 20 is Worst Radius.
- class:
- WDBC-Malignant
- WDBC-Benign
:Summary Statistics:
===================================== ====== ======
Min Max
===================================== ====== ======
radius (mean): 6.981 28.11
texture (mean): 9.71 39.28
perimeter (mean): 43.79 188.5
area (mean): 143.5 2501.0
smoothness (mean): 0.053 0.163
compactness (mean): 0.019 0.345
concavity (mean): 0.0 0.427
concave points (mean): 0.0 0.201
symmetry (mean): 0.106 0.304
fractal dimension (mean): 0.05 0.097
radius (standard error): 0.112 2.873
texture (standard error): 0.36 4.885
perimeter (standard error): 0.757 21.98
area (standard error): 6.802 542.2
smoothness (standard error): 0.002 0.031
compactness (standard error): 0.002 0.135
concavity (standard error): 0.0 0.396
concave points (standard error): 0.0 0.053
symmetry (standard error): 0.008 0.079
fractal dimension (standard error): 0.001 0.03
radius (worst): 7.93 36.04
texture (worst): 12.02 49.54
perimeter (worst): 50.41 251.2
area (worst): 185.2 4254.0
smoothness (worst): 0.071 0.223
compactness (worst): 0.027 1.058
concavity (worst): 0.0 1.252
concave points (worst): 0.0 0.291
symmetry (worst): 0.156 0.664
fractal dimension (worst): 0.055 0.208
===================================== ====== ======
:Missing Attribute Values: None
:Class Distribution: 212 - Malignant, 357 - Benign
:Creator: Dr. William H. Wolberg, W. Nick Street, Olvi L. Mangasarian
:Donor: Nick Street
:Date: November, 1995
This is a copy of UCI ML Breast Cancer Wisconsin (Diagnostic) datasets.
https://goo.gl/U2Uwz2
Features are computed from a digitized image of a fine needle
aspirate (FNA) of a breast mass. They describe
characteristics of the cell nuclei present in the image.
Separating plane described above was obtained using
Multisurface Method-Tree (MSM-T) [K. P. Bennett, "Decision Tree
Construction Via Linear Programming." Proceedings of the 4th
Midwest Artificial Intelligence and Cognitive Science Society,
pp. 97-101, 1992], a classification method which uses linear
programming to construct a decision tree. Relevant features
were selected using an exhaustive search in the space of 1-4
features and 1-3 separating planes.
The actual linear program used to obtain the separating plane
in the 3-dimensional space is that described in:
[K. P. Bennett and O. L. Mangasarian: "Robust Linear
Programming Discrimination of Two Linearly Inseparable Sets",
Optimization Methods and Software 1, 1992, 23-34].
This database is also available through the UW CS ftp server:
ftp ftp.cs.wisc.edu
cd math-prog/cpo-dataset/machine-learn/WDBC/
.. dropdown:: References
- W.N. Street, W.H. Wolberg and O.L. Mangasarian. Nuclear feature extraction
for breast tumor diagnosis. IS&T/SPIE 1993 International Symposium on
Electronic Imaging: Science and Technology, volume 1905, pages 861-870,
San Jose, CA, 1993.
- O.L. Mangasarian, W.N. Street and W.H. Wolberg. Breast cancer diagnosis and
prognosis via linear programming. Operations Research, 43(4), pages 570-577,
July-August 1995.
- W.H. Wolberg, W.N. Street, and O.L. Mangasarian. Machine learning techniques
to diagnose breast cancer from fine-needle aspirates. Cancer Letters 77 (1994)
163-171.
@@ -1,46 +1,46 @@
.. _california_housing_dataset:
California Housing dataset
--------------------------
**Data Set Characteristics:**
:Number of Instances: 20640
:Number of Attributes: 8 numeric, predictive attributes and the target
:Attribute Information:
- MedInc median income in block group
- HouseAge median house age in block group
- AveRooms average number of rooms per household
- AveBedrms average number of bedrooms per household
- Population block group population
- AveOccup average number of household members
- Latitude block group latitude
- Longitude block group longitude
:Missing Attribute Values: None
This dataset was obtained from the StatLib repository.
https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html
The target variable is the median house value for California districts,
expressed in hundreds of thousands of dollars ($100,000).
This dataset was derived from the 1990 U.S. census, using one row per census
block group. A block group is the smallest geographical unit for which the U.S.
Census Bureau publishes sample data (a block group typically has a population
of 600 to 3,000 people).
A household is a group of people residing within a home. Since the average
number of rooms and bedrooms in this dataset are provided per household, these
columns may take surprisingly large values for block groups with few households
and many empty houses, such as vacation resorts.
It can be downloaded/loaded using the
:func:`sklearn.datasets.fetch_california_housing` function.
.. rubric:: References
- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,
Statistics and Probability Letters, 33:291-297, 1997.
.. _california_housing_dataset:
California Housing dataset
--------------------------
**Data Set Characteristics:**
:Number of Instances: 20640
:Number of Attributes: 8 numeric, predictive attributes and the target
:Attribute Information:
- MedInc median income in block group
- HouseAge median house age in block group
- AveRooms average number of rooms per household
- AveBedrms average number of bedrooms per household
- Population block group population
- AveOccup average number of household members
- Latitude block group latitude
- Longitude block group longitude
:Missing Attribute Values: None
This dataset was obtained from the StatLib repository.
https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html
The target variable is the median house value for California districts,
expressed in hundreds of thousands of dollars ($100,000).
This dataset was derived from the 1990 U.S. census, using one row per census
block group. A block group is the smallest geographical unit for which the U.S.
Census Bureau publishes sample data (a block group typically has a population
of 600 to 3,000 people).
A household is a group of people residing within a home. Since the average
number of rooms and bedrooms in this dataset are provided per household, these
columns may take surprisingly large values for block groups with few households
and many empty houses, such as vacation resorts.
It can be downloaded/loaded using the
:func:`sklearn.datasets.fetch_california_housing` function.
.. rubric:: References
- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,
Statistics and Probability Letters, 33:291-297, 1997.
@@ -1,30 +1,30 @@
.. _covtype_dataset:
Forest covertypes
-----------------
The samples in this dataset correspond to 30×30m patches of forest in the US,
collected for the task of predicting each patch's cover type,
i.e. the dominant species of tree.
There are seven covertypes, making this a multiclass classification problem.
Each sample has 54 features, described on the
`dataset's homepage <https://archive.ics.uci.edu/ml/datasets/Covertype>`__.
Some of the features are boolean indicators,
while others are discrete or continuous measurements.
**Data Set Characteristics:**
================= ============
Classes 7
Samples total 581012
Dimensionality 54
Features int
================= ============
:func:`sklearn.datasets.fetch_covtype` will load the covertype dataset;
it returns a dictionary-like 'Bunch' object
with the feature matrix in the ``data`` member
and the target values in ``target``. If optional argument 'as_frame' is
set to 'True', it will return ``data`` and ``target`` as pandas
data frame, and there will be an additional member ``frame`` as well.
The dataset will be downloaded from the web if necessary.
.. _covtype_dataset:
Forest covertypes
-----------------
The samples in this dataset correspond to 30×30m patches of forest in the US,
collected for the task of predicting each patch's cover type,
i.e. the dominant species of tree.
There are seven covertypes, making this a multiclass classification problem.
Each sample has 54 features, described on the
`dataset's homepage <https://archive.ics.uci.edu/ml/datasets/Covertype>`__.
Some of the features are boolean indicators,
while others are discrete or continuous measurements.
**Data Set Characteristics:**
================= ============
Classes 7
Samples total 581012
Dimensionality 54
Features int
================= ============
:func:`sklearn.datasets.fetch_covtype` will load the covertype dataset;
it returns a dictionary-like 'Bunch' object
with the feature matrix in the ``data`` member
and the target values in ``target``. If optional argument 'as_frame' is
set to 'True', it will return ``data`` and ``target`` as pandas
data frame, and there will be an additional member ``frame`` as well.
The dataset will be downloaded from the web if necessary.
@@ -1,38 +1,38 @@
.. _diabetes_dataset:
Diabetes dataset
----------------
Ten baseline variables, age, sex, body mass index, average blood
pressure, and six blood serum measurements were obtained for each of n =
442 diabetes patients, as well as the response of interest, a
quantitative measure of disease progression one year after baseline.
**Data Set Characteristics:**
:Number of Instances: 442
:Number of Attributes: First 10 columns are numeric predictive values
:Target: Column 11 is a quantitative measure of disease progression one year after baseline
:Attribute Information:
- age age in years
- sex
- bmi body mass index
- bp average blood pressure
- s1 tc, total serum cholesterol
- s2 ldl, low-density lipoproteins
- s3 hdl, high-density lipoproteins
- s4 tch, total cholesterol / HDL
- s5 ltg, possibly log of serum triglycerides level
- s6 glu, blood sugar level
Note: Each of these 10 feature variables have been mean centered and scaled by the standard deviation times the square root of `n_samples` (i.e. the sum of squares of each column totals 1).
Source URL:
https://www4.stat.ncsu.edu/~boos/var.select/diabetes.html
For more information see:
Bradley Efron, Trevor Hastie, Iain Johnstone and Robert Tibshirani (2004) "Least Angle Regression," Annals of Statistics (with discussion), 407-499.
(https://web.stanford.edu/~hastie/Papers/LARS/LeastAngle_2002.pdf)
.. _diabetes_dataset:
Diabetes dataset
----------------
Ten baseline variables, age, sex, body mass index, average blood
pressure, and six blood serum measurements were obtained for each of n =
442 diabetes patients, as well as the response of interest, a
quantitative measure of disease progression one year after baseline.
**Data Set Characteristics:**
:Number of Instances: 442
:Number of Attributes: First 10 columns are numeric predictive values
:Target: Column 11 is a quantitative measure of disease progression one year after baseline
:Attribute Information:
- age age in years
- sex
- bmi body mass index
- bp average blood pressure
- s1 tc, total serum cholesterol
- s2 ldl, low-density lipoproteins
- s3 hdl, high-density lipoproteins
- s4 tch, total cholesterol / HDL
- s5 ltg, possibly log of serum triglycerides level
- s6 glu, blood sugar level
Note: Each of these 10 feature variables have been mean centered and scaled by the standard deviation times the square root of `n_samples` (i.e. the sum of squares of each column totals 1).
Source URL:
https://www4.stat.ncsu.edu/~boos/var.select/diabetes.html
For more information see:
Bradley Efron, Trevor Hastie, Iain Johnstone and Robert Tibshirani (2004) "Least Angle Regression," Annals of Statistics (with discussion), 407-499.
(https://web.stanford.edu/~hastie/Papers/LARS/LeastAngle_2002.pdf)
@@ -1,46 +1,46 @@
.. _digits_dataset:
Optical recognition of handwritten digits dataset
--------------------------------------------------
**Data Set Characteristics:**
:Number of Instances: 1797
:Number of Attributes: 64
:Attribute Information: 8x8 image of integer pixels in the range 0..16.
:Missing Attribute Values: None
:Creator: E. Alpaydin (alpaydin '@' boun.edu.tr)
:Date: July; 1998
This is a copy of the test set of the UCI ML hand-written digits datasets
https://archive.ics.uci.edu/ml/datasets/Optical+Recognition+of+Handwritten+Digits
The data set contains images of hand-written digits: 10 classes where
each class refers to a digit.
Preprocessing programs made available by NIST were used to extract
normalized bitmaps of handwritten digits from a preprinted form. From a
total of 43 people, 30 contributed to the training set and different 13
to the test set. 32x32 bitmaps are divided into nonoverlapping blocks of
4x4 and the number of on pixels are counted in each block. This generates
an input matrix of 8x8 where each element is an integer in the range
0..16. This reduces dimensionality and gives invariance to small
distortions.
For info on NIST preprocessing routines, see M. D. Garris, J. L. Blue, G.
T. Candela, D. L. Dimmick, J. Geist, P. J. Grother, S. A. Janet, and C.
L. Wilson, NIST Form-Based Handprint Recognition System, NISTIR 5469,
1994.
.. dropdown:: References
- C. Kaynak (1995) Methods of Combining Multiple Classifiers and Their
Applications to Handwritten Digit Recognition, MSc Thesis, Institute of
Graduate Studies in Science and Engineering, Bogazici University.
- E. Alpaydin, C. Kaynak (1998) Cascading Classifiers, Kybernetika.
- Ken Tang and Ponnuthurai N. Suganthan and Xi Yao and A. Kai Qin.
Linear dimensionalityreduction using relevance weighted LDA. School of
Electrical and Electronic Engineering Nanyang Technological University.
2005.
- Claudio Gentile. A New Approximate Maximal Margin Classification
Algorithm. NIPS. 2000.
.. _digits_dataset:
Optical recognition of handwritten digits dataset
--------------------------------------------------
**Data Set Characteristics:**
:Number of Instances: 1797
:Number of Attributes: 64
:Attribute Information: 8x8 image of integer pixels in the range 0..16.
:Missing Attribute Values: None
:Creator: E. Alpaydin (alpaydin '@' boun.edu.tr)
:Date: July; 1998
This is a copy of the test set of the UCI ML hand-written digits datasets
https://archive.ics.uci.edu/ml/datasets/Optical+Recognition+of+Handwritten+Digits
The data set contains images of hand-written digits: 10 classes where
each class refers to a digit.
Preprocessing programs made available by NIST were used to extract
normalized bitmaps of handwritten digits from a preprinted form. From a
total of 43 people, 30 contributed to the training set and different 13
to the test set. 32x32 bitmaps are divided into nonoverlapping blocks of
4x4 and the number of on pixels are counted in each block. This generates
an input matrix of 8x8 where each element is an integer in the range
0..16. This reduces dimensionality and gives invariance to small
distortions.
For info on NIST preprocessing routines, see M. D. Garris, J. L. Blue, G.
T. Candela, D. L. Dimmick, J. Geist, P. J. Grother, S. A. Janet, and C.
L. Wilson, NIST Form-Based Handprint Recognition System, NISTIR 5469,
1994.
.. dropdown:: References
- C. Kaynak (1995) Methods of Combining Multiple Classifiers and Their
Applications to Handwritten Digit Recognition, MSc Thesis, Institute of
Graduate Studies in Science and Engineering, Bogazici University.
- E. Alpaydin, C. Kaynak (1998) Cascading Classifiers, Kybernetika.
- Ken Tang and Ponnuthurai N. Suganthan and Xi Yao and A. Kai Qin.
Linear dimensionalityreduction using relevance weighted LDA. School of
Electrical and Electronic Engineering Nanyang Technological University.
2005.
- Claudio Gentile. A New Approximate Maximal Margin Classification
Algorithm. NIPS. 2000.
@@ -1,63 +1,63 @@
.. _iris_dataset:
Iris plants dataset
--------------------
**Data Set Characteristics:**
:Number of Instances: 150 (50 in each of three classes)
:Number of Attributes: 4 numeric, predictive attributes and the class
:Attribute Information:
- sepal length in cm
- sepal width in cm
- petal length in cm
- petal width in cm
- class:
- Iris-Setosa
- Iris-Versicolour
- Iris-Virginica
:Summary Statistics:
============== ==== ==== ======= ===== ====================
Min Max Mean SD Class Correlation
============== ==== ==== ======= ===== ====================
sepal length: 4.3 7.9 5.84 0.83 0.7826
sepal width: 2.0 4.4 3.05 0.43 -0.4194
petal length: 1.0 6.9 3.76 1.76 0.9490 (high!)
petal width: 0.1 2.5 1.20 0.76 0.9565 (high!)
============== ==== ==== ======= ===== ====================
:Missing Attribute Values: None
:Class Distribution: 33.3% for each of 3 classes.
:Creator: R.A. Fisher
:Donor: Michael Marshall (MARSHALL%PLU@io.arc.nasa.gov)
:Date: July, 1988
The famous Iris database, first used by Sir R.A. Fisher. The dataset is taken
from Fisher's paper. Note that it's the same as in R, but not as in the UCI
Machine Learning Repository, which has two wrong data points.
This is perhaps the best known database to be found in the
pattern recognition literature. Fisher's paper is a classic in the field and
is referenced frequently to this day. (See Duda & Hart, for example.) The
data set contains 3 classes of 50 instances each, where each class refers to a
type of iris plant. One class is linearly separable from the other 2; the
latter are NOT linearly separable from each other.
.. dropdown:: References
- Fisher, R.A. "The use of multiple measurements in taxonomic problems"
Annual Eugenics, 7, Part II, 179-188 (1936); also in "Contributions to
Mathematical Statistics" (John Wiley, NY, 1950).
- Duda, R.O., & Hart, P.E. (1973) Pattern Classification and Scene Analysis.
(Q327.D83) John Wiley & Sons. ISBN 0-471-22361-1. See page 218.
- Dasarathy, B.V. (1980) "Nosing Around the Neighborhood: A New System
Structure and Classification Rule for Recognition in Partially Exposed
Environments". IEEE Transactions on Pattern Analysis and Machine
Intelligence, Vol. PAMI-2, No. 1, 67-71.
- Gates, G.W. (1972) "The Reduced Nearest Neighbor Rule". IEEE Transactions
on Information Theory, May 1972, 431-433.
- See also: 1988 MLC Proceedings, 54-64. Cheeseman et al"s AUTOCLASS II
conceptual clustering system finds 3 classes in the data.
- Many, many more ...
.. _iris_dataset:
Iris plants dataset
--------------------
**Data Set Characteristics:**
:Number of Instances: 150 (50 in each of three classes)
:Number of Attributes: 4 numeric, predictive attributes and the class
:Attribute Information:
- sepal length in cm
- sepal width in cm
- petal length in cm
- petal width in cm
- class:
- Iris-Setosa
- Iris-Versicolour
- Iris-Virginica
:Summary Statistics:
============== ==== ==== ======= ===== ====================
Min Max Mean SD Class Correlation
============== ==== ==== ======= ===== ====================
sepal length: 4.3 7.9 5.84 0.83 0.7826
sepal width: 2.0 4.4 3.05 0.43 -0.4194
petal length: 1.0 6.9 3.76 1.76 0.9490 (high!)
petal width: 0.1 2.5 1.20 0.76 0.9565 (high!)
============== ==== ==== ======= ===== ====================
:Missing Attribute Values: None
:Class Distribution: 33.3% for each of 3 classes.
:Creator: R.A. Fisher
:Donor: Michael Marshall (MARSHALL%PLU@io.arc.nasa.gov)
:Date: July, 1988
The famous Iris database, first used by Sir R.A. Fisher. The dataset is taken
from Fisher's paper. Note that it's the same as in R, but not as in the UCI
Machine Learning Repository, which has two wrong data points.
This is perhaps the best known database to be found in the
pattern recognition literature. Fisher's paper is a classic in the field and
is referenced frequently to this day. (See Duda & Hart, for example.) The
data set contains 3 classes of 50 instances each, where each class refers to a
type of iris plant. One class is linearly separable from the other 2; the
latter are NOT linearly separable from each other.
.. dropdown:: References
- Fisher, R.A. "The use of multiple measurements in taxonomic problems"
Annual Eugenics, 7, Part II, 179-188 (1936); also in "Contributions to
Mathematical Statistics" (John Wiley, NY, 1950).
- Duda, R.O., & Hart, P.E. (1973) Pattern Classification and Scene Analysis.
(Q327.D83) John Wiley & Sons. ISBN 0-471-22361-1. See page 218.
- Dasarathy, B.V. (1980) "Nosing Around the Neighborhood: A New System
Structure and Classification Rule for Recognition in Partially Exposed
Environments". IEEE Transactions on Pattern Analysis and Machine
Intelligence, Vol. PAMI-2, No. 1, 67-71.
- Gates, G.W. (1972) "The Reduced Nearest Neighbor Rule". IEEE Transactions
on Information Theory, May 1972, 431-433.
- See also: 1988 MLC Proceedings, 54-64. Cheeseman et al"s AUTOCLASS II
conceptual clustering system finds 3 classes in the data.
- Many, many more ...
@@ -1,94 +1,94 @@
.. _kddcup99_dataset:
Kddcup 99 dataset
-----------------
The KDD Cup '99 dataset was created by processing the tcpdump portions
of the 1998 DARPA Intrusion Detection System (IDS) Evaluation dataset,
created by MIT Lincoln Lab [2]_. The artificial data (described on the `dataset's
homepage <https://kdd.ics.uci.edu/databases/kddcup99/kddcup99.html>`_) was
generated using a closed network and hand-injected attacks to produce a
large number of different types of attack with normal activity in the
background. As the initial goal was to produce a large training set for
supervised learning algorithms, there is a large proportion (80.1%) of
abnormal data which is unrealistic in real world, and inappropriate for
unsupervised anomaly detection which aims at detecting 'abnormal' data, i.e.:
* qualitatively different from normal data
* in large minority among the observations.
We thus transform the KDD Data set into two different data sets: SA and SF.
* SA is obtained by simply selecting all the normal data, and a small
proportion of abnormal data to gives an anomaly proportion of 1%.
* SF is obtained as in [3]_
by simply picking up the data whose attribute logged_in is positive, thus
focusing on the intrusion attack, which gives a proportion of 0.3% of
attack.
* http and smtp are two subsets of SF corresponding with third feature
equal to 'http' (resp. to 'smtp').
General KDD structure:
================ ==========================================
Samples total 4898431
Dimensionality 41
Features discrete (int) or continuous (float)
Targets str, 'normal.' or name of the anomaly type
================ ==========================================
SA structure:
================ ==========================================
Samples total 976158
Dimensionality 41
Features discrete (int) or continuous (float)
Targets str, 'normal.' or name of the anomaly type
================ ==========================================
SF structure:
================ ==========================================
Samples total 699691
Dimensionality 4
Features discrete (int) or continuous (float)
Targets str, 'normal.' or name of the anomaly type
================ ==========================================
http structure:
================ ==========================================
Samples total 619052
Dimensionality 3
Features discrete (int) or continuous (float)
Targets str, 'normal.' or name of the anomaly type
================ ==========================================
smtp structure:
================ ==========================================
Samples total 95373
Dimensionality 3
Features discrete (int) or continuous (float)
Targets str, 'normal.' or name of the anomaly type
================ ==========================================
:func:`sklearn.datasets.fetch_kddcup99` will load the kddcup99 dataset; it
returns a dictionary-like object with the feature matrix in the ``data`` member
and the target values in ``target``. The "as_frame" optional argument converts
``data`` into a pandas DataFrame and ``target`` into a pandas Series. The
dataset will be downloaded from the web if necessary.
.. rubric:: References
.. [2] Analysis and Results of the 1999 DARPA Off-Line Intrusion
Detection Evaluation, Richard Lippmann, Joshua W. Haines,
David J. Fried, Jonathan Korba, Kumar Das.
.. [3] K. Yamanishi, J.-I. Takeuchi, G. Williams, and P. Milne. Online
unsupervised outlier detection using finite mixtures with
discounting learning algorithms. In Proceedings of the sixth
ACM SIGKDD international conference on Knowledge discovery
and data mining, pages 320-324. ACM Press, 2000.
.. _kddcup99_dataset:
Kddcup 99 dataset
-----------------
The KDD Cup '99 dataset was created by processing the tcpdump portions
of the 1998 DARPA Intrusion Detection System (IDS) Evaluation dataset,
created by MIT Lincoln Lab [2]_. The artificial data (described on the `dataset's
homepage <https://kdd.ics.uci.edu/databases/kddcup99/kddcup99.html>`_) was
generated using a closed network and hand-injected attacks to produce a
large number of different types of attack with normal activity in the
background. As the initial goal was to produce a large training set for
supervised learning algorithms, there is a large proportion (80.1%) of
abnormal data which is unrealistic in real world, and inappropriate for
unsupervised anomaly detection which aims at detecting 'abnormal' data, i.e.:
* qualitatively different from normal data
* in large minority among the observations.
We thus transform the KDD Data set into two different data sets: SA and SF.
* SA is obtained by simply selecting all the normal data, and a small
proportion of abnormal data to gives an anomaly proportion of 1%.
* SF is obtained as in [3]_
by simply picking up the data whose attribute logged_in is positive, thus
focusing on the intrusion attack, which gives a proportion of 0.3% of
attack.
* http and smtp are two subsets of SF corresponding with third feature
equal to 'http' (resp. to 'smtp').
General KDD structure:
================ ==========================================
Samples total 4898431
Dimensionality 41
Features discrete (int) or continuous (float)
Targets str, 'normal.' or name of the anomaly type
================ ==========================================
SA structure:
================ ==========================================
Samples total 976158
Dimensionality 41
Features discrete (int) or continuous (float)
Targets str, 'normal.' or name of the anomaly type
================ ==========================================
SF structure:
================ ==========================================
Samples total 699691
Dimensionality 4
Features discrete (int) or continuous (float)
Targets str, 'normal.' or name of the anomaly type
================ ==========================================
http structure:
================ ==========================================
Samples total 619052
Dimensionality 3
Features discrete (int) or continuous (float)
Targets str, 'normal.' or name of the anomaly type
================ ==========================================
smtp structure:
================ ==========================================
Samples total 95373
Dimensionality 3
Features discrete (int) or continuous (float)
Targets str, 'normal.' or name of the anomaly type
================ ==========================================
:func:`sklearn.datasets.fetch_kddcup99` will load the kddcup99 dataset; it
returns a dictionary-like object with the feature matrix in the ``data`` member
and the target values in ``target``. The "as_frame" optional argument converts
``data`` into a pandas DataFrame and ``target`` into a pandas Series. The
dataset will be downloaded from the web if necessary.
.. rubric:: References
.. [2] Analysis and Results of the 1999 DARPA Off-Line Intrusion
Detection Evaluation, Richard Lippmann, Joshua W. Haines,
David J. Fried, Jonathan Korba, Kumar Das.
.. [3] K. Yamanishi, J.-I. Takeuchi, G. Williams, and P. Milne. Online
unsupervised outlier detection using finite mixtures with
discounting learning algorithms. In Proceedings of the sixth
ACM SIGKDD international conference on Knowledge discovery
and data mining, pages 320-324. ACM Press, 2000.
@@ -1,124 +1,124 @@
.. _labeled_faces_in_the_wild_dataset:
The Labeled Faces in the Wild face recognition dataset
------------------------------------------------------
This dataset is a collection of JPEG pictures of famous people collected
over the internet, and the details are available on the Kaggle website:
https://www.kaggle.com/datasets/jessicali9530/lfw-dataset
Each picture is centered on a single face. The typical task is called
Face Verification: given a pair of two pictures, a binary classifier
must predict whether the two images are from the same person.
An alternative task, Face Recognition or Face Identification is:
given the picture of the face of an unknown person, identify the name
of the person by referring to a gallery of previously seen pictures of
identified persons.
Both Face Verification and Face Recognition are tasks that are typically
performed on the output of a model trained to perform Face Detection. The
most popular model for Face Detection is called Viola-Jones and is
implemented in the OpenCV library. The LFW faces were extracted by this
face detector from various online websites.
**Data Set Characteristics:**
================= =======================
Classes 5749
Samples total 13233
Dimensionality 5828
Features real, between 0 and 255
================= =======================
.. dropdown:: Usage
``scikit-learn`` provides two loaders that will automatically download,
cache, parse the metadata files, decode the jpeg and convert the
interesting slices into memmapped numpy arrays. This dataset size is more
than 200 MB. The first load typically takes more than a couple of minutes
to fully decode the relevant part of the JPEG files into numpy arrays. If
the dataset has been loaded once, the following times the loading times
less than 200ms by using a memmapped version memoized on the disk in the
``~/scikit_learn_data/lfw_home/`` folder using ``joblib``.
The first loader is used for the Face Identification task: a multi-class
classification task (hence supervised learning)::
>>> from sklearn.datasets import fetch_lfw_people
>>> lfw_people = fetch_lfw_people(min_faces_per_person=70, resize=0.4)
>>> for name in lfw_people.target_names:
... print(name)
...
Ariel Sharon
Colin Powell
Donald Rumsfeld
George W Bush
Gerhard Schroeder
Hugo Chavez
Tony Blair
The default slice is a rectangular shape around the face, removing
most of the background::
>>> lfw_people.data.dtype
dtype('float32')
>>> lfw_people.data.shape
(1288, 1850)
>>> lfw_people.images.shape
(1288, 50, 37)
Each of the ``1140`` faces is assigned to a single person id in the ``target``
array::
>>> lfw_people.target.shape
(1288,)
>>> list(lfw_people.target[:10])
[5, 6, 3, 1, 0, 1, 3, 4, 3, 0]
The second loader is typically used for the face verification task: each sample
is a pair of two picture belonging or not to the same person::
>>> from sklearn.datasets import fetch_lfw_pairs
>>> lfw_pairs_train = fetch_lfw_pairs(subset='train')
>>> list(lfw_pairs_train.target_names)
['Different persons', 'Same person']
>>> lfw_pairs_train.pairs.shape
(2200, 2, 62, 47)
>>> lfw_pairs_train.data.shape
(2200, 5828)
>>> lfw_pairs_train.target.shape
(2200,)
Both for the :func:`sklearn.datasets.fetch_lfw_people` and
:func:`sklearn.datasets.fetch_lfw_pairs` function it is
possible to get an additional dimension with the RGB color channels by
passing ``color=True``, in that case the shape will be
``(2200, 2, 62, 47, 3)``.
The :func:`sklearn.datasets.fetch_lfw_pairs` datasets is subdivided into
3 subsets: the development ``train`` set, the development ``test`` set and
an evaluation ``10_folds`` set meant to compute performance metrics using a
10-folds cross validation scheme.
.. rubric:: References
* `Labeled Faces in the Wild: A Database for Studying Face Recognition
in Unconstrained Environments.
<https://people.cs.umass.edu/~elm/papers/lfw.pdf>`_
Gary B. Huang, Manu Ramesh, Tamara Berg, and Erik Learned-Miller.
University of Massachusetts, Amherst, Technical Report 07-49, October, 2007.
.. rubric:: Examples
* :ref:`sphx_glr_auto_examples_applications_plot_face_recognition.py`
.. _labeled_faces_in_the_wild_dataset:
The Labeled Faces in the Wild face recognition dataset
------------------------------------------------------
This dataset is a collection of JPEG pictures of famous people collected
over the internet, and the details are available on the Kaggle website:
https://www.kaggle.com/datasets/jessicali9530/lfw-dataset
Each picture is centered on a single face. The typical task is called
Face Verification: given a pair of two pictures, a binary classifier
must predict whether the two images are from the same person.
An alternative task, Face Recognition or Face Identification is:
given the picture of the face of an unknown person, identify the name
of the person by referring to a gallery of previously seen pictures of
identified persons.
Both Face Verification and Face Recognition are tasks that are typically
performed on the output of a model trained to perform Face Detection. The
most popular model for Face Detection is called Viola-Jones and is
implemented in the OpenCV library. The LFW faces were extracted by this
face detector from various online websites.
**Data Set Characteristics:**
================= =======================
Classes 5749
Samples total 13233
Dimensionality 5828
Features real, between 0 and 255
================= =======================
.. dropdown:: Usage
``scikit-learn`` provides two loaders that will automatically download,
cache, parse the metadata files, decode the jpeg and convert the
interesting slices into memmapped numpy arrays. This dataset size is more
than 200 MB. The first load typically takes more than a couple of minutes
to fully decode the relevant part of the JPEG files into numpy arrays. If
the dataset has been loaded once, the following times the loading times
less than 200ms by using a memmapped version memoized on the disk in the
``~/scikit_learn_data/lfw_home/`` folder using ``joblib``.
The first loader is used for the Face Identification task: a multi-class
classification task (hence supervised learning)::
>>> from sklearn.datasets import fetch_lfw_people
>>> lfw_people = fetch_lfw_people(min_faces_per_person=70, resize=0.4)
>>> for name in lfw_people.target_names:
... print(name)
...
Ariel Sharon
Colin Powell
Donald Rumsfeld
George W Bush
Gerhard Schroeder
Hugo Chavez
Tony Blair
The default slice is a rectangular shape around the face, removing
most of the background::
>>> lfw_people.data.dtype
dtype('float32')
>>> lfw_people.data.shape
(1288, 1850)
>>> lfw_people.images.shape
(1288, 50, 37)
Each of the ``1140`` faces is assigned to a single person id in the ``target``
array::
>>> lfw_people.target.shape
(1288,)
>>> list(lfw_people.target[:10])
[5, 6, 3, 1, 0, 1, 3, 4, 3, 0]
The second loader is typically used for the face verification task: each sample
is a pair of two picture belonging or not to the same person::
>>> from sklearn.datasets import fetch_lfw_pairs
>>> lfw_pairs_train = fetch_lfw_pairs(subset='train')
>>> list(lfw_pairs_train.target_names)
['Different persons', 'Same person']
>>> lfw_pairs_train.pairs.shape
(2200, 2, 62, 47)
>>> lfw_pairs_train.data.shape
(2200, 5828)
>>> lfw_pairs_train.target.shape
(2200,)
Both for the :func:`sklearn.datasets.fetch_lfw_people` and
:func:`sklearn.datasets.fetch_lfw_pairs` function it is
possible to get an additional dimension with the RGB color channels by
passing ``color=True``, in that case the shape will be
``(2200, 2, 62, 47, 3)``.
The :func:`sklearn.datasets.fetch_lfw_pairs` datasets is subdivided into
3 subsets: the development ``train`` set, the development ``test`` set and
an evaluation ``10_folds`` set meant to compute performance metrics using a
10-folds cross validation scheme.
.. rubric:: References
* `Labeled Faces in the Wild: A Database for Studying Face Recognition
in Unconstrained Environments.
<https://people.cs.umass.edu/~elm/papers/lfw.pdf>`_
Gary B. Huang, Manu Ramesh, Tamara Berg, and Erik Learned-Miller.
University of Massachusetts, Amherst, Technical Report 07-49, October, 2007.
.. rubric:: Examples
* :ref:`sphx_glr_auto_examples_applications_plot_face_recognition.py`
@@ -1,24 +1,24 @@
.. _linnerrud_dataset:
Linnerrud dataset
-----------------
**Data Set Characteristics:**
:Number of Instances: 20
:Number of Attributes: 3
:Missing Attribute Values: None
The Linnerud dataset is a multi-output regression dataset. It consists of three
exercise (data) and three physiological (target) variables collected from
twenty middle-aged men in a fitness club:
- *physiological* - CSV containing 20 observations on 3 physiological variables:
Weight, Waist and Pulse.
- *exercise* - CSV containing 20 observations on 3 exercise variables:
Chins, Situps and Jumps.
.. dropdown:: References
* Tenenhaus, M. (1998). La regression PLS: theorie et pratique. Paris:
Editions Technic.
.. _linnerrud_dataset:
Linnerrud dataset
-----------------
**Data Set Characteristics:**
:Number of Instances: 20
:Number of Attributes: 3
:Missing Attribute Values: None
The Linnerud dataset is a multi-output regression dataset. It consists of three
exercise (data) and three physiological (target) variables collected from
twenty middle-aged men in a fitness club:
- *physiological* - CSV containing 20 observations on 3 physiological variables:
Weight, Waist and Pulse.
- *exercise* - CSV containing 20 observations on 3 exercise variables:
Chins, Situps and Jumps.
.. dropdown:: References
* Tenenhaus, M. (1998). La regression PLS: theorie et pratique. Paris:
Editions Technic.
@@ -1,44 +1,44 @@
.. _olivetti_faces_dataset:
The Olivetti faces dataset
--------------------------
`This dataset contains a set of face images`_ taken between April 1992 and
April 1994 at AT&T Laboratories Cambridge. The
:func:`sklearn.datasets.fetch_olivetti_faces` function is the data
fetching / caching function that downloads the data
archive from AT&T.
.. _This dataset contains a set of face images: https://cam-orl.co.uk/facedatabase.html
As described on the original website:
There are ten different images of each of 40 distinct subjects. For some
subjects, the images were taken at different times, varying the lighting,
facial expressions (open / closed eyes, smiling / not smiling) and facial
details (glasses / no glasses). All the images were taken against a dark
homogeneous background with the subjects in an upright, frontal position
(with tolerance for some side movement).
**Data Set Characteristics:**
================= =====================
Classes 40
Samples total 400
Dimensionality 4096
Features real, between 0 and 1
================= =====================
The image is quantized to 256 grey levels and stored as unsigned 8-bit
integers; the loader will convert these to floating point values on the
interval [0, 1], which are easier to work with for many algorithms.
The "target" for this database is an integer from 0 to 39 indicating the
identity of the person pictured; however, with only 10 examples per class, this
relatively small dataset is more interesting from an unsupervised or
semi-supervised perspective.
The original dataset consisted of 92 x 112, while the version available here
consists of 64x64 images.
When using these images, please give credit to AT&T Laboratories Cambridge.
.. _olivetti_faces_dataset:
The Olivetti faces dataset
--------------------------
`This dataset contains a set of face images`_ taken between April 1992 and
April 1994 at AT&T Laboratories Cambridge. The
:func:`sklearn.datasets.fetch_olivetti_faces` function is the data
fetching / caching function that downloads the data
archive from AT&T.
.. _This dataset contains a set of face images: https://cam-orl.co.uk/facedatabase.html
As described on the original website:
There are ten different images of each of 40 distinct subjects. For some
subjects, the images were taken at different times, varying the lighting,
facial expressions (open / closed eyes, smiling / not smiling) and facial
details (glasses / no glasses). All the images were taken against a dark
homogeneous background with the subjects in an upright, frontal position
(with tolerance for some side movement).
**Data Set Characteristics:**
================= =====================
Classes 40
Samples total 400
Dimensionality 4096
Features real, between 0 and 1
================= =====================
The image is quantized to 256 grey levels and stored as unsigned 8-bit
integers; the loader will convert these to floating point values on the
interval [0, 1], which are easier to work with for many algorithms.
The "target" for this database is an integer from 0 to 39 indicating the
identity of the person pictured; however, with only 10 examples per class, this
relatively small dataset is more interesting from an unsupervised or
semi-supervised perspective.
The original dataset consisted of 92 x 112, while the version available here
consists of 64x64 images.
When using these images, please give credit to AT&T Laboratories Cambridge.
@@ -1,72 +1,72 @@
.. _rcv1_dataset:
RCV1 dataset
------------
Reuters Corpus Volume I (RCV1) is an archive of over 800,000 manually
categorized newswire stories made available by Reuters, Ltd. for research
purposes. The dataset is extensively described in [1]_.
**Data Set Characteristics:**
============== =====================
Classes 103
Samples total 804414
Dimensionality 47236
Features real, between 0 and 1
============== =====================
:func:`sklearn.datasets.fetch_rcv1` will load the following
version: RCV1-v2, vectors, full sets, topics multilabels::
>>> from sklearn.datasets import fetch_rcv1
>>> rcv1 = fetch_rcv1()
It returns a dictionary-like object, with the following attributes:
``data``:
The feature matrix is a scipy CSR sparse matrix, with 804414 samples and
47236 features. Non-zero values contains cosine-normalized, log TF-IDF vectors.
A nearly chronological split is proposed in [1]_: The first 23149 samples are
the training set. The last 781265 samples are the testing set. This follows
the official LYRL2004 chronological split. The array has 0.16% of non zero
values::
>>> rcv1.data.shape
(804414, 47236)
``target``:
The target values are stored in a scipy CSR sparse matrix, with 804414 samples
and 103 categories. Each sample has a value of 1 in its categories, and 0 in
others. The array has 3.15% of non zero values::
>>> rcv1.target.shape
(804414, 103)
``sample_id``:
Each sample can be identified by its ID, ranging (with gaps) from 2286
to 810596::
>>> rcv1.sample_id[:3]
array([2286, 2287, 2288], dtype=uint32)
``target_names``:
The target values are the topics of each sample. Each sample belongs to at
least one topic, and to up to 17 topics. There are 103 topics, each
represented by a string. Their corpus frequencies span five orders of
magnitude, from 5 occurrences for 'GMIL', to 381327 for 'CCAT'::
>>> rcv1.target_names[:3].tolist() # doctest: +SKIP
['E11', 'ECAT', 'M11']
The dataset will be downloaded from the `rcv1 homepage`_ if necessary.
The compressed size is about 656 MB.
.. _rcv1 homepage: http://jmlr.csail.mit.edu/papers/volume5/lewis04a/
.. rubric:: References
.. [1] Lewis, D. D., Yang, Y., Rose, T. G., & Li, F. (2004).
RCV1: A new benchmark collection for text categorization research.
The Journal of Machine Learning Research, 5, 361-397.
.. _rcv1_dataset:
RCV1 dataset
------------
Reuters Corpus Volume I (RCV1) is an archive of over 800,000 manually
categorized newswire stories made available by Reuters, Ltd. for research
purposes. The dataset is extensively described in [1]_.
**Data Set Characteristics:**
============== =====================
Classes 103
Samples total 804414
Dimensionality 47236
Features real, between 0 and 1
============== =====================
:func:`sklearn.datasets.fetch_rcv1` will load the following
version: RCV1-v2, vectors, full sets, topics multilabels::
>>> from sklearn.datasets import fetch_rcv1
>>> rcv1 = fetch_rcv1()
It returns a dictionary-like object, with the following attributes:
``data``:
The feature matrix is a scipy CSR sparse matrix, with 804414 samples and
47236 features. Non-zero values contains cosine-normalized, log TF-IDF vectors.
A nearly chronological split is proposed in [1]_: The first 23149 samples are
the training set. The last 781265 samples are the testing set. This follows
the official LYRL2004 chronological split. The array has 0.16% of non zero
values::
>>> rcv1.data.shape
(804414, 47236)
``target``:
The target values are stored in a scipy CSR sparse matrix, with 804414 samples
and 103 categories. Each sample has a value of 1 in its categories, and 0 in
others. The array has 3.15% of non zero values::
>>> rcv1.target.shape
(804414, 103)
``sample_id``:
Each sample can be identified by its ID, ranging (with gaps) from 2286
to 810596::
>>> rcv1.sample_id[:3]
array([2286, 2287, 2288], dtype=uint32)
``target_names``:
The target values are the topics of each sample. Each sample belongs to at
least one topic, and to up to 17 topics. There are 103 topics, each
represented by a string. Their corpus frequencies span five orders of
magnitude, from 5 occurrences for 'GMIL', to 381327 for 'CCAT'::
>>> rcv1.target_names[:3].tolist() # doctest: +SKIP
['E11', 'ECAT', 'M11']
The dataset will be downloaded from the `rcv1 homepage`_ if necessary.
The compressed size is about 656 MB.
.. _rcv1 homepage: http://jmlr.csail.mit.edu/papers/volume5/lewis04a/
.. rubric:: References
.. [1] Lewis, D. D., Yang, Y., Rose, T. G., & Li, F. (2004).
RCV1: A new benchmark collection for text categorization research.
The Journal of Machine Learning Research, 5, 361-397.
@@ -1,40 +1,40 @@
.. _species_distribution_dataset:
Species distribution dataset
----------------------------
This dataset represents the geographic distribution of two species in Central and
South America. The two species are:
- `"Bradypus variegatus" <http://www.iucnredlist.org/details/3038/0>`_ ,
the Brown-throated Sloth.
- `"Microryzomys minutus" <http://www.iucnredlist.org/details/13408/0>`_ ,
also known as the Forest Small Rice Rat, a rodent that lives in Peru,
Colombia, Ecuador, Peru, and Venezuela.
The dataset is not a typical dataset since a :class:`~sklearn.datasets.base.Bunch`
containing the attributes `data` and `target` is not returned. Instead, we have
information allowing to create a "density" map of the different species.
The grid for the map can be built using the attributes `x_left_lower_corner`,
`y_left_lower_corner`, `Nx`, `Ny` and `grid_size`, which respectively correspond
to the x and y coordinates of the lower left corner of the grid, the number of
points along the x- and y-axis and the size of the step on the grid.
The density at each location of the grid is contained in the `coverage` attribute.
Finally, the `train` and `test` attributes contain information regarding the location
of a species at a specific location.
The dataset is provided by Phillips et. al. (2006).
.. rubric:: References
* `"Maximum entropy modeling of species geographic distributions"
<http://rob.schapire.net/papers/ecolmod.pdf>`_ S. J. Phillips,
R. P. Anderson, R. E. Schapire - Ecological Modelling, 190:231-259, 2006.
.. rubric:: Examples
* :ref:`sphx_glr_auto_examples_applications_plot_species_distribution_modeling.py`
.. _species_distribution_dataset:
Species distribution dataset
----------------------------
This dataset represents the geographic distribution of two species in Central and
South America. The two species are:
- `"Bradypus variegatus" <http://www.iucnredlist.org/details/3038/0>`_ ,
the Brown-throated Sloth.
- `"Microryzomys minutus" <http://www.iucnredlist.org/details/13408/0>`_ ,
also known as the Forest Small Rice Rat, a rodent that lives in Peru,
Colombia, Ecuador, Peru, and Venezuela.
The dataset is not a typical dataset since a :class:`~sklearn.datasets.base.Bunch`
containing the attributes `data` and `target` is not returned. Instead, we have
information allowing to create a "density" map of the different species.
The grid for the map can be built using the attributes `x_left_lower_corner`,
`y_left_lower_corner`, `Nx`, `Ny` and `grid_size`, which respectively correspond
to the x and y coordinates of the lower left corner of the grid, the number of
points along the x- and y-axis and the size of the step on the grid.
The density at each location of the grid is contained in the `coverage` attribute.
Finally, the `train` and `test` attributes contain information regarding the location
of a species at a specific location.
The dataset is provided by Phillips et. al. (2006).
.. rubric:: References
* `"Maximum entropy modeling of species geographic distributions"
<http://rob.schapire.net/papers/ecolmod.pdf>`_ S. J. Phillips,
R. P. Anderson, R. E. Schapire - Ecological Modelling, 190:231-259, 2006.
.. rubric:: Examples
* :ref:`sphx_glr_auto_examples_applications_plot_species_distribution_modeling.py`
@@ -1,248 +1,248 @@
.. _20newsgroups_dataset:
The 20 newsgroups text dataset
------------------------------
The 20 newsgroups dataset comprises around 18000 newsgroups posts on
20 topics split in two subsets: one for training (or development)
and the other one for testing (or for performance evaluation). The split
between the train and test set is based upon a messages posted before
and after a specific date.
This module contains two loaders. The first one,
:func:`sklearn.datasets.fetch_20newsgroups`,
returns a list of the raw texts that can be fed to text feature
extractors such as :class:`~sklearn.feature_extraction.text.CountVectorizer`
with custom parameters so as to extract feature vectors.
The second one, :func:`sklearn.datasets.fetch_20newsgroups_vectorized`,
returns ready-to-use features, i.e., it is not necessary to use a feature
extractor.
**Data Set Characteristics:**
================= ==========
Classes 20
Samples total 18846
Dimensionality 1
Features text
================= ==========
.. dropdown:: Usage
The :func:`sklearn.datasets.fetch_20newsgroups` function is a data
fetching / caching functions that downloads the data archive from
the original `20 newsgroups website <http://people.csail.mit.edu/jrennie/20Newsgroups/>`__,
extracts the archive contents
in the ``~/scikit_learn_data/20news_home`` folder and calls the
:func:`sklearn.datasets.load_files` on either the training or
testing set folder, or both of them::
>>> from sklearn.datasets import fetch_20newsgroups
>>> newsgroups_train = fetch_20newsgroups(subset='train')
>>> from pprint import pprint
>>> pprint(list(newsgroups_train.target_names))
['alt.atheism',
'comp.graphics',
'comp.os.ms-windows.misc',
'comp.sys.ibm.pc.hardware',
'comp.sys.mac.hardware',
'comp.windows.x',
'misc.forsale',
'rec.autos',
'rec.motorcycles',
'rec.sport.baseball',
'rec.sport.hockey',
'sci.crypt',
'sci.electronics',
'sci.med',
'sci.space',
'soc.religion.christian',
'talk.politics.guns',
'talk.politics.mideast',
'talk.politics.misc',
'talk.religion.misc']
The real data lies in the ``filenames`` and ``target`` attributes. The target
attribute is the integer index of the category::
>>> newsgroups_train.filenames.shape
(11314,)
>>> newsgroups_train.target.shape
(11314,)
>>> newsgroups_train.target[:10]
array([ 7, 4, 4, 1, 14, 16, 13, 3, 2, 4])
It is possible to load only a sub-selection of the categories by passing the
list of the categories to load to the
:func:`sklearn.datasets.fetch_20newsgroups` function::
>>> cats = ['alt.atheism', 'sci.space']
>>> newsgroups_train = fetch_20newsgroups(subset='train', categories=cats)
>>> list(newsgroups_train.target_names)
['alt.atheism', 'sci.space']
>>> newsgroups_train.filenames.shape
(1073,)
>>> newsgroups_train.target.shape
(1073,)
>>> newsgroups_train.target[:10]
array([0, 1, 1, 1, 0, 1, 1, 0, 0, 0])
.. dropdown:: Converting text to vectors
In order to feed predictive or clustering models with the text data,
one first need to turn the text into vectors of numerical values suitable
for statistical analysis. This can be achieved with the utilities of the
``sklearn.feature_extraction.text`` as demonstrated in the following
example that extract `TF-IDF <https://en.wikipedia.org/wiki/Tf-idf>`__ vectors
of unigram tokens from a subset of 20news::
>>> from sklearn.feature_extraction.text import TfidfVectorizer
>>> categories = ['alt.atheism', 'talk.religion.misc',
... 'comp.graphics', 'sci.space']
>>> newsgroups_train = fetch_20newsgroups(subset='train',
... categories=categories)
>>> vectorizer = TfidfVectorizer()
>>> vectors = vectorizer.fit_transform(newsgroups_train.data)
>>> vectors.shape
(2034, 34118)
The extracted TF-IDF vectors are very sparse, with an average of 159 non-zero
components by sample in a more than 30000-dimensional space
(less than .5% non-zero features)::
>>> vectors.nnz / float(vectors.shape[0])
159.01327...
:func:`sklearn.datasets.fetch_20newsgroups_vectorized` is a function which
returns ready-to-use token counts features instead of file names.
.. dropdown:: Filtering text for more realistic training
It is easy for a classifier to overfit on particular things that appear in the
20 Newsgroups data, such as newsgroup headers. Many classifiers achieve very
high F-scores, but their results would not generalize to other documents that
aren't from this window of time.
For example, let's look at the results of a multinomial Naive Bayes classifier,
which is fast to train and achieves a decent F-score::
>>> from sklearn.naive_bayes import MultinomialNB
>>> from sklearn import metrics
>>> newsgroups_test = fetch_20newsgroups(subset='test',
... categories=categories)
>>> vectors_test = vectorizer.transform(newsgroups_test.data)
>>> clf = MultinomialNB(alpha=.01)
>>> clf.fit(vectors, newsgroups_train.target)
MultinomialNB(alpha=0.01, class_prior=None, fit_prior=True)
>>> pred = clf.predict(vectors_test)
>>> metrics.f1_score(newsgroups_test.target, pred, average='macro')
0.88213...
(The example :ref:`sphx_glr_auto_examples_text_plot_document_classification_20newsgroups.py` shuffles
the training and test data, instead of segmenting by time, and in that case
multinomial Naive Bayes gets a much higher F-score of 0.88. Are you suspicious
yet of what's going on inside this classifier?)
Let's take a look at what the most informative features are:
>>> import numpy as np
>>> def show_top10(classifier, vectorizer, categories):
... feature_names = vectorizer.get_feature_names_out()
... for i, category in enumerate(categories):
... top10 = np.argsort(classifier.coef_[i])[-10:]
... print("%s: %s" % (category, " ".join(feature_names[top10])))
...
>>> show_top10(clf, vectorizer, newsgroups_train.target_names)
alt.atheism: edu it and in you that is of to the
comp.graphics: edu in graphics it is for and of to the
sci.space: edu it that is in and space to of the
talk.religion.misc: not it you in is that and to of the
You can now see many things that these features have overfit to:
- Almost every group is distinguished by whether headers such as
``NNTP-Posting-Host:`` and ``Distribution:`` appear more or less often.
- Another significant feature involves whether the sender is affiliated with
a university, as indicated either by their headers or their signature.
- The word "article" is a significant feature, based on how often people quote
previous posts like this: "In article [article ID], [name] <[e-mail address]>
wrote:"
- Other features match the names and e-mail addresses of particular people who
were posting at the time.
With such an abundance of clues that distinguish newsgroups, the classifiers
barely have to identify topics from text at all, and they all perform at the
same high level.
For this reason, the functions that load 20 Newsgroups data provide a
parameter called **remove**, telling it what kinds of information to strip out
of each file. **remove** should be a tuple containing any subset of
``('headers', 'footers', 'quotes')``, telling it to remove headers, signature
blocks, and quotation blocks respectively.
>>> newsgroups_test = fetch_20newsgroups(subset='test',
... remove=('headers', 'footers', 'quotes'),
... categories=categories)
>>> vectors_test = vectorizer.transform(newsgroups_test.data)
>>> pred = clf.predict(vectors_test)
>>> metrics.f1_score(pred, newsgroups_test.target, average='macro')
0.77310...
This classifier lost over a lot of its F-score, just because we removed
metadata that has little to do with topic classification.
It loses even more if we also strip this metadata from the training data:
>>> newsgroups_train = fetch_20newsgroups(subset='train',
... remove=('headers', 'footers', 'quotes'),
... categories=categories)
>>> vectors = vectorizer.fit_transform(newsgroups_train.data)
>>> clf = MultinomialNB(alpha=.01)
>>> clf.fit(vectors, newsgroups_train.target)
MultinomialNB(alpha=0.01, class_prior=None, fit_prior=True)
>>> vectors_test = vectorizer.transform(newsgroups_test.data)
>>> pred = clf.predict(vectors_test)
>>> metrics.f1_score(newsgroups_test.target, pred, average='macro')
0.76995...
Some other classifiers cope better with this harder version of the task. Try the
:ref:`sphx_glr_auto_examples_model_selection_plot_grid_search_text_feature_extraction.py`
example with and without the `remove` option to compare the results.
.. rubric:: Data Considerations
The Cleveland Indians is a major league baseball team based in Cleveland,
Ohio, USA. In December 2020, it was reported that "After several months of
discussion sparked by the death of George Floyd and a national reckoning over
race and colonialism, the Cleveland Indians have decided to change their
name." Team owner Paul Dolan "did make it clear that the team will not make
its informal nickname -- the Tribe -- its new team name." "It's not going to
be a half-step away from the Indians," Dolan said."We will not have a Native
American-themed name."
https://www.mlb.com/news/cleveland-indians-team-name-change
.. rubric:: Recommendation
- When evaluating text classifiers on the 20 Newsgroups data, you
should strip newsgroup-related metadata. In scikit-learn, you can do this
by setting ``remove=('headers', 'footers', 'quotes')``. The F-score will be
lower because it is more realistic.
- This text dataset contains data which may be inappropriate for certain NLP
applications. An example is listed in the "Data Considerations" section
above. The challenge with using current text datasets in NLP for tasks such
as sentence completion, clustering, and other applications is that text
that is culturally biased and inflammatory will propagate biases. This
should be taken into consideration when using the dataset, reviewing the
output, and the bias should be documented.
.. rubric:: Examples
* :ref:`sphx_glr_auto_examples_model_selection_plot_grid_search_text_feature_extraction.py`
* :ref:`sphx_glr_auto_examples_text_plot_document_classification_20newsgroups.py`
* :ref:`sphx_glr_auto_examples_text_plot_hashing_vs_dict_vectorizer.py`
* :ref:`sphx_glr_auto_examples_text_plot_document_clustering.py`
.. _20newsgroups_dataset:
The 20 newsgroups text dataset
------------------------------
The 20 newsgroups dataset comprises around 18000 newsgroups posts on
20 topics split in two subsets: one for training (or development)
and the other one for testing (or for performance evaluation). The split
between the train and test set is based upon a messages posted before
and after a specific date.
This module contains two loaders. The first one,
:func:`sklearn.datasets.fetch_20newsgroups`,
returns a list of the raw texts that can be fed to text feature
extractors such as :class:`~sklearn.feature_extraction.text.CountVectorizer`
with custom parameters so as to extract feature vectors.
The second one, :func:`sklearn.datasets.fetch_20newsgroups_vectorized`,
returns ready-to-use features, i.e., it is not necessary to use a feature
extractor.
**Data Set Characteristics:**
================= ==========
Classes 20
Samples total 18846
Dimensionality 1
Features text
================= ==========
.. dropdown:: Usage
The :func:`sklearn.datasets.fetch_20newsgroups` function is a data
fetching / caching functions that downloads the data archive from
the original `20 newsgroups website <http://people.csail.mit.edu/jrennie/20Newsgroups/>`__,
extracts the archive contents
in the ``~/scikit_learn_data/20news_home`` folder and calls the
:func:`sklearn.datasets.load_files` on either the training or
testing set folder, or both of them::
>>> from sklearn.datasets import fetch_20newsgroups
>>> newsgroups_train = fetch_20newsgroups(subset='train')
>>> from pprint import pprint
>>> pprint(list(newsgroups_train.target_names))
['alt.atheism',
'comp.graphics',
'comp.os.ms-windows.misc',
'comp.sys.ibm.pc.hardware',
'comp.sys.mac.hardware',
'comp.windows.x',
'misc.forsale',
'rec.autos',
'rec.motorcycles',
'rec.sport.baseball',
'rec.sport.hockey',
'sci.crypt',
'sci.electronics',
'sci.med',
'sci.space',
'soc.religion.christian',
'talk.politics.guns',
'talk.politics.mideast',
'talk.politics.misc',
'talk.religion.misc']
The real data lies in the ``filenames`` and ``target`` attributes. The target
attribute is the integer index of the category::
>>> newsgroups_train.filenames.shape
(11314,)
>>> newsgroups_train.target.shape
(11314,)
>>> newsgroups_train.target[:10]
array([ 7, 4, 4, 1, 14, 16, 13, 3, 2, 4])
It is possible to load only a sub-selection of the categories by passing the
list of the categories to load to the
:func:`sklearn.datasets.fetch_20newsgroups` function::
>>> cats = ['alt.atheism', 'sci.space']
>>> newsgroups_train = fetch_20newsgroups(subset='train', categories=cats)
>>> list(newsgroups_train.target_names)
['alt.atheism', 'sci.space']
>>> newsgroups_train.filenames.shape
(1073,)
>>> newsgroups_train.target.shape
(1073,)
>>> newsgroups_train.target[:10]
array([0, 1, 1, 1, 0, 1, 1, 0, 0, 0])
.. dropdown:: Converting text to vectors
In order to feed predictive or clustering models with the text data,
one first need to turn the text into vectors of numerical values suitable
for statistical analysis. This can be achieved with the utilities of the
``sklearn.feature_extraction.text`` as demonstrated in the following
example that extract `TF-IDF <https://en.wikipedia.org/wiki/Tf-idf>`__ vectors
of unigram tokens from a subset of 20news::
>>> from sklearn.feature_extraction.text import TfidfVectorizer
>>> categories = ['alt.atheism', 'talk.religion.misc',
... 'comp.graphics', 'sci.space']
>>> newsgroups_train = fetch_20newsgroups(subset='train',
... categories=categories)
>>> vectorizer = TfidfVectorizer()
>>> vectors = vectorizer.fit_transform(newsgroups_train.data)
>>> vectors.shape
(2034, 34118)
The extracted TF-IDF vectors are very sparse, with an average of 159 non-zero
components by sample in a more than 30000-dimensional space
(less than .5% non-zero features)::
>>> vectors.nnz / float(vectors.shape[0])
159.01327...
:func:`sklearn.datasets.fetch_20newsgroups_vectorized` is a function which
returns ready-to-use token counts features instead of file names.
.. dropdown:: Filtering text for more realistic training
It is easy for a classifier to overfit on particular things that appear in the
20 Newsgroups data, such as newsgroup headers. Many classifiers achieve very
high F-scores, but their results would not generalize to other documents that
aren't from this window of time.
For example, let's look at the results of a multinomial Naive Bayes classifier,
which is fast to train and achieves a decent F-score::
>>> from sklearn.naive_bayes import MultinomialNB
>>> from sklearn import metrics
>>> newsgroups_test = fetch_20newsgroups(subset='test',
... categories=categories)
>>> vectors_test = vectorizer.transform(newsgroups_test.data)
>>> clf = MultinomialNB(alpha=.01)
>>> clf.fit(vectors, newsgroups_train.target)
MultinomialNB(alpha=0.01, class_prior=None, fit_prior=True)
>>> pred = clf.predict(vectors_test)
>>> metrics.f1_score(newsgroups_test.target, pred, average='macro')
0.88213...
(The example :ref:`sphx_glr_auto_examples_text_plot_document_classification_20newsgroups.py` shuffles
the training and test data, instead of segmenting by time, and in that case
multinomial Naive Bayes gets a much higher F-score of 0.88. Are you suspicious
yet of what's going on inside this classifier?)
Let's take a look at what the most informative features are:
>>> import numpy as np
>>> def show_top10(classifier, vectorizer, categories):
... feature_names = vectorizer.get_feature_names_out()
... for i, category in enumerate(categories):
... top10 = np.argsort(classifier.coef_[i])[-10:]
... print("%s: %s" % (category, " ".join(feature_names[top10])))
...
>>> show_top10(clf, vectorizer, newsgroups_train.target_names)
alt.atheism: edu it and in you that is of to the
comp.graphics: edu in graphics it is for and of to the
sci.space: edu it that is in and space to of the
talk.religion.misc: not it you in is that and to of the
You can now see many things that these features have overfit to:
- Almost every group is distinguished by whether headers such as
``NNTP-Posting-Host:`` and ``Distribution:`` appear more or less often.
- Another significant feature involves whether the sender is affiliated with
a university, as indicated either by their headers or their signature.
- The word "article" is a significant feature, based on how often people quote
previous posts like this: "In article [article ID], [name] <[e-mail address]>
wrote:"
- Other features match the names and e-mail addresses of particular people who
were posting at the time.
With such an abundance of clues that distinguish newsgroups, the classifiers
barely have to identify topics from text at all, and they all perform at the
same high level.
For this reason, the functions that load 20 Newsgroups data provide a
parameter called **remove**, telling it what kinds of information to strip out
of each file. **remove** should be a tuple containing any subset of
``('headers', 'footers', 'quotes')``, telling it to remove headers, signature
blocks, and quotation blocks respectively.
>>> newsgroups_test = fetch_20newsgroups(subset='test',
... remove=('headers', 'footers', 'quotes'),
... categories=categories)
>>> vectors_test = vectorizer.transform(newsgroups_test.data)
>>> pred = clf.predict(vectors_test)
>>> metrics.f1_score(pred, newsgroups_test.target, average='macro')
0.77310...
This classifier lost over a lot of its F-score, just because we removed
metadata that has little to do with topic classification.
It loses even more if we also strip this metadata from the training data:
>>> newsgroups_train = fetch_20newsgroups(subset='train',
... remove=('headers', 'footers', 'quotes'),
... categories=categories)
>>> vectors = vectorizer.fit_transform(newsgroups_train.data)
>>> clf = MultinomialNB(alpha=.01)
>>> clf.fit(vectors, newsgroups_train.target)
MultinomialNB(alpha=0.01, class_prior=None, fit_prior=True)
>>> vectors_test = vectorizer.transform(newsgroups_test.data)
>>> pred = clf.predict(vectors_test)
>>> metrics.f1_score(newsgroups_test.target, pred, average='macro')
0.76995...
Some other classifiers cope better with this harder version of the task. Try the
:ref:`sphx_glr_auto_examples_model_selection_plot_grid_search_text_feature_extraction.py`
example with and without the `remove` option to compare the results.
.. rubric:: Data Considerations
The Cleveland Indians is a major league baseball team based in Cleveland,
Ohio, USA. In December 2020, it was reported that "After several months of
discussion sparked by the death of George Floyd and a national reckoning over
race and colonialism, the Cleveland Indians have decided to change their
name." Team owner Paul Dolan "did make it clear that the team will not make
its informal nickname -- the Tribe -- its new team name." "It's not going to
be a half-step away from the Indians," Dolan said."We will not have a Native
American-themed name."
https://www.mlb.com/news/cleveland-indians-team-name-change
.. rubric:: Recommendation
- When evaluating text classifiers on the 20 Newsgroups data, you
should strip newsgroup-related metadata. In scikit-learn, you can do this
by setting ``remove=('headers', 'footers', 'quotes')``. The F-score will be
lower because it is more realistic.
- This text dataset contains data which may be inappropriate for certain NLP
applications. An example is listed in the "Data Considerations" section
above. The challenge with using current text datasets in NLP for tasks such
as sentence completion, clustering, and other applications is that text
that is culturally biased and inflammatory will propagate biases. This
should be taken into consideration when using the dataset, reviewing the
output, and the bias should be documented.
.. rubric:: Examples
* :ref:`sphx_glr_auto_examples_model_selection_plot_grid_search_text_feature_extraction.py`
* :ref:`sphx_glr_auto_examples_text_plot_document_classification_20newsgroups.py`
* :ref:`sphx_glr_auto_examples_text_plot_hashing_vs_dict_vectorizer.py`
* :ref:`sphx_glr_auto_examples_text_plot_document_clustering.py`
@@ -1,94 +1,94 @@
.. _wine_dataset:
Wine recognition dataset
------------------------
**Data Set Characteristics:**
:Number of Instances: 178
:Number of Attributes: 13 numeric, predictive attributes and the class
:Attribute Information:
- Alcohol
- Malic acid
- Ash
- Alcalinity of ash
- Magnesium
- Total phenols
- Flavanoids
- Nonflavanoid phenols
- Proanthocyanins
- Color intensity
- Hue
- OD280/OD315 of diluted wines
- Proline
- class:
- class_0
- class_1
- class_2
:Summary Statistics:
============================= ==== ===== ======= =====
Min Max Mean SD
============================= ==== ===== ======= =====
Alcohol: 11.0 14.8 13.0 0.8
Malic Acid: 0.74 5.80 2.34 1.12
Ash: 1.36 3.23 2.36 0.27
Alcalinity of Ash: 10.6 30.0 19.5 3.3
Magnesium: 70.0 162.0 99.7 14.3
Total Phenols: 0.98 3.88 2.29 0.63
Flavanoids: 0.34 5.08 2.03 1.00
Nonflavanoid Phenols: 0.13 0.66 0.36 0.12
Proanthocyanins: 0.41 3.58 1.59 0.57
Colour Intensity: 1.3 13.0 5.1 2.3
Hue: 0.48 1.71 0.96 0.23
OD280/OD315 of diluted wines: 1.27 4.00 2.61 0.71
Proline: 278 1680 746 315
============================= ==== ===== ======= =====
:Missing Attribute Values: None
:Class Distribution: class_0 (59), class_1 (71), class_2 (48)
:Creator: R.A. Fisher
:Donor: Michael Marshall (MARSHALL%PLU@io.arc.nasa.gov)
:Date: July, 1988
This is a copy of UCI ML Wine recognition datasets.
https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data
The data is the results of a chemical analysis of wines grown in the same
region in Italy by three different cultivators. There are thirteen different
measurements taken for different constituents found in the three types of
wine.
Original Owners:
Forina, M. et al, PARVUS -
An Extendible Package for Data Exploration, Classification and Correlation.
Institute of Pharmaceutical and Food Analysis and Technologies,
Via Brigata Salerno, 16147 Genoa, Italy.
Citation:
Lichman, M. (2013). UCI Machine Learning Repository
[https://archive.ics.uci.edu/ml]. Irvine, CA: University of California,
School of Information and Computer Science.
.. dropdown:: References
(1) S. Aeberhard, D. Coomans and O. de Vel,
Comparison of Classifiers in High Dimensional Settings,
Tech. Rep. no. 92-02, (1992), Dept. of Computer Science and Dept. of
Mathematics and Statistics, James Cook University of North Queensland.
(Also submitted to Technometrics).
The data was used with many others for comparing various
classifiers. The classes are separable, though only RDA
has achieved 100% correct classification.
(RDA : 100%, QDA 99.4%, LDA 98.9%, 1NN 96.1% (z-transformed data))
(All results using the leave-one-out technique)
(2) S. Aeberhard, D. Coomans and O. de Vel,
"THE CLASSIFICATION PERFORMANCE OF RDA"
Tech. Rep. no. 92-01, (1992), Dept. of Computer Science and Dept. of
Mathematics and Statistics, James Cook University of North Queensland.
(Also submitted to Journal of Chemometrics).
.. _wine_dataset:
Wine recognition dataset
------------------------
**Data Set Characteristics:**
:Number of Instances: 178
:Number of Attributes: 13 numeric, predictive attributes and the class
:Attribute Information:
- Alcohol
- Malic acid
- Ash
- Alcalinity of ash
- Magnesium
- Total phenols
- Flavanoids
- Nonflavanoid phenols
- Proanthocyanins
- Color intensity
- Hue
- OD280/OD315 of diluted wines
- Proline
- class:
- class_0
- class_1
- class_2
:Summary Statistics:
============================= ==== ===== ======= =====
Min Max Mean SD
============================= ==== ===== ======= =====
Alcohol: 11.0 14.8 13.0 0.8
Malic Acid: 0.74 5.80 2.34 1.12
Ash: 1.36 3.23 2.36 0.27
Alcalinity of Ash: 10.6 30.0 19.5 3.3
Magnesium: 70.0 162.0 99.7 14.3
Total Phenols: 0.98 3.88 2.29 0.63
Flavanoids: 0.34 5.08 2.03 1.00
Nonflavanoid Phenols: 0.13 0.66 0.36 0.12
Proanthocyanins: 0.41 3.58 1.59 0.57
Colour Intensity: 1.3 13.0 5.1 2.3
Hue: 0.48 1.71 0.96 0.23
OD280/OD315 of diluted wines: 1.27 4.00 2.61 0.71
Proline: 278 1680 746 315
============================= ==== ===== ======= =====
:Missing Attribute Values: None
:Class Distribution: class_0 (59), class_1 (71), class_2 (48)
:Creator: R.A. Fisher
:Donor: Michael Marshall (MARSHALL%PLU@io.arc.nasa.gov)
:Date: July, 1988
This is a copy of UCI ML Wine recognition datasets.
https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data
The data is the results of a chemical analysis of wines grown in the same
region in Italy by three different cultivators. There are thirteen different
measurements taken for different constituents found in the three types of
wine.
Original Owners:
Forina, M. et al, PARVUS -
An Extendible Package for Data Exploration, Classification and Correlation.
Institute of Pharmaceutical and Food Analysis and Technologies,
Via Brigata Salerno, 16147 Genoa, Italy.
Citation:
Lichman, M. (2013). UCI Machine Learning Repository
[https://archive.ics.uci.edu/ml]. Irvine, CA: University of California,
School of Information and Computer Science.
.. dropdown:: References
(1) S. Aeberhard, D. Coomans and O. de Vel,
Comparison of Classifiers in High Dimensional Settings,
Tech. Rep. no. 92-02, (1992), Dept. of Computer Science and Dept. of
Mathematics and Statistics, James Cook University of North Queensland.
(Also submitted to Technometrics).
The data was used with many others for comparing various
classifiers. The classes are separable, though only RDA
has achieved 100% correct classification.
(RDA : 100%, QDA 99.4%, LDA 98.9%, 1NN 96.1% (z-transformed data))
(All results using the leave-one-out technique)
(2) S. Aeberhard, D. Coomans and O. de Vel,
"THE CLASSIFICATION PERFORMANCE OF RDA"
Tech. Rep. no. 92-01, (1992), Dept. of Computer Science and Dept. of
Mathematics and Statistics, James Cook University of North Queensland.
(Also submitted to Journal of Chemometrics).
@@ -1,18 +1,18 @@
Image: china.jpg
Released under a creative commons license. [1]
Attribution: Some rights reserved by danielbuechele [2]
Retrieved 21st August, 2011 from [3] by Robert Layton
[1] https://creativecommons.org/licenses/by/2.0/
[2] https://www.flickr.com/photos/danielbuechele/
[3] https://www.flickr.com/photos/danielbuechele/6061409035/sizes/z/in/photostream/
Image: flower.jpg
Released under a creative commons license. [1]
Attribution: Some rights reserved by danielbuechele [2]
Retrieved 21st August, 2011 from [3] by Robert Layton
[1] https://creativecommons.org/licenses/by/2.0/
[2] https://www.flickr.com/photos/vultilion/
[3] https://www.flickr.com/photos/vultilion/6056698931/sizes/z/in/photostream/
Image: china.jpg
Released under a creative commons license. [1]
Attribution: Some rights reserved by danielbuechele [2]
Retrieved 21st August, 2011 from [3] by Robert Layton
[1] https://creativecommons.org/licenses/by/2.0/
[2] https://www.flickr.com/photos/danielbuechele/
[3] https://www.flickr.com/photos/danielbuechele/6061409035/sizes/z/in/photostream/
Image: flower.jpg
Released under a creative commons license. [1]
Attribution: Some rights reserved by danielbuechele [2]
Retrieved 21st August, 2011 from [3] by Robert Layton
[1] https://creativecommons.org/licenses/by/2.0/
[2] https://www.flickr.com/photos/vultilion/
[3] https://www.flickr.com/photos/vultilion/6056698931/sizes/z/in/photostream/
@@ -1,7 +1,7 @@
py.extension_module(
'_svmlight_format_fast',
cython_gen.process('_svmlight_format_fast.pyx'),
dependencies: [np_dep],
subdir: 'sklearn/datasets',
install: true
)
py.extension_module(
'_svmlight_format_fast',
cython_gen.process('_svmlight_format_fast.pyx'),
dependencies: [np_dep],
subdir: 'sklearn/datasets',
install: true
)
@@ -1,9 +1,9 @@
# comment
# note: the next line contains a tab
1.0 3:2.5 11:-5.2 16:1.5 # and an inline comment
2.0 6:1.0 13:-3
# another comment
3.0 21:27
4.0 2:1.234567890123456e10 # double precision value
1.0 # empty line, all zeros
2.0 3:0 # explicit zeros
# comment
# note: the next line contains a tab
1.0 3:2.5 11:-5.2 16:1.5 # and an inline comment
2.0 6:1.0 13:-3
# another comment
3.0 21:27
4.0 2:1.234567890123456e10 # double precision value
1.0 # empty line, all zeros
2.0 3:0 # explicit zeros
@@ -1,3 +1,3 @@
python 2:2.5 10:-5.2 15:1.5
2.0 5:1.0 12:-3
3.0 20:27
python 2:2.5 10:-5.2 15:1.5
2.0 5:1.0 12:-3
3.0 20:27
@@ -1 +1 @@
-1 5:2.5 2:-5.2 15:1.5
-1 5:2.5 2:-5.2 15:1.5
@@ -1,5 +1,5 @@
# multilabel dataset in SVMlight format
1,0 2:2.5 10:-5.2 15:1.5
2 5:1.0 12:-3
2:3.5 11:26
1,2 20:27
# multilabel dataset in SVMlight format
1,0 2:2.5 10:-5.2 15:1.5
2 5:1.0 12:-3
2:3.5 11:26
1,2 20:27
@@ -1,38 +1,38 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from cython cimport floating
from libc.math cimport fabs
def _update_cdnmf_fast(floating[:, ::1] W, floating[:, :] HHt,
floating[:, :] XHt, Py_ssize_t[::1] permutation):
cdef:
floating violation = 0
Py_ssize_t n_components = W.shape[1]
Py_ssize_t n_samples = W.shape[0] # n_features for H update
floating grad, pg, hess
Py_ssize_t i, r, s, t
with nogil:
for s in range(n_components):
t = permutation[s]
for i in range(n_samples):
# gradient = GW[t, i] where GW = np.dot(W, HHt) - XHt
grad = -XHt[i, t]
for r in range(n_components):
grad += HHt[t, r] * W[i, r]
# projected gradient
pg = min(0., grad) if W[i, t] == 0 else grad
violation += fabs(pg)
# Hessian
hess = HHt[t, t]
if hess != 0:
W[i, t] = max(W[i, t] - grad / hess, 0.)
return violation
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from cython cimport floating
from libc.math cimport fabs
def _update_cdnmf_fast(floating[:, ::1] W, floating[:, :] HHt,
floating[:, :] XHt, Py_ssize_t[::1] permutation):
cdef:
floating violation = 0
Py_ssize_t n_components = W.shape[1]
Py_ssize_t n_samples = W.shape[0] # n_features for H update
floating grad, pg, hess
Py_ssize_t i, r, s, t
with nogil:
for s in range(n_components):
t = permutation[s]
for i in range(n_samples):
# gradient = GW[t, i] where GW = np.dot(W, HHt) - XHt
grad = -XHt[i, t]
for r in range(n_components):
grad += HHt[t, r] * W[i, r]
# projected gradient
pg = min(0., grad) if W[i, t] == 0 else grad
violation += fabs(pg)
# Hessian
hess = HHt[t, t]
if hess != 0:
W[i, t] = max(W[i, t] - grad / hess, 0.)
return violation
@@ -1,110 +1,110 @@
import numpy as np
from cython cimport floating
from libc.math cimport exp, fabs, log
from ..utils._typedefs cimport float64_t, intp_t
def mean_change(const floating[:] arr_1, const floating[:] arr_2):
"""Calculate the mean difference between two arrays.
Equivalent to np.abs(arr_1 - arr2).mean().
"""
cdef float64_t total, diff
cdef intp_t i, size
size = arr_1.shape[0]
total = 0.0
for i in range(size):
diff = fabs(arr_1[i] - arr_2[i])
total += diff
return total / size
def _dirichlet_expectation_1d(
floating[:] doc_topic,
floating doc_topic_prior,
floating[:] out
):
"""Dirichlet expectation for a single sample:
exp(E[log(theta)]) for theta ~ Dir(doc_topic)
after adding doc_topic_prior to doc_topic, in-place.
Equivalent to
doc_topic += doc_topic_prior
out[:] = np.exp(psi(doc_topic) - psi(np.sum(doc_topic)))
"""
cdef floating dt, psi_total, total
cdef intp_t i, size
size = doc_topic.shape[0]
total = 0.0
for i in range(size):
dt = doc_topic[i] + doc_topic_prior
doc_topic[i] = dt
total += dt
psi_total = psi(total)
for i in range(size):
out[i] = exp(psi(doc_topic[i]) - psi_total)
def _dirichlet_expectation_2d(const floating[:, :] arr):
"""Dirichlet expectation for multiple samples:
E[log(theta)] for theta ~ Dir(arr).
Equivalent to psi(arr) - psi(np.sum(arr, axis=1))[:, np.newaxis].
Note that unlike _dirichlet_expectation_1d, this function doesn't compute
the exp and doesn't add in the prior.
"""
cdef floating row_total, psi_row_total
cdef floating[:, :] d_exp
cdef intp_t i, j, n_rows, n_cols
n_rows = arr.shape[0]
n_cols = arr.shape[1]
d_exp = np.empty_like(arr)
for i in range(n_rows):
row_total = 0
for j in range(n_cols):
row_total += arr[i, j]
psi_row_total = psi(row_total)
for j in range(n_cols):
d_exp[i, j] = psi(arr[i, j]) - psi_row_total
return d_exp.base
# Psi function for positive arguments. Optimized for speed, not accuracy.
#
# After: J. Bernardo (1976). Algorithm AS 103: Psi (Digamma) Function.
# https://www.uv.es/~bernardo/1976AppStatist.pdf
cdef floating psi(floating x) noexcept nogil:
cdef double EULER = 0.577215664901532860606512090082402431
if x <= 1e-6:
# psi(x) = -EULER - 1/x + O(x)
return -EULER - 1. / x
cdef floating r, result = 0
# psi(x + 1) = psi(x) + 1/x
while x < 6:
result -= 1. / x
x += 1
# psi(x) = log(x) - 1/(2x) - 1/(12x**2) + 1/(120x**4) - 1/(252x**6)
# + O(1/x**8)
r = 1. / x
result += log(x) - .5 * r
r = r * r
result -= r * ((1./12.) - r * ((1./120.) - r * (1./252.)))
return result
import numpy as np
from cython cimport floating
from libc.math cimport exp, fabs, log
from ..utils._typedefs cimport float64_t, intp_t
def mean_change(const floating[:] arr_1, const floating[:] arr_2):
"""Calculate the mean difference between two arrays.
Equivalent to np.abs(arr_1 - arr2).mean().
"""
cdef float64_t total, diff
cdef intp_t i, size
size = arr_1.shape[0]
total = 0.0
for i in range(size):
diff = fabs(arr_1[i] - arr_2[i])
total += diff
return total / size
def _dirichlet_expectation_1d(
floating[:] doc_topic,
floating doc_topic_prior,
floating[:] out
):
"""Dirichlet expectation for a single sample:
exp(E[log(theta)]) for theta ~ Dir(doc_topic)
after adding doc_topic_prior to doc_topic, in-place.
Equivalent to
doc_topic += doc_topic_prior
out[:] = np.exp(psi(doc_topic) - psi(np.sum(doc_topic)))
"""
cdef floating dt, psi_total, total
cdef intp_t i, size
size = doc_topic.shape[0]
total = 0.0
for i in range(size):
dt = doc_topic[i] + doc_topic_prior
doc_topic[i] = dt
total += dt
psi_total = psi(total)
for i in range(size):
out[i] = exp(psi(doc_topic[i]) - psi_total)
def _dirichlet_expectation_2d(const floating[:, :] arr):
"""Dirichlet expectation for multiple samples:
E[log(theta)] for theta ~ Dir(arr).
Equivalent to psi(arr) - psi(np.sum(arr, axis=1))[:, np.newaxis].
Note that unlike _dirichlet_expectation_1d, this function doesn't compute
the exp and doesn't add in the prior.
"""
cdef floating row_total, psi_row_total
cdef floating[:, :] d_exp
cdef intp_t i, j, n_rows, n_cols
n_rows = arr.shape[0]
n_cols = arr.shape[1]
d_exp = np.empty_like(arr)
for i in range(n_rows):
row_total = 0
for j in range(n_cols):
row_total += arr[i, j]
psi_row_total = psi(row_total)
for j in range(n_cols):
d_exp[i, j] = psi(arr[i, j]) - psi_row_total
return d_exp.base
# Psi function for positive arguments. Optimized for speed, not accuracy.
#
# After: J. Bernardo (1976). Algorithm AS 103: Psi (Digamma) Function.
# https://www.uv.es/~bernardo/1976AppStatist.pdf
cdef floating psi(floating x) noexcept nogil:
cdef double EULER = 0.577215664901532860606512090082402431
if x <= 1e-6:
# psi(x) = -EULER - 1/x + O(x)
return -EULER - 1. / x
cdef floating r, result = 0
# psi(x + 1) = psi(x) + 1/x
while x < 6:
result -= 1. / x
x += 1
# psi(x) = log(x) - 1/(2x) - 1/(12x**2) + 1/(120x**4) - 1/(252x**6)
# + O(1/x**8)
r = 1. / x
result += log(x) - .5 * r
r = r * r
result -= r * ((1./12.) - r * ((1./120.) - r * (1./252.)))
return result
@@ -1,14 +1,14 @@
py.extension_module(
'_online_lda_fast',
[cython_gen.process('_online_lda_fast.pyx'), utils_cython_tree],
subdir: 'sklearn/decomposition',
install: true
)
py.extension_module(
'_cdnmf_fast',
cython_gen.process('_cdnmf_fast.pyx'),
dependencies: [np_dep],
subdir: 'sklearn/decomposition',
install: true
)
py.extension_module(
'_online_lda_fast',
[cython_gen.process('_online_lda_fast.pyx'), utils_cython_tree],
subdir: 'sklearn/decomposition',
install: true
)
py.extension_module(
'_cdnmf_fast',
cython_gen.process('_cdnmf_fast.pyx'),
dependencies: [np_dep],
subdir: 'sklearn/decomposition',
install: true
)
@@ -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')
@@ -1,7 +1,7 @@
This directory contains bundled external dependencies that are updated
every once in a while.
Note for distribution packagers: if you want to remove the duplicated
code and depend on a packaged version, we suggest that you simply do a
symbolic link in this directory.
This directory contains bundled external dependencies that are updated
every once in a while.
Note for distribution packagers: if you want to remove the duplicated
code and depend on a packaged version, we suggest that you simply do a
symbolic link in this directory.
@@ -1,21 +1,21 @@
MIT License
Copyright (c) 2022 Consortium for Python Data API Standards
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
MIT License
Copyright (c) 2022 Consortium for Python Data API Standards
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -1 +1 @@
Update this directory using maint_tools/vendor_array_api_compat.sh
Update this directory using maint_tools/vendor_array_api_compat.sh
@@ -1,21 +1,21 @@
MIT License
Copyright (c) 2024 Consortium for Python Data API Standards
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
MIT License
Copyright (c) 2024 Consortium for Python Data API Standards
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -1 +1 @@
Update this directory using maint_tools/vendor_array_api_extra.sh
Update this directory using maint_tools/vendor_array_api_extra.sh
@@ -1,40 +1,40 @@
"""Static type stubs for `_compat.py`."""
# https://github.com/scikit-learn/scikit-learn/pull/27910#issuecomment-2568023972
from __future__ import annotations
from types import ModuleType
# TODO import from typing (requires Python >=3.13)
from typing_extensions import TypeIs
from ._typing import Array, Device
# pylint: disable=missing-class-docstring,unused-argument
class Namespace(ModuleType):
def device(self, x: Array, /) -> Device: ...
def array_namespace(
*xs: Array | complex | None,
api_version: str | None = None,
use_compat: bool | None = None,
) -> Namespace: ...
def device(x: Array, /) -> Device: ...
def is_array_api_obj(x: object, /) -> TypeIs[Array]: ...
def is_array_api_strict_namespace(xp: ModuleType, /) -> TypeIs[Namespace]: ...
def is_cupy_namespace(xp: ModuleType, /) -> TypeIs[Namespace]: ...
def is_dask_namespace(xp: ModuleType, /) -> TypeIs[Namespace]: ...
def is_jax_namespace(xp: ModuleType, /) -> TypeIs[Namespace]: ...
def is_numpy_namespace(xp: ModuleType, /) -> TypeIs[Namespace]: ...
def is_pydata_sparse_namespace(xp: ModuleType, /) -> TypeIs[Namespace]: ...
def is_torch_namespace(xp: ModuleType, /) -> TypeIs[Namespace]: ...
def is_cupy_array(x: object, /) -> TypeIs[Array]: ...
def is_dask_array(x: object, /) -> TypeIs[Array]: ...
def is_jax_array(x: object, /) -> TypeIs[Array]: ...
def is_numpy_array(x: object, /) -> TypeIs[Array]: ...
def is_pydata_sparse_array(x: object, /) -> TypeIs[Array]: ...
def is_torch_array(x: object, /) -> TypeIs[Array]: ...
def is_lazy_array(x: object, /) -> TypeIs[Array]: ...
def is_writeable_array(x: object, /) -> TypeIs[Array]: ...
def size(x: Array, /) -> int | None: ...
"""Static type stubs for `_compat.py`."""
# https://github.com/scikit-learn/scikit-learn/pull/27910#issuecomment-2568023972
from __future__ import annotations
from types import ModuleType
# TODO import from typing (requires Python >=3.13)
from typing_extensions import TypeIs
from ._typing import Array, Device
# pylint: disable=missing-class-docstring,unused-argument
class Namespace(ModuleType):
def device(self, x: Array, /) -> Device: ...
def array_namespace(
*xs: Array | complex | None,
api_version: str | None = None,
use_compat: bool | None = None,
) -> Namespace: ...
def device(x: Array, /) -> Device: ...
def is_array_api_obj(x: object, /) -> TypeIs[Array]: ...
def is_array_api_strict_namespace(xp: ModuleType, /) -> TypeIs[Namespace]: ...
def is_cupy_namespace(xp: ModuleType, /) -> TypeIs[Namespace]: ...
def is_dask_namespace(xp: ModuleType, /) -> TypeIs[Namespace]: ...
def is_jax_namespace(xp: ModuleType, /) -> TypeIs[Namespace]: ...
def is_numpy_namespace(xp: ModuleType, /) -> TypeIs[Namespace]: ...
def is_pydata_sparse_namespace(xp: ModuleType, /) -> TypeIs[Namespace]: ...
def is_torch_namespace(xp: ModuleType, /) -> TypeIs[Namespace]: ...
def is_cupy_array(x: object, /) -> TypeIs[Array]: ...
def is_dask_array(x: object, /) -> TypeIs[Array]: ...
def is_jax_array(x: object, /) -> TypeIs[Array]: ...
def is_numpy_array(x: object, /) -> TypeIs[Array]: ...
def is_pydata_sparse_array(x: object, /) -> TypeIs[Array]: ...
def is_torch_array(x: object, /) -> TypeIs[Array]: ...
def is_lazy_array(x: object, /) -> TypeIs[Array]: ...
def is_writeable_array(x: object, /) -> TypeIs[Array]: ...
def size(x: Array, /) -> int | None: ...
@@ -1,105 +1,105 @@
"""Static typing helpers."""
from __future__ import annotations
from types import EllipsisType
from typing import Protocol, TypeAlias
# TODO import from typing (requires Python >=3.12)
from typing_extensions import override
# TODO: use array-api-typing once it is available
class Array(Protocol): # pylint: disable=missing-class-docstring
# Unary operations
def __abs__(self) -> Array: ...
def __pos__(self) -> Array: ...
def __neg__(self) -> Array: ...
def __invert__(self) -> Array: ...
# Binary operations
def __add__(self, other: Array | complex, /) -> Array: ...
def __sub__(self, other: Array | complex, /) -> Array: ...
def __mul__(self, other: Array | complex, /) -> Array: ...
def __truediv__(self, other: Array | complex, /) -> Array: ...
def __floordiv__(self, other: Array | complex, /) -> Array: ...
def __mod__(self, other: Array | complex, /) -> Array: ...
def __pow__(self, other: Array | complex, /) -> Array: ...
def __matmul__(self, other: Array, /) -> Array: ...
def __and__(self, other: Array | int, /) -> Array: ...
def __or__(self, other: Array | int, /) -> Array: ...
def __xor__(self, other: Array | int, /) -> Array: ...
def __lshift__(self, other: Array | int, /) -> Array: ...
def __rshift__(self, other: Array | int, /) -> Array: ...
def __lt__(self, other: Array | complex, /) -> Array: ...
def __le__(self, other: Array | complex, /) -> Array: ...
def __gt__(self, other: Array | complex, /) -> Array: ...
def __ge__(self, other: Array | complex, /) -> Array: ...
@override
def __eq__(self, other: Array | complex, /) -> Array: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
@override
def __ne__(self, other: Array | complex, /) -> Array: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
# Reflected operations
def __radd__(self, other: Array | complex, /) -> Array: ...
def __rsub__(self, other: Array | complex, /) -> Array: ...
def __rmul__(self, other: Array | complex, /) -> Array: ...
def __rtruediv__(self, other: Array | complex, /) -> Array: ...
def __rfloordiv__(self, other: Array | complex, /) -> Array: ...
def __rmod__(self, other: Array | complex, /) -> Array: ...
def __rpow__(self, other: Array | complex, /) -> Array: ...
def __rmatmul__(self, other: Array, /) -> Array: ...
def __rand__(self, other: Array | int, /) -> Array: ...
def __ror__(self, other: Array | int, /) -> Array: ...
def __rxor__(self, other: Array | int, /) -> Array: ...
def __rlshift__(self, other: Array | int, /) -> Array: ...
def __rrshift__(self, other: Array | int, /) -> Array: ...
# Attributes
@property
def dtype(self) -> DType: ...
@property
def device(self) -> Device: ...
@property
def mT(self) -> Array: ... # pylint: disable=invalid-name
@property
def ndim(self) -> int: ...
@property
def shape(self) -> tuple[int | None, ...]: ...
@property
def size(self) -> int | None: ...
@property
def T(self) -> Array: ... # pylint: disable=invalid-name
# Collection operations (note: an Array does not have to be Sized or Iterable)
def __getitem__(self, key: GetIndex, /) -> Array: ...
def __setitem__(self, key: SetIndex, value: Array | complex, /) -> None: ...
# Materialization methods (may raise on lazy arrays)
def __bool__(self) -> bool: ...
def __complex__(self) -> complex: ...
def __float__(self) -> float: ...
def __index__(self) -> int: ...
def __int__(self) -> int: ...
# Misc methods (frequently not implemented in Arrays wrapped by array-api-compat)
# def __array_namespace__(*, api_version: str | None) -> ModuleType: ...
# def __dlpack__(
# *,
# stream: int | Any | None = None,
# max_version: tuple[int, int] | None = None,
# dl_device: tuple[int, int] | None = None, # tuple[Enum, int]
# copy: bool | None = None,
# ) -> Any: ...
# def __dlpack_device__() -> tuple[int, int]: ... # tuple[Enum, int]
# def to_device(device: Device, /, *, stream: int | Any | None = None) -> Array: ...
class DType(Protocol): # pylint: disable=missing-class-docstring
pass
class Device(Protocol): # pylint: disable=missing-class-docstring
pass
SetIndex: TypeAlias = ( # type: ignore[explicit-any]
int | slice | EllipsisType | Array | tuple[int | slice | EllipsisType | Array, ...]
)
GetIndex: TypeAlias = ( # type: ignore[explicit-any]
SetIndex | None | tuple[int | slice | EllipsisType | None | Array, ...]
)
__all__ = ["Array", "DType", "Device", "GetIndex", "SetIndex"]
"""Static typing helpers."""
from __future__ import annotations
from types import EllipsisType
from typing import Protocol, TypeAlias
# TODO import from typing (requires Python >=3.12)
from typing_extensions import override
# TODO: use array-api-typing once it is available
class Array(Protocol): # pylint: disable=missing-class-docstring
# Unary operations
def __abs__(self) -> Array: ...
def __pos__(self) -> Array: ...
def __neg__(self) -> Array: ...
def __invert__(self) -> Array: ...
# Binary operations
def __add__(self, other: Array | complex, /) -> Array: ...
def __sub__(self, other: Array | complex, /) -> Array: ...
def __mul__(self, other: Array | complex, /) -> Array: ...
def __truediv__(self, other: Array | complex, /) -> Array: ...
def __floordiv__(self, other: Array | complex, /) -> Array: ...
def __mod__(self, other: Array | complex, /) -> Array: ...
def __pow__(self, other: Array | complex, /) -> Array: ...
def __matmul__(self, other: Array, /) -> Array: ...
def __and__(self, other: Array | int, /) -> Array: ...
def __or__(self, other: Array | int, /) -> Array: ...
def __xor__(self, other: Array | int, /) -> Array: ...
def __lshift__(self, other: Array | int, /) -> Array: ...
def __rshift__(self, other: Array | int, /) -> Array: ...
def __lt__(self, other: Array | complex, /) -> Array: ...
def __le__(self, other: Array | complex, /) -> Array: ...
def __gt__(self, other: Array | complex, /) -> Array: ...
def __ge__(self, other: Array | complex, /) -> Array: ...
@override
def __eq__(self, other: Array | complex, /) -> Array: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
@override
def __ne__(self, other: Array | complex, /) -> Array: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
# Reflected operations
def __radd__(self, other: Array | complex, /) -> Array: ...
def __rsub__(self, other: Array | complex, /) -> Array: ...
def __rmul__(self, other: Array | complex, /) -> Array: ...
def __rtruediv__(self, other: Array | complex, /) -> Array: ...
def __rfloordiv__(self, other: Array | complex, /) -> Array: ...
def __rmod__(self, other: Array | complex, /) -> Array: ...
def __rpow__(self, other: Array | complex, /) -> Array: ...
def __rmatmul__(self, other: Array, /) -> Array: ...
def __rand__(self, other: Array | int, /) -> Array: ...
def __ror__(self, other: Array | int, /) -> Array: ...
def __rxor__(self, other: Array | int, /) -> Array: ...
def __rlshift__(self, other: Array | int, /) -> Array: ...
def __rrshift__(self, other: Array | int, /) -> Array: ...
# Attributes
@property
def dtype(self) -> DType: ...
@property
def device(self) -> Device: ...
@property
def mT(self) -> Array: ... # pylint: disable=invalid-name
@property
def ndim(self) -> int: ...
@property
def shape(self) -> tuple[int | None, ...]: ...
@property
def size(self) -> int | None: ...
@property
def T(self) -> Array: ... # pylint: disable=invalid-name
# Collection operations (note: an Array does not have to be Sized or Iterable)
def __getitem__(self, key: GetIndex, /) -> Array: ...
def __setitem__(self, key: SetIndex, value: Array | complex, /) -> None: ...
# Materialization methods (may raise on lazy arrays)
def __bool__(self) -> bool: ...
def __complex__(self) -> complex: ...
def __float__(self) -> float: ...
def __index__(self) -> int: ...
def __int__(self) -> int: ...
# Misc methods (frequently not implemented in Arrays wrapped by array-api-compat)
# def __array_namespace__(*, api_version: str | None) -> ModuleType: ...
# def __dlpack__(
# *,
# stream: int | Any | None = None,
# max_version: tuple[int, int] | None = None,
# dl_device: tuple[int, int] | None = None, # tuple[Enum, int]
# copy: bool | None = None,
# ) -> Any: ...
# def __dlpack_device__() -> tuple[int, int]: ... # tuple[Enum, int]
# def to_device(device: Device, /, *, stream: int | Any | None = None) -> Array: ...
class DType(Protocol): # pylint: disable=missing-class-docstring
pass
class Device(Protocol): # pylint: disable=missing-class-docstring
pass
SetIndex: TypeAlias = ( # type: ignore[explicit-any]
int | slice | EllipsisType | Array | tuple[int | slice | EllipsisType | Array, ...]
)
GetIndex: TypeAlias = ( # type: ignore[explicit-any]
SetIndex | None | tuple[int | slice | EllipsisType | None | Array, ...]
)
__all__ = ["Array", "DType", "Device", "GetIndex", "SetIndex"]
@@ -1,89 +1,89 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from libc.stdlib cimport abs
from libcpp.vector cimport vector
cimport numpy as cnp
import numpy as np
from ..utils._typedefs cimport int32_t, int64_t
from ..utils.murmurhash cimport murmurhash3_bytes_s32
from ..utils._vector_sentinel cimport vector_to_nd_array
cnp.import_array()
def transform(raw_X, Py_ssize_t n_features, dtype,
bint alternate_sign=1, unsigned int seed=0):
"""Guts of FeatureHasher.transform.
Returns
-------
n_samples : integer
indices, indptr, values : lists
For constructing a scipy.sparse.csr_matrix.
"""
cdef int32_t h
cdef double value
cdef vector[int32_t] indices
cdef vector[int64_t] indptr
indptr.push_back(0)
# Since Python array does not understand Numpy dtypes, we grow the indices
# and values arrays ourselves. Use a Py_ssize_t capacity for safety.
cdef Py_ssize_t capacity = 8192 # arbitrary
cdef int64_t size = 0
cdef cnp.ndarray values = np.empty(capacity, dtype=dtype)
for x in raw_X:
for f, v in x:
if isinstance(v, (str, unicode)):
f = "%s%s%s" % (f, '=', v)
value = 1
else:
value = v
if value == 0:
continue
if isinstance(f, unicode):
f = (<unicode>f).encode("utf-8")
# Need explicit type check because Murmurhash does not propagate
# all exceptions. Add "except *" there?
elif not isinstance(f, bytes):
raise TypeError("feature names must be strings")
h = murmurhash3_bytes_s32(<bytes>f, seed)
if h == - 2147483648:
# abs(-2**31) is undefined behavior because h is a `np.int32`
# The following is defined such that it is equal to: abs(-2**31) % n_features
indices.push_back((2147483647 - (n_features - 1)) % n_features)
else:
indices.push_back(abs(h) % n_features)
# improve inner product preservation in the hashed space
if alternate_sign:
value *= (h >= 0) * 2 - 1
values[size] = value
size += 1
if size == capacity:
capacity *= 2
# can't use resize member because there might be multiple
# references to the arrays due to Cython's error checking
values = np.resize(values, capacity)
indptr.push_back(size)
indices_array = vector_to_nd_array(&indices)
indptr_array = vector_to_nd_array(&indptr)
if indptr_array[indptr_array.shape[0]-1] > np.iinfo(np.int32).max: # = 2**31 - 1
# both indices and indptr have the same dtype in CSR arrays
indices_array = indices_array.astype(np.int64, copy=False)
else:
indptr_array = indptr_array.astype(np.int32, copy=False)
return (indices_array, indptr_array, values[:size])
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from libc.stdlib cimport abs
from libcpp.vector cimport vector
cimport numpy as cnp
import numpy as np
from ..utils._typedefs cimport int32_t, int64_t
from ..utils.murmurhash cimport murmurhash3_bytes_s32
from ..utils._vector_sentinel cimport vector_to_nd_array
cnp.import_array()
def transform(raw_X, Py_ssize_t n_features, dtype,
bint alternate_sign=1, unsigned int seed=0):
"""Guts of FeatureHasher.transform.
Returns
-------
n_samples : integer
indices, indptr, values : lists
For constructing a scipy.sparse.csr_matrix.
"""
cdef int32_t h
cdef double value
cdef vector[int32_t] indices
cdef vector[int64_t] indptr
indptr.push_back(0)
# Since Python array does not understand Numpy dtypes, we grow the indices
# and values arrays ourselves. Use a Py_ssize_t capacity for safety.
cdef Py_ssize_t capacity = 8192 # arbitrary
cdef int64_t size = 0
cdef cnp.ndarray values = np.empty(capacity, dtype=dtype)
for x in raw_X:
for f, v in x:
if isinstance(v, (str, unicode)):
f = "%s%s%s" % (f, '=', v)
value = 1
else:
value = v
if value == 0:
continue
if isinstance(f, unicode):
f = (<unicode>f).encode("utf-8")
# Need explicit type check because Murmurhash does not propagate
# all exceptions. Add "except *" there?
elif not isinstance(f, bytes):
raise TypeError("feature names must be strings")
h = murmurhash3_bytes_s32(<bytes>f, seed)
if h == - 2147483648:
# abs(-2**31) is undefined behavior because h is a `np.int32`
# The following is defined such that it is equal to: abs(-2**31) % n_features
indices.push_back((2147483647 - (n_features - 1)) % n_features)
else:
indices.push_back(abs(h) % n_features)
# improve inner product preservation in the hashed space
if alternate_sign:
value *= (h >= 0) * 2 - 1
values[size] = value
size += 1
if size == capacity:
capacity *= 2
# can't use resize member because there might be multiple
# references to the arrays due to Cython's error checking
values = np.resize(values, capacity)
indptr.push_back(size)
indices_array = vector_to_nd_array(&indices)
indptr_array = vector_to_nd_array(&indptr)
if indptr_array[indptr_array.shape[0]-1] > np.iinfo(np.int32).max: # = 2**31 - 1
# both indices and indptr have the same dtype in CSR arrays
indices_array = indices_array.astype(np.int64, copy=False)
else:
indptr_array = indptr_array.astype(np.int32, copy=False)
return (indices_array, indptr_array, values[:size])
@@ -1,7 +1,7 @@
py.extension_module(
'_hashing_fast',
[cython_gen_cpp.process('_hashing_fast.pyx'), utils_cython_tree],
dependencies: [np_dep],
subdir: 'sklearn/feature_extraction',
install: true
)
py.extension_module(
'_hashing_fast',
[cython_gen_cpp.process('_hashing_fast.pyx'), utils_cython_tree],
dependencies: [np_dep],
subdir: 'sklearn/feature_extraction',
install: true
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,32 +1,32 @@
# .pyx is generated, so this is needed to make Cython compilation work
linear_model_cython_tree = [
fs.copyfile('__init__.py'),
]
py.extension_module(
'_cd_fast',
[cython_gen.process('_cd_fast.pyx'), utils_cython_tree],
subdir: 'sklearn/linear_model',
install: true
)
name_list = ['_sgd_fast', '_sag_fast']
foreach name: name_list
pyx = custom_target(
name + '_pyx',
output: name + '.pyx',
input: name + '.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [linear_model_cython_tree, utils_cython_tree, _loss_cython_tree],
)
py.extension_module(
name,
cython_gen.process(pyx),
subdir: 'sklearn/linear_model',
install: true
)
endforeach
# .pyx is generated, so this is needed to make Cython compilation work
linear_model_cython_tree = [
fs.copyfile('__init__.py'),
]
py.extension_module(
'_cd_fast',
[cython_gen.process('_cd_fast.pyx'), utils_cython_tree],
subdir: 'sklearn/linear_model',
install: true
)
name_list = ['_sgd_fast', '_sag_fast']
foreach name: name_list
pyx = custom_target(
name + '_pyx',
output: name + '.pyx',
input: name + '.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [linear_model_cython_tree, utils_cython_tree, _loss_cython_tree],
)
py.extension_module(
name,
cython_gen.process(pyx),
subdir: 'sklearn/linear_model',
install: true
)
endforeach
@@ -1,295 +1,295 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
# See http://homepage.tudelft.nl/19j49/t-SNE.html for reference
# implementations and papers describing the technique
import numpy as np
cimport numpy as cnp
from libc.stdio cimport printf
from libc.math cimport log
from libc.stdlib cimport malloc, free
from libc.time cimport clock, clock_t
from cython.parallel cimport prange, parallel
from ..neighbors._quad_tree cimport _QuadTree
cnp.import_array()
cdef char* EMPTY_STRING = ""
# Smallest strictly positive value that can be represented by floating
# point numbers for different precision levels. This is useful to avoid
# taking the log of zero when computing the KL divergence.
cdef float FLOAT32_TINY = np.finfo(np.float32).tiny
# Useful to void division by zero or divergence to +inf.
cdef float FLOAT64_EPS = np.finfo(np.float64).eps
# This is effectively an ifdef statement in Cython
# It allows us to write printf debugging lines
# and remove them at compile time
cdef enum:
DEBUGFLAG = 0
cdef float compute_gradient(float[:] val_P,
float[:, :] pos_reference,
cnp.int64_t[:] neighbors,
cnp.int64_t[:] indptr,
float[:, :] tot_force,
_QuadTree qt,
float theta,
int dof,
long start,
bint compute_error,
int num_threads) noexcept nogil:
# Having created the tree, calculate the gradient
# in two components, the positive and negative forces
cdef:
long i, coord
int ax
long n_samples = pos_reference.shape[0]
int n_dimensions = qt.n_dimensions
clock_t t1 = 0, t2 = 0
double sQ
float error
int take_timing = 1 if qt.verbose > 15 else 0
if qt.verbose > 11:
printf("[t-SNE] Allocating %li elements in force arrays\n",
n_samples * n_dimensions * 2)
cdef float* neg_f = <float*> malloc(sizeof(float) * n_samples * n_dimensions)
cdef float* pos_f = <float*> malloc(sizeof(float) * n_samples * n_dimensions)
if take_timing:
t1 = clock()
sQ = compute_gradient_negative(pos_reference, neg_f, qt, dof, theta, start,
num_threads)
if take_timing:
t2 = clock()
printf("[t-SNE] Computing negative gradient: %e ticks\n", ((float) (t2 - t1)))
if take_timing:
t1 = clock()
error = compute_gradient_positive(val_P, pos_reference, neighbors, indptr,
pos_f, n_dimensions, dof, sQ, start,
qt.verbose, compute_error, num_threads)
if take_timing:
t2 = clock()
printf("[t-SNE] Computing positive gradient: %e ticks\n",
((float) (t2 - t1)))
for i in prange(start, n_samples, nogil=True, num_threads=num_threads,
schedule='static'):
for ax in range(n_dimensions):
coord = i * n_dimensions + ax
tot_force[i, ax] = pos_f[coord] - (neg_f[coord] / sQ)
free(neg_f)
free(pos_f)
return error
cdef float compute_gradient_positive(float[:] val_P,
float[:, :] pos_reference,
cnp.int64_t[:] neighbors,
cnp.int64_t[:] indptr,
float* pos_f,
int n_dimensions,
int dof,
double sum_Q,
cnp.int64_t start,
int verbose,
bint compute_error,
int num_threads) noexcept nogil:
# Sum over the following expression for i not equal to j
# grad_i = p_ij (1 + ||y_i - y_j||^2)^-1 (y_i - y_j)
# This is equivalent to compute_edge_forces in the authors' code
# It just goes over the nearest neighbors instead of all the data points
# (unlike the non-nearest neighbors version of `compute_gradient_positive')
cdef:
int ax
long i, j, k
long n_samples = indptr.shape[0] - 1
float C = 0.0
float dij, qij, pij
float exponent = (dof + 1.0) / 2.0
float float_dof = (float) (dof)
float* buff
clock_t t1 = 0, t2 = 0
float dt
if verbose > 10:
t1 = clock()
with nogil, parallel(num_threads=num_threads):
# Define private buffer variables
buff = <float *> malloc(sizeof(float) * n_dimensions)
for i in prange(start, n_samples, schedule='static'):
# Init the gradient vector
for ax in range(n_dimensions):
pos_f[i * n_dimensions + ax] = 0.0
# Compute the positive interaction for the nearest neighbors
for k in range(indptr[i], indptr[i+1]):
j = neighbors[k]
dij = 0.0
pij = val_P[k]
for ax in range(n_dimensions):
buff[ax] = pos_reference[i, ax] - pos_reference[j, ax]
dij += buff[ax] * buff[ax]
qij = float_dof / (float_dof + dij)
if dof != 1: # i.e. exponent != 1
qij = qij ** exponent
dij = pij * qij
# only compute the error when needed
if compute_error:
qij = qij / sum_Q
C += pij * log(max(pij, FLOAT32_TINY) / max(qij, FLOAT32_TINY))
for ax in range(n_dimensions):
pos_f[i * n_dimensions + ax] += dij * buff[ax]
free(buff)
if verbose > 10:
t2 = clock()
dt = ((float) (t2 - t1))
printf("[t-SNE] Computed error=%1.4f in %1.1e ticks\n", C, dt)
return C
cdef double compute_gradient_negative(float[:, :] pos_reference,
float* neg_f,
_QuadTree qt,
int dof,
float theta,
long start,
int num_threads) noexcept nogil:
cdef:
int ax
int n_dimensions = qt.n_dimensions
int offset = n_dimensions + 2
long i, j, idx
long n_samples = pos_reference.shape[0]
long n = n_samples - start
long dta = 0
long dtb = 0
float size, dist2s, mult
float exponent = (dof + 1.0) / 2.0
float float_dof = (float) (dof)
double qijZ, sum_Q = 0.0
float* force
float* neg_force
float* pos
clock_t t1 = 0, t2 = 0, t3 = 0
int take_timing = 1 if qt.verbose > 20 else 0
with nogil, parallel(num_threads=num_threads):
# Define thread-local buffers
summary = <float*> malloc(sizeof(float) * n * offset)
pos = <float *> malloc(sizeof(float) * n_dimensions)
force = <float *> malloc(sizeof(float) * n_dimensions)
neg_force = <float *> malloc(sizeof(float) * n_dimensions)
for i in prange(start, n_samples, schedule='static'):
# Clear the arrays
for ax in range(n_dimensions):
force[ax] = 0.0
neg_force[ax] = 0.0
pos[ax] = pos_reference[i, ax]
# Find which nodes are summarizing and collect their centers of mass
# deltas, and sizes, into vectorized arrays
if take_timing:
t1 = clock()
idx = qt.summarize(pos, summary, theta*theta)
if take_timing:
t2 = clock()
# Compute the t-SNE negative force
# for the digits dataset, walking the tree
# is about 10-15x more expensive than the
# following for loop
for j in range(idx // offset):
dist2s = summary[j * offset + n_dimensions]
size = summary[j * offset + n_dimensions + 1]
qijZ = float_dof / (float_dof + dist2s) # 1/(1+dist)
if dof != 1: # i.e. exponent != 1
qijZ = qijZ ** exponent
sum_Q += size * qijZ # size of the node * q
mult = size * qijZ * qijZ
for ax in range(n_dimensions):
neg_force[ax] += mult * summary[j * offset + ax]
if take_timing:
t3 = clock()
for ax in range(n_dimensions):
neg_f[i * n_dimensions + ax] = neg_force[ax]
if take_timing:
dta += t2 - t1
dtb += t3 - t2
free(pos)
free(force)
free(neg_force)
free(summary)
if take_timing:
printf("[t-SNE] Tree: %li clock ticks | ", dta)
printf("Force computation: %li clock ticks\n", dtb)
# Put sum_Q to machine EPSILON to avoid divisions by 0
sum_Q = max(sum_Q, FLOAT64_EPS)
return sum_Q
def gradient(float[:] val_P,
float[:, :] pos_output,
cnp.int64_t[:] neighbors,
cnp.int64_t[:] indptr,
float[:, :] forces,
float theta,
int n_dimensions,
int verbose,
int dof=1,
long skip_num_points=0,
bint compute_error=1,
int num_threads=1):
# This function is designed to be called from external Python
# it passes the 'forces' array by reference and fills that's array
# up in-place
cdef float C
cdef int n
n = pos_output.shape[0]
assert val_P.itemsize == 4
assert pos_output.itemsize == 4
assert forces.itemsize == 4
m = "Forces array and pos_output shapes are incompatible"
assert n == forces.shape[0], m
m = "Pij and pos_output shapes are incompatible"
assert n == indptr.shape[0] - 1, m
if verbose > 10:
printf("[t-SNE] Initializing tree of n_dimensions %i\n", n_dimensions)
cdef _QuadTree qt = _QuadTree(pos_output.shape[1], verbose)
if verbose > 10:
printf("[t-SNE] Inserting %li points\n", pos_output.shape[0])
qt.build_tree(pos_output)
if verbose > 10:
# XXX: format hack to workaround lack of `const char *` type
# in the generated C code that triggers error with gcc 4.9
# and -Werror=format-security
printf("[t-SNE] Computing gradient\n%s", EMPTY_STRING)
C = compute_gradient(val_P, pos_output, neighbors, indptr, forces,
qt, theta, dof, skip_num_points, compute_error,
num_threads)
if verbose > 10:
# XXX: format hack to workaround lack of `const char *` type
# in the generated C code
# and -Werror=format-security
printf("[t-SNE] Checking tree consistency\n%s", EMPTY_STRING)
m = "Tree consistency failed: unexpected number of points on the tree"
assert qt.cells[0].cumulative_size == qt.n_points, m
if not compute_error:
C = np.nan
return C
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
# See http://homepage.tudelft.nl/19j49/t-SNE.html for reference
# implementations and papers describing the technique
import numpy as np
cimport numpy as cnp
from libc.stdio cimport printf
from libc.math cimport log
from libc.stdlib cimport malloc, free
from libc.time cimport clock, clock_t
from cython.parallel cimport prange, parallel
from ..neighbors._quad_tree cimport _QuadTree
cnp.import_array()
cdef char* EMPTY_STRING = ""
# Smallest strictly positive value that can be represented by floating
# point numbers for different precision levels. This is useful to avoid
# taking the log of zero when computing the KL divergence.
cdef float FLOAT32_TINY = np.finfo(np.float32).tiny
# Useful to void division by zero or divergence to +inf.
cdef float FLOAT64_EPS = np.finfo(np.float64).eps
# This is effectively an ifdef statement in Cython
# It allows us to write printf debugging lines
# and remove them at compile time
cdef enum:
DEBUGFLAG = 0
cdef float compute_gradient(float[:] val_P,
float[:, :] pos_reference,
cnp.int64_t[:] neighbors,
cnp.int64_t[:] indptr,
float[:, :] tot_force,
_QuadTree qt,
float theta,
int dof,
long start,
bint compute_error,
int num_threads) noexcept nogil:
# Having created the tree, calculate the gradient
# in two components, the positive and negative forces
cdef:
long i, coord
int ax
long n_samples = pos_reference.shape[0]
int n_dimensions = qt.n_dimensions
clock_t t1 = 0, t2 = 0
double sQ
float error
int take_timing = 1 if qt.verbose > 15 else 0
if qt.verbose > 11:
printf("[t-SNE] Allocating %li elements in force arrays\n",
n_samples * n_dimensions * 2)
cdef float* neg_f = <float*> malloc(sizeof(float) * n_samples * n_dimensions)
cdef float* pos_f = <float*> malloc(sizeof(float) * n_samples * n_dimensions)
if take_timing:
t1 = clock()
sQ = compute_gradient_negative(pos_reference, neg_f, qt, dof, theta, start,
num_threads)
if take_timing:
t2 = clock()
printf("[t-SNE] Computing negative gradient: %e ticks\n", ((float) (t2 - t1)))
if take_timing:
t1 = clock()
error = compute_gradient_positive(val_P, pos_reference, neighbors, indptr,
pos_f, n_dimensions, dof, sQ, start,
qt.verbose, compute_error, num_threads)
if take_timing:
t2 = clock()
printf("[t-SNE] Computing positive gradient: %e ticks\n",
((float) (t2 - t1)))
for i in prange(start, n_samples, nogil=True, num_threads=num_threads,
schedule='static'):
for ax in range(n_dimensions):
coord = i * n_dimensions + ax
tot_force[i, ax] = pos_f[coord] - (neg_f[coord] / sQ)
free(neg_f)
free(pos_f)
return error
cdef float compute_gradient_positive(float[:] val_P,
float[:, :] pos_reference,
cnp.int64_t[:] neighbors,
cnp.int64_t[:] indptr,
float* pos_f,
int n_dimensions,
int dof,
double sum_Q,
cnp.int64_t start,
int verbose,
bint compute_error,
int num_threads) noexcept nogil:
# Sum over the following expression for i not equal to j
# grad_i = p_ij (1 + ||y_i - y_j||^2)^-1 (y_i - y_j)
# This is equivalent to compute_edge_forces in the authors' code
# It just goes over the nearest neighbors instead of all the data points
# (unlike the non-nearest neighbors version of `compute_gradient_positive')
cdef:
int ax
long i, j, k
long n_samples = indptr.shape[0] - 1
float C = 0.0
float dij, qij, pij
float exponent = (dof + 1.0) / 2.0
float float_dof = (float) (dof)
float* buff
clock_t t1 = 0, t2 = 0
float dt
if verbose > 10:
t1 = clock()
with nogil, parallel(num_threads=num_threads):
# Define private buffer variables
buff = <float *> malloc(sizeof(float) * n_dimensions)
for i in prange(start, n_samples, schedule='static'):
# Init the gradient vector
for ax in range(n_dimensions):
pos_f[i * n_dimensions + ax] = 0.0
# Compute the positive interaction for the nearest neighbors
for k in range(indptr[i], indptr[i+1]):
j = neighbors[k]
dij = 0.0
pij = val_P[k]
for ax in range(n_dimensions):
buff[ax] = pos_reference[i, ax] - pos_reference[j, ax]
dij += buff[ax] * buff[ax]
qij = float_dof / (float_dof + dij)
if dof != 1: # i.e. exponent != 1
qij = qij ** exponent
dij = pij * qij
# only compute the error when needed
if compute_error:
qij = qij / sum_Q
C += pij * log(max(pij, FLOAT32_TINY) / max(qij, FLOAT32_TINY))
for ax in range(n_dimensions):
pos_f[i * n_dimensions + ax] += dij * buff[ax]
free(buff)
if verbose > 10:
t2 = clock()
dt = ((float) (t2 - t1))
printf("[t-SNE] Computed error=%1.4f in %1.1e ticks\n", C, dt)
return C
cdef double compute_gradient_negative(float[:, :] pos_reference,
float* neg_f,
_QuadTree qt,
int dof,
float theta,
long start,
int num_threads) noexcept nogil:
cdef:
int ax
int n_dimensions = qt.n_dimensions
int offset = n_dimensions + 2
long i, j, idx
long n_samples = pos_reference.shape[0]
long n = n_samples - start
long dta = 0
long dtb = 0
float size, dist2s, mult
float exponent = (dof + 1.0) / 2.0
float float_dof = (float) (dof)
double qijZ, sum_Q = 0.0
float* force
float* neg_force
float* pos
clock_t t1 = 0, t2 = 0, t3 = 0
int take_timing = 1 if qt.verbose > 20 else 0
with nogil, parallel(num_threads=num_threads):
# Define thread-local buffers
summary = <float*> malloc(sizeof(float) * n * offset)
pos = <float *> malloc(sizeof(float) * n_dimensions)
force = <float *> malloc(sizeof(float) * n_dimensions)
neg_force = <float *> malloc(sizeof(float) * n_dimensions)
for i in prange(start, n_samples, schedule='static'):
# Clear the arrays
for ax in range(n_dimensions):
force[ax] = 0.0
neg_force[ax] = 0.0
pos[ax] = pos_reference[i, ax]
# Find which nodes are summarizing and collect their centers of mass
# deltas, and sizes, into vectorized arrays
if take_timing:
t1 = clock()
idx = qt.summarize(pos, summary, theta*theta)
if take_timing:
t2 = clock()
# Compute the t-SNE negative force
# for the digits dataset, walking the tree
# is about 10-15x more expensive than the
# following for loop
for j in range(idx // offset):
dist2s = summary[j * offset + n_dimensions]
size = summary[j * offset + n_dimensions + 1]
qijZ = float_dof / (float_dof + dist2s) # 1/(1+dist)
if dof != 1: # i.e. exponent != 1
qijZ = qijZ ** exponent
sum_Q += size * qijZ # size of the node * q
mult = size * qijZ * qijZ
for ax in range(n_dimensions):
neg_force[ax] += mult * summary[j * offset + ax]
if take_timing:
t3 = clock()
for ax in range(n_dimensions):
neg_f[i * n_dimensions + ax] = neg_force[ax]
if take_timing:
dta += t2 - t1
dtb += t3 - t2
free(pos)
free(force)
free(neg_force)
free(summary)
if take_timing:
printf("[t-SNE] Tree: %li clock ticks | ", dta)
printf("Force computation: %li clock ticks\n", dtb)
# Put sum_Q to machine EPSILON to avoid divisions by 0
sum_Q = max(sum_Q, FLOAT64_EPS)
return sum_Q
def gradient(float[:] val_P,
float[:, :] pos_output,
cnp.int64_t[:] neighbors,
cnp.int64_t[:] indptr,
float[:, :] forces,
float theta,
int n_dimensions,
int verbose,
int dof=1,
long skip_num_points=0,
bint compute_error=1,
int num_threads=1):
# This function is designed to be called from external Python
# it passes the 'forces' array by reference and fills that's array
# up in-place
cdef float C
cdef int n
n = pos_output.shape[0]
assert val_P.itemsize == 4
assert pos_output.itemsize == 4
assert forces.itemsize == 4
m = "Forces array and pos_output shapes are incompatible"
assert n == forces.shape[0], m
m = "Pij and pos_output shapes are incompatible"
assert n == indptr.shape[0] - 1, m
if verbose > 10:
printf("[t-SNE] Initializing tree of n_dimensions %i\n", n_dimensions)
cdef _QuadTree qt = _QuadTree(pos_output.shape[1], verbose)
if verbose > 10:
printf("[t-SNE] Inserting %li points\n", pos_output.shape[0])
qt.build_tree(pos_output)
if verbose > 10:
# XXX: format hack to workaround lack of `const char *` type
# in the generated C code that triggers error with gcc 4.9
# and -Werror=format-security
printf("[t-SNE] Computing gradient\n%s", EMPTY_STRING)
C = compute_gradient(val_P, pos_output, neighbors, indptr, forces,
qt, theta, dof, skip_num_points, compute_error,
num_threads)
if verbose > 10:
# XXX: format hack to workaround lack of `const char *` type
# in the generated C code
# and -Werror=format-security
printf("[t-SNE] Checking tree consistency\n%s", EMPTY_STRING)
m = "Tree consistency failed: unexpected number of points on the tree"
assert qt.cells[0].cumulative_size == qt.n_points, m
if not compute_error:
C = np.nan
return C
@@ -1,120 +1,120 @@
import numpy as np
from libc cimport math
from libc.math cimport INFINITY
from ..utils._typedefs cimport float32_t, float64_t
cdef float EPSILON_DBL = 1e-8
cdef float PERPLEXITY_TOLERANCE = 1e-5
# TODO: have this function support float32 and float64 and preserve inputs' dtypes.
def _binary_search_perplexity(
const float32_t[:, :] sqdistances,
float desired_perplexity,
int verbose):
"""Binary search for sigmas of conditional Gaussians.
This approximation reduces the computational complexity from O(N^2) to
O(uN).
Parameters
----------
sqdistances : ndarray of shape (n_samples, n_neighbors), dtype=np.float32
Distances between training samples and their k nearest neighbors.
When using the exact method, this is a square (n_samples, n_samples)
distance matrix. The TSNE default metric is "euclidean" which is
interpreted as squared euclidean distance.
desired_perplexity : float
Desired perplexity (2^entropy) of the conditional Gaussians.
verbose : int
Verbosity level.
Returns
-------
P : ndarray of shape (n_samples, n_samples), dtype=np.float64
Probabilities of conditional Gaussian distributions p_i|j.
"""
# Maximum number of binary search steps
cdef long n_steps = 100
cdef long n_samples = sqdistances.shape[0]
cdef long n_neighbors = sqdistances.shape[1]
cdef int using_neighbors = n_neighbors < n_samples
# Precisions of conditional Gaussian distributions
cdef double beta
cdef double beta_min
cdef double beta_max
cdef double beta_sum = 0.0
# Use log scale
cdef double desired_entropy = math.log(desired_perplexity)
cdef double entropy_diff
cdef double entropy
cdef double sum_Pi
cdef double sum_disti_Pi
cdef long i, j, l
# This array is later used as a 32bit array. It has multiple intermediate
# floating point additions that benefit from the extra precision
cdef float64_t[:, :] P = np.zeros(
(n_samples, n_neighbors), dtype=np.float64)
for i in range(n_samples):
beta_min = -INFINITY
beta_max = INFINITY
beta = 1.0
# Binary search of precision for i-th conditional distribution
for l in range(n_steps):
# Compute current entropy and corresponding probabilities
# computed just over the nearest neighbors or over all data
# if we're not using neighbors
sum_Pi = 0.0
for j in range(n_neighbors):
if j != i or using_neighbors:
P[i, j] = math.exp(-sqdistances[i, j] * beta)
sum_Pi += P[i, j]
if sum_Pi == 0.0:
sum_Pi = EPSILON_DBL
sum_disti_Pi = 0.0
for j in range(n_neighbors):
P[i, j] /= sum_Pi
sum_disti_Pi += sqdistances[i, j] * P[i, j]
entropy = math.log(sum_Pi) + beta * sum_disti_Pi
entropy_diff = entropy - desired_entropy
if math.fabs(entropy_diff) <= PERPLEXITY_TOLERANCE:
break
if entropy_diff > 0.0:
beta_min = beta
if beta_max == INFINITY:
beta *= 2.0
else:
beta = (beta + beta_max) / 2.0
else:
beta_max = beta
if beta_min == -INFINITY:
beta /= 2.0
else:
beta = (beta + beta_min) / 2.0
beta_sum += beta
if verbose and ((i + 1) % 1000 == 0 or i + 1 == n_samples):
print("[t-SNE] Computed conditional probabilities for sample "
"%d / %d" % (i + 1, n_samples))
if verbose:
print("[t-SNE] Mean sigma: %f"
% np.mean(math.sqrt(n_samples / beta_sum)))
return np.asarray(P)
import numpy as np
from libc cimport math
from libc.math cimport INFINITY
from ..utils._typedefs cimport float32_t, float64_t
cdef float EPSILON_DBL = 1e-8
cdef float PERPLEXITY_TOLERANCE = 1e-5
# TODO: have this function support float32 and float64 and preserve inputs' dtypes.
def _binary_search_perplexity(
const float32_t[:, :] sqdistances,
float desired_perplexity,
int verbose):
"""Binary search for sigmas of conditional Gaussians.
This approximation reduces the computational complexity from O(N^2) to
O(uN).
Parameters
----------
sqdistances : ndarray of shape (n_samples, n_neighbors), dtype=np.float32
Distances between training samples and their k nearest neighbors.
When using the exact method, this is a square (n_samples, n_samples)
distance matrix. The TSNE default metric is "euclidean" which is
interpreted as squared euclidean distance.
desired_perplexity : float
Desired perplexity (2^entropy) of the conditional Gaussians.
verbose : int
Verbosity level.
Returns
-------
P : ndarray of shape (n_samples, n_samples), dtype=np.float64
Probabilities of conditional Gaussian distributions p_i|j.
"""
# Maximum number of binary search steps
cdef long n_steps = 100
cdef long n_samples = sqdistances.shape[0]
cdef long n_neighbors = sqdistances.shape[1]
cdef int using_neighbors = n_neighbors < n_samples
# Precisions of conditional Gaussian distributions
cdef double beta
cdef double beta_min
cdef double beta_max
cdef double beta_sum = 0.0
# Use log scale
cdef double desired_entropy = math.log(desired_perplexity)
cdef double entropy_diff
cdef double entropy
cdef double sum_Pi
cdef double sum_disti_Pi
cdef long i, j, l
# This array is later used as a 32bit array. It has multiple intermediate
# floating point additions that benefit from the extra precision
cdef float64_t[:, :] P = np.zeros(
(n_samples, n_neighbors), dtype=np.float64)
for i in range(n_samples):
beta_min = -INFINITY
beta_max = INFINITY
beta = 1.0
# Binary search of precision for i-th conditional distribution
for l in range(n_steps):
# Compute current entropy and corresponding probabilities
# computed just over the nearest neighbors or over all data
# if we're not using neighbors
sum_Pi = 0.0
for j in range(n_neighbors):
if j != i or using_neighbors:
P[i, j] = math.exp(-sqdistances[i, j] * beta)
sum_Pi += P[i, j]
if sum_Pi == 0.0:
sum_Pi = EPSILON_DBL
sum_disti_Pi = 0.0
for j in range(n_neighbors):
P[i, j] /= sum_Pi
sum_disti_Pi += sqdistances[i, j] * P[i, j]
entropy = math.log(sum_Pi) + beta * sum_disti_Pi
entropy_diff = entropy - desired_entropy
if math.fabs(entropy_diff) <= PERPLEXITY_TOLERANCE:
break
if entropy_diff > 0.0:
beta_min = beta
if beta_max == INFINITY:
beta *= 2.0
else:
beta = (beta + beta_max) / 2.0
else:
beta_max = beta
if beta_min == -INFINITY:
beta /= 2.0
else:
beta = (beta + beta_min) / 2.0
beta_sum += beta
if verbose and ((i + 1) % 1000 == 0 or i + 1 == n_samples):
print("[t-SNE] Computed conditional probabilities for sample "
"%d / %d" % (i + 1, n_samples))
if verbose:
print("[t-SNE] Mean sigma: %f"
% np.mean(math.sqrt(n_samples / beta_sum)))
return np.asarray(P)
@@ -1,14 +1,14 @@
py.extension_module(
'_utils',
[cython_gen.process('_utils.pyx'), utils_cython_tree],
subdir: 'sklearn/manifold',
install: true
)
py.extension_module(
'_barnes_hut_tsne',
cython_gen.process('_barnes_hut_tsne.pyx'),
dependencies: [np_dep, openmp_dep],
subdir: 'sklearn/manifold',
install: true
)
py.extension_module(
'_utils',
[cython_gen.process('_utils.pyx'), utils_cython_tree],
subdir: 'sklearn/manifold',
install: true
)
py.extension_module(
'_barnes_hut_tsne',
cython_gen.process('_barnes_hut_tsne.pyx'),
dependencies: [np_dep, openmp_dep],
subdir: 'sklearn/manifold',
install: true
)
@@ -1,273 +1,273 @@
fs = import('fs')
cython_args = []
# Platform detection
is_windows = host_machine.system() == 'windows'
is_mingw = is_windows and cc.get_id() == 'gcc'
# Adapted from Scipy. mingw is untested and not officially supported. If you
# ever bump into issues when trying to compile for mingw, please open an issue
# in the scikit-learn issue tracker
if is_mingw
# For mingw-w64, link statically against the UCRT.
gcc_link_args = ['-lucrt', '-static']
add_project_link_arguments(gcc_link_args, language: ['c', 'cpp'])
# Force gcc to float64 long doubles for compatibility with MSVC
# builds, for C only.
add_project_arguments('-mlong-double-64', language: 'c')
endif
# Only check build dependencies version when not cross-compiling, as running
# Python interpreter can be tricky in cross-compilation settings. For more
# details, see https://docs.scipy.org/doc/scipy/building/cross_compilation.html
if not meson.is_cross_build()
if not py.version().version_compare('>=3.10')
error('scikit-learn requires Python>=3.10, got ' + py.version() + ' instead')
endif
cython_min_version = run_command(py, ['_min_dependencies.py', 'cython'], check: true).stdout().strip()
if not cython.version().version_compare('>=' + cython_min_version)
error('scikit-learn requires Cython>=' + cython_min_version + ', got ' + cython.version() + ' instead')
endif
numpy_version = run_command(py,
['-c', 'import numpy; print(numpy.__version__)'], check: true).stdout().strip()
numpy_min_version = run_command(py, ['_min_dependencies.py', 'numpy'], check: true).stdout().strip()
if not numpy_version.version_compare('>=' + numpy_min_version)
error('scikit-learn requires numpy>=' + numpy_min_version + ', got ' + numpy_version + ' instead')
endif
scipy_version = run_command(py,
['-c', 'import scipy; print(scipy.__version__)'], check: true).stdout().strip()
scipy_min_version = run_command(py, ['_min_dependencies.py', 'scipy'], check: true).stdout().strip()
if not scipy_version.version_compare('>=' + scipy_min_version)
error('scikit-learn requires scipy>=' + scipy_min_version + ', got ' + scipy_version + ' instead')
endif
# meson-python is required only when going through pip. Using meson directly
# should not check meson-python version.
meson_python_version_command_result = run_command(py,
['-c', 'import importlib.metadata; print(importlib.metadata.version("meson-python"))'], check: false)
meson_python_installed = meson_python_version_command_result.returncode() == 0
if meson_python_installed
meson_python_version = meson_python_version_command_result.stdout().strip()
meson_python_min_version = run_command(py, ['_min_dependencies.py', 'meson-python'], check: true).stdout().strip()
if not meson_python_version.version_compare('>=' + meson_python_min_version)
error('scikit-learn requires meson-python>=' + meson_python_min_version + ', got ' + meson_python_version + ' instead')
endif
endif
endif
# Adapted from scipy, each project seems to have its own tweaks for this. One
# day using dependency('numpy') will be a thing, see
# https://github.com/mesonbuild/meson/issues/9598.
# NumPy include directory - needed in all submodules
# Relative paths are needed when for example a virtualenv is
# placed inside the source tree; Meson rejects absolute paths to places inside
# the source tree. The try-except is needed because when things are split
# across drives on Windows, there is no relative path and an exception gets
# raised. There may be other such cases, so add a catch-all and switch to
# an absolute path.
# For cross-compilation it is often not possible to run the Python interpreter
# in order to retrieve numpy's include directory. It can be specified in the
# cross file instead:
# [properties]
# numpy-include-dir = /abspath/to/host-pythons/site-packages/numpy/core/include
#
# This uses the path as is, and avoids running the interpreter.
incdir_numpy = meson.get_external_property('numpy-include-dir', 'not-given')
if incdir_numpy == 'not-given'
incdir_numpy = run_command(py,
[
'-c',
'''
import os
import numpy as np
try:
incdir = os.path.relpath(np.get_include())
except Exception:
incdir = np.get_include()
print(incdir)
'''
],
check: true
).stdout().strip()
endif
inc_np = include_directories(incdir_numpy)
# Don't use the deprecated NumPy C API. Define this to a fixed version instead of
# NPY_API_VERSION in order not to break compilation for released SciPy versions
# when NumPy introduces a new deprecation.
numpy_no_deprecated_api = ['-DNPY_NO_DEPRECATED_API=NPY_1_9_API_VERSION']
np_dep = declare_dependency(include_directories: inc_np, compile_args: numpy_no_deprecated_api)
openmp_dep = dependency('OpenMP', language: 'c', required: false)
if not openmp_dep.found()
warn_about_missing_openmp = true
# On Apple Clang avoid a misleading warning if compiler variables are set.
# See https://github.com/scikit-learn/scikit-learn/issues/28710 for more
# details. This may be removed if the OpenMP detection on Apple Clang improves,
# see https://github.com/mesonbuild/meson/issues/7435#issuecomment-2047585466.
if host_machine.system() == 'darwin' and cc.get_id() == 'clang'
compiler_env_vars_with_openmp = run_command(py,
[
'-c',
'''
import os
compiler_env_vars_to_check = ["CPPFLAGS", "CFLAGS", "CXXFLAGS"]
compiler_env_vars_with_openmp = [
var for var in compiler_env_vars_to_check if "-fopenmp" in os.getenv(var, "")]
print(compiler_env_vars_with_openmp)
'''], check: true).stdout().strip()
warn_about_missing_openmp = compiler_env_vars_with_openmp == '[]'
endif
if warn_about_missing_openmp
warning(
'''
***********
* WARNING *
***********
It seems that scikit-learn cannot be built with OpenMP.
- Make sure you have followed the installation instructions:
https://scikit-learn.org/dev/developers/advanced_installation.html
- If your compiler supports OpenMP but you still see this
message, please submit a bug report at:
https://github.com/scikit-learn/scikit-learn/issues
- The build will continue with OpenMP-based parallelism
disabled. Note however that some estimators will run in
sequential mode instead of leveraging thread-based
parallelism.
***
''')
else
warning(
'''It looks like compiler environment variables were set to enable OpenMP support.
Check the output of "import sklearn; sklearn.show_versions()" after the build
to make sure that scikit-learn was actually built with OpenMP support.
''')
endif
endif
# For now, we keep supporting SKLEARN_ENABLE_DEBUG_CYTHON_DIRECTIVES variable
# (see how it is done in sklearn/_build_utils/__init__.py when building with
# setuptools). Accessing environment variables in meson.build is discouraged,
# so once we drop setuptools this functionality should be behind a meson option
# or buildtype
boundscheck = run_command(py,
[
'-c',
'''
import os
if os.environ.get("SKLEARN_ENABLE_DEBUG_CYTHON_DIRECTIVES", "0") != "0":
print(True)
else:
print(False)
'''
],
check: true
).stdout().strip()
cython_program = find_program(cython.cmd_array()[0])
scikit_learn_cython_args = [
'-X language_level=3', '-X boundscheck=' + boundscheck, '-X wraparound=False',
'-X initializedcheck=False', '-X nonecheck=False', '-X cdivision=True',
'-X profile=False',
# Needed for cython imports across subpackages, e.g. cluster pyx that
# cimports metrics pxd
'--include-dir', meson.global_build_root(),
]
cython_args += scikit_learn_cython_args
if cython.version().version_compare('>=3.1.0')
cython_shared_src = custom_target(
install: false,
output: '_cyutility.c',
command: [
cython_program, '-3', '--fast-fail',
'--generate-shared=' + meson.current_build_dir()/'_cyutility.c'
],
)
py.extension_module('_cyutility',
cython_shared_src,
subdir: 'sklearn',
cython_args: cython_args,
install: true,
)
cython_args += ['--shared=sklearn._cyutility']
endif
cython_gen = generator(cython_program,
arguments : cython_args + ['@INPUT@', '--output-file', '@OUTPUT@'],
output : '@BASENAME@.c',
)
cython_gen_cpp = generator(cython_program,
arguments : cython_args + ['--cplus', '@INPUT@', '--output-file', '@OUTPUT@'],
output : '@BASENAME@.cpp',
)
# Write file in Meson build dir to be able to figure out from Python code
# whether scikit-learn was built with Meson. Adapted from pandas
# _version_meson.py.
custom_target('write_built_with_meson_file',
output: '_built_with_meson.py',
command: [
py, '-c', 'with open("sklearn/_built_with_meson.py", "w") as f: f.write("")'
],
install: true,
install_dir: py.get_install_dir() / 'sklearn'
)
extensions = ['_isotonic']
py.extension_module(
'_isotonic',
cython_gen.process('_isotonic.pyx'),
cython_args: cython_args,
install: true,
subdir: 'sklearn',
)
# Need for Cython cimports across subpackages to work, i.e. avoid errors like
# relative cimport from non-package directory is not allowed
sklearn_root_cython_tree = [
fs.copyfile('__init__.py')
]
sklearn_dir = py.get_install_dir() / 'sklearn'
# Subpackages are mostly in alphabetical order except to handle Cython
# dependencies across subpackages
subdir('__check_build')
subdir('_loss')
# utils needs to be early since plenty of other modules cimports utils .pxd
subdir('utils')
# metrics needs to be to be before cluster since cluster cimports metrics .pxd
subdir('metrics')
subdir('cluster')
subdir('datasets')
subdir('decomposition')
subdir('ensemble')
subdir('feature_extraction')
subdir('linear_model')
subdir('manifold')
subdir('neighbors')
subdir('preprocessing')
subdir('svm')
subdir('tree')
fs = import('fs')
cython_args = []
# Platform detection
is_windows = host_machine.system() == 'windows'
is_mingw = is_windows and cc.get_id() == 'gcc'
# Adapted from Scipy. mingw is untested and not officially supported. If you
# ever bump into issues when trying to compile for mingw, please open an issue
# in the scikit-learn issue tracker
if is_mingw
# For mingw-w64, link statically against the UCRT.
gcc_link_args = ['-lucrt', '-static']
add_project_link_arguments(gcc_link_args, language: ['c', 'cpp'])
# Force gcc to float64 long doubles for compatibility with MSVC
# builds, for C only.
add_project_arguments('-mlong-double-64', language: 'c')
endif
# Only check build dependencies version when not cross-compiling, as running
# Python interpreter can be tricky in cross-compilation settings. For more
# details, see https://docs.scipy.org/doc/scipy/building/cross_compilation.html
if not meson.is_cross_build()
if not py.version().version_compare('>=3.10')
error('scikit-learn requires Python>=3.10, got ' + py.version() + ' instead')
endif
cython_min_version = run_command(py, ['_min_dependencies.py', 'cython'], check: true).stdout().strip()
if not cython.version().version_compare('>=' + cython_min_version)
error('scikit-learn requires Cython>=' + cython_min_version + ', got ' + cython.version() + ' instead')
endif
numpy_version = run_command(py,
['-c', 'import numpy; print(numpy.__version__)'], check: true).stdout().strip()
numpy_min_version = run_command(py, ['_min_dependencies.py', 'numpy'], check: true).stdout().strip()
if not numpy_version.version_compare('>=' + numpy_min_version)
error('scikit-learn requires numpy>=' + numpy_min_version + ', got ' + numpy_version + ' instead')
endif
scipy_version = run_command(py,
['-c', 'import scipy; print(scipy.__version__)'], check: true).stdout().strip()
scipy_min_version = run_command(py, ['_min_dependencies.py', 'scipy'], check: true).stdout().strip()
if not scipy_version.version_compare('>=' + scipy_min_version)
error('scikit-learn requires scipy>=' + scipy_min_version + ', got ' + scipy_version + ' instead')
endif
# meson-python is required only when going through pip. Using meson directly
# should not check meson-python version.
meson_python_version_command_result = run_command(py,
['-c', 'import importlib.metadata; print(importlib.metadata.version("meson-python"))'], check: false)
meson_python_installed = meson_python_version_command_result.returncode() == 0
if meson_python_installed
meson_python_version = meson_python_version_command_result.stdout().strip()
meson_python_min_version = run_command(py, ['_min_dependencies.py', 'meson-python'], check: true).stdout().strip()
if not meson_python_version.version_compare('>=' + meson_python_min_version)
error('scikit-learn requires meson-python>=' + meson_python_min_version + ', got ' + meson_python_version + ' instead')
endif
endif
endif
# Adapted from scipy, each project seems to have its own tweaks for this. One
# day using dependency('numpy') will be a thing, see
# https://github.com/mesonbuild/meson/issues/9598.
# NumPy include directory - needed in all submodules
# Relative paths are needed when for example a virtualenv is
# placed inside the source tree; Meson rejects absolute paths to places inside
# the source tree. The try-except is needed because when things are split
# across drives on Windows, there is no relative path and an exception gets
# raised. There may be other such cases, so add a catch-all and switch to
# an absolute path.
# For cross-compilation it is often not possible to run the Python interpreter
# in order to retrieve numpy's include directory. It can be specified in the
# cross file instead:
# [properties]
# numpy-include-dir = /abspath/to/host-pythons/site-packages/numpy/core/include
#
# This uses the path as is, and avoids running the interpreter.
incdir_numpy = meson.get_external_property('numpy-include-dir', 'not-given')
if incdir_numpy == 'not-given'
incdir_numpy = run_command(py,
[
'-c',
'''
import os
import numpy as np
try:
incdir = os.path.relpath(np.get_include())
except Exception:
incdir = np.get_include()
print(incdir)
'''
],
check: true
).stdout().strip()
endif
inc_np = include_directories(incdir_numpy)
# Don't use the deprecated NumPy C API. Define this to a fixed version instead of
# NPY_API_VERSION in order not to break compilation for released SciPy versions
# when NumPy introduces a new deprecation.
numpy_no_deprecated_api = ['-DNPY_NO_DEPRECATED_API=NPY_1_9_API_VERSION']
np_dep = declare_dependency(include_directories: inc_np, compile_args: numpy_no_deprecated_api)
openmp_dep = dependency('OpenMP', language: 'c', required: false)
if not openmp_dep.found()
warn_about_missing_openmp = true
# On Apple Clang avoid a misleading warning if compiler variables are set.
# See https://github.com/scikit-learn/scikit-learn/issues/28710 for more
# details. This may be removed if the OpenMP detection on Apple Clang improves,
# see https://github.com/mesonbuild/meson/issues/7435#issuecomment-2047585466.
if host_machine.system() == 'darwin' and cc.get_id() == 'clang'
compiler_env_vars_with_openmp = run_command(py,
[
'-c',
'''
import os
compiler_env_vars_to_check = ["CPPFLAGS", "CFLAGS", "CXXFLAGS"]
compiler_env_vars_with_openmp = [
var for var in compiler_env_vars_to_check if "-fopenmp" in os.getenv(var, "")]
print(compiler_env_vars_with_openmp)
'''], check: true).stdout().strip()
warn_about_missing_openmp = compiler_env_vars_with_openmp == '[]'
endif
if warn_about_missing_openmp
warning(
'''
***********
* WARNING *
***********
It seems that scikit-learn cannot be built with OpenMP.
- Make sure you have followed the installation instructions:
https://scikit-learn.org/dev/developers/advanced_installation.html
- If your compiler supports OpenMP but you still see this
message, please submit a bug report at:
https://github.com/scikit-learn/scikit-learn/issues
- The build will continue with OpenMP-based parallelism
disabled. Note however that some estimators will run in
sequential mode instead of leveraging thread-based
parallelism.
***
''')
else
warning(
'''It looks like compiler environment variables were set to enable OpenMP support.
Check the output of "import sklearn; sklearn.show_versions()" after the build
to make sure that scikit-learn was actually built with OpenMP support.
''')
endif
endif
# For now, we keep supporting SKLEARN_ENABLE_DEBUG_CYTHON_DIRECTIVES variable
# (see how it is done in sklearn/_build_utils/__init__.py when building with
# setuptools). Accessing environment variables in meson.build is discouraged,
# so once we drop setuptools this functionality should be behind a meson option
# or buildtype
boundscheck = run_command(py,
[
'-c',
'''
import os
if os.environ.get("SKLEARN_ENABLE_DEBUG_CYTHON_DIRECTIVES", "0") != "0":
print(True)
else:
print(False)
'''
],
check: true
).stdout().strip()
cython_program = find_program(cython.cmd_array()[0])
scikit_learn_cython_args = [
'-X language_level=3', '-X boundscheck=' + boundscheck, '-X wraparound=False',
'-X initializedcheck=False', '-X nonecheck=False', '-X cdivision=True',
'-X profile=False',
# Needed for cython imports across subpackages, e.g. cluster pyx that
# cimports metrics pxd
'--include-dir', meson.global_build_root(),
]
cython_args += scikit_learn_cython_args
if cython.version().version_compare('>=3.1.0')
cython_shared_src = custom_target(
install: false,
output: '_cyutility.c',
command: [
cython_program, '-3', '--fast-fail',
'--generate-shared=' + meson.current_build_dir()/'_cyutility.c'
],
)
py.extension_module('_cyutility',
cython_shared_src,
subdir: 'sklearn',
cython_args: cython_args,
install: true,
)
cython_args += ['--shared=sklearn._cyutility']
endif
cython_gen = generator(cython_program,
arguments : cython_args + ['@INPUT@', '--output-file', '@OUTPUT@'],
output : '@BASENAME@.c',
)
cython_gen_cpp = generator(cython_program,
arguments : cython_args + ['--cplus', '@INPUT@', '--output-file', '@OUTPUT@'],
output : '@BASENAME@.cpp',
)
# Write file in Meson build dir to be able to figure out from Python code
# whether scikit-learn was built with Meson. Adapted from pandas
# _version_meson.py.
custom_target('write_built_with_meson_file',
output: '_built_with_meson.py',
command: [
py, '-c', 'with open("sklearn/_built_with_meson.py", "w") as f: f.write("")'
],
install: true,
install_dir: py.get_install_dir() / 'sklearn'
)
extensions = ['_isotonic']
py.extension_module(
'_isotonic',
cython_gen.process('_isotonic.pyx'),
cython_args: cython_args,
install: true,
subdir: 'sklearn',
)
# Need for Cython cimports across subpackages to work, i.e. avoid errors like
# relative cimport from non-package directory is not allowed
sklearn_root_cython_tree = [
fs.copyfile('__init__.py')
]
sklearn_dir = py.get_install_dir() / 'sklearn'
# Subpackages are mostly in alphabetical order except to handle Cython
# dependencies across subpackages
subdir('__check_build')
subdir('_loss')
# utils needs to be early since plenty of other modules cimports utils .pxd
subdir('utils')
# metrics needs to be to be before cluster since cluster cimports metrics .pxd
subdir('metrics')
subdir('cluster')
subdir('datasets')
subdir('decomposition')
subdir('ensemble')
subdir('feature_extraction')
subdir('linear_model')
subdir('manifold')
subdir('neighbors')
subdir('preprocessing')
subdir('svm')
subdir('tree')
@@ -1,268 +1,268 @@
from libc.math cimport sqrt, exp
from ..utils._typedefs cimport float64_t, float32_t, int32_t, intp_t
cdef class DistanceMetric:
pass
######################################################################
# Inline distance functions
#
# We use these for the default (euclidean) case so that they can be
# inlined. This leads to faster computation for the most common case
cdef inline float64_t euclidean_dist64(
const float64_t* x1,
const float64_t* x2,
intp_t size,
) except -1 nogil:
cdef float64_t tmp, d=0
cdef intp_t j
for j in range(size):
tmp = <float64_t> (x1[j] - x2[j])
d += tmp * tmp
return sqrt(d)
cdef inline float64_t euclidean_rdist64(
const float64_t* x1,
const float64_t* x2,
intp_t size,
) except -1 nogil:
cdef float64_t tmp, d=0
cdef intp_t j
for j in range(size):
tmp = <float64_t>(x1[j] - x2[j])
d += tmp * tmp
return d
cdef inline float64_t euclidean_dist_to_rdist64(const float64_t dist) except -1 nogil:
return dist * dist
cdef inline float64_t euclidean_rdist_to_dist64(const float64_t dist) except -1 nogil:
return sqrt(dist)
######################################################################
# DistanceMetric64 base class
cdef class DistanceMetric64(DistanceMetric):
# The following attributes are required for a few of the subclasses.
# we must define them here so that cython's limited polymorphism will work.
# Because we don't expect to instantiate a lot of these objects, the
# extra memory overhead of this setup should not be an issue.
cdef float64_t p
cdef const float64_t[::1] vec
cdef const float64_t[:, ::1] mat
cdef intp_t size
cdef object func
cdef object kwargs
cdef float64_t dist(
self,
const float64_t* x1,
const float64_t* x2,
intp_t size,
) except -1 nogil
cdef float64_t rdist(
self,
const float64_t* x1,
const float64_t* x2,
intp_t size,
) except -1 nogil
cdef float64_t dist_csr(
self,
const float64_t* x1_data,
const int32_t* x1_indices,
const float64_t* x2_data,
const int32_t* x2_indices,
const int32_t x1_start,
const int32_t x1_end,
const int32_t x2_start,
const int32_t x2_end,
const intp_t size,
) except -1 nogil
cdef float64_t rdist_csr(
self,
const float64_t* x1_data,
const int32_t* x1_indices,
const float64_t* x2_data,
const int32_t* x2_indices,
const int32_t x1_start,
const int32_t x1_end,
const int32_t x2_start,
const int32_t x2_end,
const intp_t size,
) except -1 nogil
cdef int pdist(
self,
const float64_t[:, ::1] X,
float64_t[:, ::1] D,
) except -1
cdef int cdist(
self,
const float64_t[:, ::1] X,
const float64_t[:, ::1] Y,
float64_t[:, ::1] D,
) except -1
cdef int pdist_csr(
self,
const float64_t* x1_data,
const int32_t[::1] x1_indices,
const int32_t[::1] x1_indptr,
const intp_t size,
float64_t[:, ::1] D,
) except -1 nogil
cdef int cdist_csr(
self,
const float64_t* x1_data,
const int32_t[::1] x1_indices,
const int32_t[::1] x1_indptr,
const float64_t* x2_data,
const int32_t[::1] x2_indices,
const int32_t[::1] x2_indptr,
const intp_t size,
float64_t[:, ::1] D,
) except -1 nogil
cdef float64_t _rdist_to_dist(self, float64_t rdist) except -1 nogil
cdef float64_t _dist_to_rdist(self, float64_t dist) except -1 nogil
######################################################################
# Inline distance functions
#
# We use these for the default (euclidean) case so that they can be
# inlined. This leads to faster computation for the most common case
cdef inline float64_t euclidean_dist32(
const float32_t* x1,
const float32_t* x2,
intp_t size,
) except -1 nogil:
cdef float64_t tmp, d=0
cdef intp_t j
for j in range(size):
tmp = <float64_t> (x1[j] - x2[j])
d += tmp * tmp
return sqrt(d)
cdef inline float64_t euclidean_rdist32(
const float32_t* x1,
const float32_t* x2,
intp_t size,
) except -1 nogil:
cdef float64_t tmp, d=0
cdef intp_t j
for j in range(size):
tmp = <float64_t>(x1[j] - x2[j])
d += tmp * tmp
return d
cdef inline float64_t euclidean_dist_to_rdist32(const float32_t dist) except -1 nogil:
return dist * dist
cdef inline float64_t euclidean_rdist_to_dist32(const float32_t dist) except -1 nogil:
return sqrt(dist)
######################################################################
# DistanceMetric32 base class
cdef class DistanceMetric32(DistanceMetric):
# The following attributes are required for a few of the subclasses.
# we must define them here so that cython's limited polymorphism will work.
# Because we don't expect to instantiate a lot of these objects, the
# extra memory overhead of this setup should not be an issue.
cdef float64_t p
cdef const float64_t[::1] vec
cdef const float64_t[:, ::1] mat
cdef intp_t size
cdef object func
cdef object kwargs
cdef float32_t dist(
self,
const float32_t* x1,
const float32_t* x2,
intp_t size,
) except -1 nogil
cdef float32_t rdist(
self,
const float32_t* x1,
const float32_t* x2,
intp_t size,
) except -1 nogil
cdef float32_t dist_csr(
self,
const float32_t* x1_data,
const int32_t* x1_indices,
const float32_t* x2_data,
const int32_t* x2_indices,
const int32_t x1_start,
const int32_t x1_end,
const int32_t x2_start,
const int32_t x2_end,
const intp_t size,
) except -1 nogil
cdef float32_t rdist_csr(
self,
const float32_t* x1_data,
const int32_t* x1_indices,
const float32_t* x2_data,
const int32_t* x2_indices,
const int32_t x1_start,
const int32_t x1_end,
const int32_t x2_start,
const int32_t x2_end,
const intp_t size,
) except -1 nogil
cdef int pdist(
self,
const float32_t[:, ::1] X,
float32_t[:, ::1] D,
) except -1
cdef int cdist(
self,
const float32_t[:, ::1] X,
const float32_t[:, ::1] Y,
float32_t[:, ::1] D,
) except -1
cdef int pdist_csr(
self,
const float32_t* x1_data,
const int32_t[::1] x1_indices,
const int32_t[::1] x1_indptr,
const intp_t size,
float32_t[:, ::1] D,
) except -1 nogil
cdef int cdist_csr(
self,
const float32_t* x1_data,
const int32_t[::1] x1_indices,
const int32_t[::1] x1_indptr,
const float32_t* x2_data,
const int32_t[::1] x2_indices,
const int32_t[::1] x2_indptr,
const intp_t size,
float32_t[:, ::1] D,
) except -1 nogil
cdef float32_t _rdist_to_dist(self, float32_t rdist) except -1 nogil
cdef float32_t _dist_to_rdist(self, float32_t dist) except -1 nogil
from libc.math cimport sqrt, exp
from ..utils._typedefs cimport float64_t, float32_t, int32_t, intp_t
cdef class DistanceMetric:
pass
######################################################################
# Inline distance functions
#
# We use these for the default (euclidean) case so that they can be
# inlined. This leads to faster computation for the most common case
cdef inline float64_t euclidean_dist64(
const float64_t* x1,
const float64_t* x2,
intp_t size,
) except -1 nogil:
cdef float64_t tmp, d=0
cdef intp_t j
for j in range(size):
tmp = <float64_t> (x1[j] - x2[j])
d += tmp * tmp
return sqrt(d)
cdef inline float64_t euclidean_rdist64(
const float64_t* x1,
const float64_t* x2,
intp_t size,
) except -1 nogil:
cdef float64_t tmp, d=0
cdef intp_t j
for j in range(size):
tmp = <float64_t>(x1[j] - x2[j])
d += tmp * tmp
return d
cdef inline float64_t euclidean_dist_to_rdist64(const float64_t dist) except -1 nogil:
return dist * dist
cdef inline float64_t euclidean_rdist_to_dist64(const float64_t dist) except -1 nogil:
return sqrt(dist)
######################################################################
# DistanceMetric64 base class
cdef class DistanceMetric64(DistanceMetric):
# The following attributes are required for a few of the subclasses.
# we must define them here so that cython's limited polymorphism will work.
# Because we don't expect to instantiate a lot of these objects, the
# extra memory overhead of this setup should not be an issue.
cdef float64_t p
cdef const float64_t[::1] vec
cdef const float64_t[:, ::1] mat
cdef intp_t size
cdef object func
cdef object kwargs
cdef float64_t dist(
self,
const float64_t* x1,
const float64_t* x2,
intp_t size,
) except -1 nogil
cdef float64_t rdist(
self,
const float64_t* x1,
const float64_t* x2,
intp_t size,
) except -1 nogil
cdef float64_t dist_csr(
self,
const float64_t* x1_data,
const int32_t* x1_indices,
const float64_t* x2_data,
const int32_t* x2_indices,
const int32_t x1_start,
const int32_t x1_end,
const int32_t x2_start,
const int32_t x2_end,
const intp_t size,
) except -1 nogil
cdef float64_t rdist_csr(
self,
const float64_t* x1_data,
const int32_t* x1_indices,
const float64_t* x2_data,
const int32_t* x2_indices,
const int32_t x1_start,
const int32_t x1_end,
const int32_t x2_start,
const int32_t x2_end,
const intp_t size,
) except -1 nogil
cdef int pdist(
self,
const float64_t[:, ::1] X,
float64_t[:, ::1] D,
) except -1
cdef int cdist(
self,
const float64_t[:, ::1] X,
const float64_t[:, ::1] Y,
float64_t[:, ::1] D,
) except -1
cdef int pdist_csr(
self,
const float64_t* x1_data,
const int32_t[::1] x1_indices,
const int32_t[::1] x1_indptr,
const intp_t size,
float64_t[:, ::1] D,
) except -1 nogil
cdef int cdist_csr(
self,
const float64_t* x1_data,
const int32_t[::1] x1_indices,
const int32_t[::1] x1_indptr,
const float64_t* x2_data,
const int32_t[::1] x2_indices,
const int32_t[::1] x2_indptr,
const intp_t size,
float64_t[:, ::1] D,
) except -1 nogil
cdef float64_t _rdist_to_dist(self, float64_t rdist) except -1 nogil
cdef float64_t _dist_to_rdist(self, float64_t dist) except -1 nogil
######################################################################
# Inline distance functions
#
# We use these for the default (euclidean) case so that they can be
# inlined. This leads to faster computation for the most common case
cdef inline float64_t euclidean_dist32(
const float32_t* x1,
const float32_t* x2,
intp_t size,
) except -1 nogil:
cdef float64_t tmp, d=0
cdef intp_t j
for j in range(size):
tmp = <float64_t> (x1[j] - x2[j])
d += tmp * tmp
return sqrt(d)
cdef inline float64_t euclidean_rdist32(
const float32_t* x1,
const float32_t* x2,
intp_t size,
) except -1 nogil:
cdef float64_t tmp, d=0
cdef intp_t j
for j in range(size):
tmp = <float64_t>(x1[j] - x2[j])
d += tmp * tmp
return d
cdef inline float64_t euclidean_dist_to_rdist32(const float32_t dist) except -1 nogil:
return dist * dist
cdef inline float64_t euclidean_rdist_to_dist32(const float32_t dist) except -1 nogil:
return sqrt(dist)
######################################################################
# DistanceMetric32 base class
cdef class DistanceMetric32(DistanceMetric):
# The following attributes are required for a few of the subclasses.
# we must define them here so that cython's limited polymorphism will work.
# Because we don't expect to instantiate a lot of these objects, the
# extra memory overhead of this setup should not be an issue.
cdef float64_t p
cdef const float64_t[::1] vec
cdef const float64_t[:, ::1] mat
cdef intp_t size
cdef object func
cdef object kwargs
cdef float32_t dist(
self,
const float32_t* x1,
const float32_t* x2,
intp_t size,
) except -1 nogil
cdef float32_t rdist(
self,
const float32_t* x1,
const float32_t* x2,
intp_t size,
) except -1 nogil
cdef float32_t dist_csr(
self,
const float32_t* x1_data,
const int32_t* x1_indices,
const float32_t* x2_data,
const int32_t* x2_indices,
const int32_t x1_start,
const int32_t x1_end,
const int32_t x2_start,
const int32_t x2_end,
const intp_t size,
) except -1 nogil
cdef float32_t rdist_csr(
self,
const float32_t* x1_data,
const int32_t* x1_indices,
const float32_t* x2_data,
const int32_t* x2_indices,
const int32_t x1_start,
const int32_t x1_end,
const int32_t x2_start,
const int32_t x2_end,
const intp_t size,
) except -1 nogil
cdef int pdist(
self,
const float32_t[:, ::1] X,
float32_t[:, ::1] D,
) except -1
cdef int cdist(
self,
const float32_t[:, ::1] X,
const float32_t[:, ::1] Y,
float32_t[:, ::1] D,
) except -1
cdef int pdist_csr(
self,
const float32_t* x1_data,
const int32_t[::1] x1_indices,
const int32_t[::1] x1_indptr,
const intp_t size,
float32_t[:, ::1] D,
) except -1 nogil
cdef int cdist_csr(
self,
const float32_t* x1_data,
const int32_t[::1] x1_indices,
const int32_t[::1] x1_indptr,
const float32_t* x2_data,
const int32_t[::1] x2_indices,
const int32_t[::1] x2_indptr,
const intp_t size,
float32_t[:, ::1] D,
) except -1 nogil
cdef float32_t _rdist_to_dist(self, float32_t rdist) except -1 nogil
cdef float32_t _dist_to_rdist(self, float32_t dist) except -1 nogil
@@ -1,152 +1,152 @@
{{py:
implementation_specific_values = [
# Values are the following ones:
#
# name_suffix, INPUT_DTYPE_t, INPUT_DTYPE
('64', 'float64_t', 'np.float64'),
('32', 'float32_t', 'np.float32')
]
}}
from libc.math cimport sqrt, exp
from ..utils._typedefs cimport float64_t, float32_t, int32_t, intp_t
cdef class DistanceMetric:
pass
{{for name_suffix, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
######################################################################
# Inline distance functions
#
# We use these for the default (euclidean) case so that they can be
# inlined. This leads to faster computation for the most common case
cdef inline float64_t euclidean_dist{{name_suffix}}(
const {{INPUT_DTYPE_t}}* x1,
const {{INPUT_DTYPE_t}}* x2,
intp_t size,
) except -1 nogil:
cdef float64_t tmp, d=0
cdef intp_t j
for j in range(size):
tmp = <float64_t> (x1[j] - x2[j])
d += tmp * tmp
return sqrt(d)
cdef inline float64_t euclidean_rdist{{name_suffix}}(
const {{INPUT_DTYPE_t}}* x1,
const {{INPUT_DTYPE_t}}* x2,
intp_t size,
) except -1 nogil:
cdef float64_t tmp, d=0
cdef intp_t j
for j in range(size):
tmp = <float64_t>(x1[j] - x2[j])
d += tmp * tmp
return d
cdef inline float64_t euclidean_dist_to_rdist{{name_suffix}}(const {{INPUT_DTYPE_t}} dist) except -1 nogil:
return dist * dist
cdef inline float64_t euclidean_rdist_to_dist{{name_suffix}}(const {{INPUT_DTYPE_t}} dist) except -1 nogil:
return sqrt(dist)
######################################################################
# DistanceMetric{{name_suffix}} base class
cdef class DistanceMetric{{name_suffix}}(DistanceMetric):
# The following attributes are required for a few of the subclasses.
# we must define them here so that cython's limited polymorphism will work.
# Because we don't expect to instantiate a lot of these objects, the
# extra memory overhead of this setup should not be an issue.
cdef float64_t p
cdef const float64_t[::1] vec
cdef const float64_t[:, ::1] mat
cdef intp_t size
cdef object func
cdef object kwargs
cdef {{INPUT_DTYPE_t}} dist(
self,
const {{INPUT_DTYPE_t}}* x1,
const {{INPUT_DTYPE_t}}* x2,
intp_t size,
) except -1 nogil
cdef {{INPUT_DTYPE_t}} rdist(
self,
const {{INPUT_DTYPE_t}}* x1,
const {{INPUT_DTYPE_t}}* x2,
intp_t size,
) except -1 nogil
cdef {{INPUT_DTYPE_t}} dist_csr(
self,
const {{INPUT_DTYPE_t}}* x1_data,
const int32_t* x1_indices,
const {{INPUT_DTYPE_t}}* x2_data,
const int32_t* x2_indices,
const int32_t x1_start,
const int32_t x1_end,
const int32_t x2_start,
const int32_t x2_end,
const intp_t size,
) except -1 nogil
cdef {{INPUT_DTYPE_t}} rdist_csr(
self,
const {{INPUT_DTYPE_t}}* x1_data,
const int32_t* x1_indices,
const {{INPUT_DTYPE_t}}* x2_data,
const int32_t* x2_indices,
const int32_t x1_start,
const int32_t x1_end,
const int32_t x2_start,
const int32_t x2_end,
const intp_t size,
) except -1 nogil
cdef int pdist(
self,
const {{INPUT_DTYPE_t}}[:, ::1] X,
{{INPUT_DTYPE_t}}[:, ::1] D,
) except -1
cdef int cdist(
self,
const {{INPUT_DTYPE_t}}[:, ::1] X,
const {{INPUT_DTYPE_t}}[:, ::1] Y,
{{INPUT_DTYPE_t}}[:, ::1] D,
) except -1
cdef int pdist_csr(
self,
const {{INPUT_DTYPE_t}}* x1_data,
const int32_t[::1] x1_indices,
const int32_t[::1] x1_indptr,
const intp_t size,
{{INPUT_DTYPE_t}}[:, ::1] D,
) except -1 nogil
cdef int cdist_csr(
self,
const {{INPUT_DTYPE_t}}* x1_data,
const int32_t[::1] x1_indices,
const int32_t[::1] x1_indptr,
const {{INPUT_DTYPE_t}}* x2_data,
const int32_t[::1] x2_indices,
const int32_t[::1] x2_indptr,
const intp_t size,
{{INPUT_DTYPE_t}}[:, ::1] D,
) except -1 nogil
cdef {{INPUT_DTYPE_t}} _rdist_to_dist(self, {{INPUT_DTYPE_t}} rdist) except -1 nogil
cdef {{INPUT_DTYPE_t}} _dist_to_rdist(self, {{INPUT_DTYPE_t}} dist) except -1 nogil
{{endfor}}
{{py:
implementation_specific_values = [
# Values are the following ones:
#
# name_suffix, INPUT_DTYPE_t, INPUT_DTYPE
('64', 'float64_t', 'np.float64'),
('32', 'float32_t', 'np.float32')
]
}}
from libc.math cimport sqrt, exp
from ..utils._typedefs cimport float64_t, float32_t, int32_t, intp_t
cdef class DistanceMetric:
pass
{{for name_suffix, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
######################################################################
# Inline distance functions
#
# We use these for the default (euclidean) case so that they can be
# inlined. This leads to faster computation for the most common case
cdef inline float64_t euclidean_dist{{name_suffix}}(
const {{INPUT_DTYPE_t}}* x1,
const {{INPUT_DTYPE_t}}* x2,
intp_t size,
) except -1 nogil:
cdef float64_t tmp, d=0
cdef intp_t j
for j in range(size):
tmp = <float64_t> (x1[j] - x2[j])
d += tmp * tmp
return sqrt(d)
cdef inline float64_t euclidean_rdist{{name_suffix}}(
const {{INPUT_DTYPE_t}}* x1,
const {{INPUT_DTYPE_t}}* x2,
intp_t size,
) except -1 nogil:
cdef float64_t tmp, d=0
cdef intp_t j
for j in range(size):
tmp = <float64_t>(x1[j] - x2[j])
d += tmp * tmp
return d
cdef inline float64_t euclidean_dist_to_rdist{{name_suffix}}(const {{INPUT_DTYPE_t}} dist) except -1 nogil:
return dist * dist
cdef inline float64_t euclidean_rdist_to_dist{{name_suffix}}(const {{INPUT_DTYPE_t}} dist) except -1 nogil:
return sqrt(dist)
######################################################################
# DistanceMetric{{name_suffix}} base class
cdef class DistanceMetric{{name_suffix}}(DistanceMetric):
# The following attributes are required for a few of the subclasses.
# we must define them here so that cython's limited polymorphism will work.
# Because we don't expect to instantiate a lot of these objects, the
# extra memory overhead of this setup should not be an issue.
cdef float64_t p
cdef const float64_t[::1] vec
cdef const float64_t[:, ::1] mat
cdef intp_t size
cdef object func
cdef object kwargs
cdef {{INPUT_DTYPE_t}} dist(
self,
const {{INPUT_DTYPE_t}}* x1,
const {{INPUT_DTYPE_t}}* x2,
intp_t size,
) except -1 nogil
cdef {{INPUT_DTYPE_t}} rdist(
self,
const {{INPUT_DTYPE_t}}* x1,
const {{INPUT_DTYPE_t}}* x2,
intp_t size,
) except -1 nogil
cdef {{INPUT_DTYPE_t}} dist_csr(
self,
const {{INPUT_DTYPE_t}}* x1_data,
const int32_t* x1_indices,
const {{INPUT_DTYPE_t}}* x2_data,
const int32_t* x2_indices,
const int32_t x1_start,
const int32_t x1_end,
const int32_t x2_start,
const int32_t x2_end,
const intp_t size,
) except -1 nogil
cdef {{INPUT_DTYPE_t}} rdist_csr(
self,
const {{INPUT_DTYPE_t}}* x1_data,
const int32_t* x1_indices,
const {{INPUT_DTYPE_t}}* x2_data,
const int32_t* x2_indices,
const int32_t x1_start,
const int32_t x1_end,
const int32_t x2_start,
const int32_t x2_end,
const intp_t size,
) except -1 nogil
cdef int pdist(
self,
const {{INPUT_DTYPE_t}}[:, ::1] X,
{{INPUT_DTYPE_t}}[:, ::1] D,
) except -1
cdef int cdist(
self,
const {{INPUT_DTYPE_t}}[:, ::1] X,
const {{INPUT_DTYPE_t}}[:, ::1] Y,
{{INPUT_DTYPE_t}}[:, ::1] D,
) except -1
cdef int pdist_csr(
self,
const {{INPUT_DTYPE_t}}* x1_data,
const int32_t[::1] x1_indices,
const int32_t[::1] x1_indptr,
const intp_t size,
{{INPUT_DTYPE_t}}[:, ::1] D,
) except -1 nogil
cdef int cdist_csr(
self,
const {{INPUT_DTYPE_t}}* x1_data,
const int32_t[::1] x1_indices,
const int32_t[::1] x1_indptr,
const {{INPUT_DTYPE_t}}* x2_data,
const int32_t[::1] x2_indices,
const int32_t[::1] x2_indptr,
const intp_t size,
{{INPUT_DTYPE_t}}[:, ::1] D,
) except -1 nogil
cdef {{INPUT_DTYPE_t}} _rdist_to_dist(self, {{INPUT_DTYPE_t}} rdist) except -1 nogil
cdef {{INPUT_DTYPE_t}} _dist_to_rdist(self, {{INPUT_DTYPE_t}} dist) except -1 nogil
{{endfor}}
File diff suppressed because it is too large Load Diff
@@ -1,31 +1,31 @@
from ...utils._typedefs cimport intp_t, float64_t
{{for name_suffix in ['64', '32']}}
from ._base cimport BaseDistancesReduction{{name_suffix}}
from ._middle_term_computer cimport MiddleTermComputer{{name_suffix}}
cdef class ArgKmin{{name_suffix}}(BaseDistancesReduction{{name_suffix}}):
"""float{{name_suffix}} implementation of the ArgKmin."""
cdef:
intp_t k
intp_t[:, ::1] argkmin_indices
float64_t[:, ::1] argkmin_distances
# Used as array of pointers to private datastructures used in threads.
float64_t ** heaps_r_distances_chunks
intp_t ** heaps_indices_chunks
cdef class EuclideanArgKmin{{name_suffix}}(ArgKmin{{name_suffix}}):
"""EuclideanDistance-specialisation of ArgKmin{{name_suffix}}."""
cdef:
MiddleTermComputer{{name_suffix}} middle_term_computer
const float64_t[::1] X_norm_squared
const float64_t[::1] Y_norm_squared
bint use_squared_distances
{{endfor}}
from ...utils._typedefs cimport intp_t, float64_t
{{for name_suffix in ['64', '32']}}
from ._base cimport BaseDistancesReduction{{name_suffix}}
from ._middle_term_computer cimport MiddleTermComputer{{name_suffix}}
cdef class ArgKmin{{name_suffix}}(BaseDistancesReduction{{name_suffix}}):
"""float{{name_suffix}} implementation of the ArgKmin."""
cdef:
intp_t k
intp_t[:, ::1] argkmin_indices
float64_t[:, ::1] argkmin_distances
# Used as array of pointers to private datastructures used in threads.
float64_t ** heaps_r_distances_chunks
intp_t ** heaps_indices_chunks
cdef class EuclideanArgKmin{{name_suffix}}(ArgKmin{{name_suffix}}):
"""EuclideanDistance-specialisation of ArgKmin{{name_suffix}}."""
cdef:
MiddleTermComputer{{name_suffix}} middle_term_computer
const float64_t[::1] X_norm_squared
const float64_t[::1] Y_norm_squared
bint use_squared_distances
{{endfor}}
@@ -1,182 +1,182 @@
from cython cimport floating, integral
from cython.parallel cimport parallel, prange
from libcpp.map cimport map as cpp_map, pair as cpp_pair
from libc.stdlib cimport free
from ...utils._typedefs cimport intp_t, float64_t
from ...utils.parallel import _get_threadpool_controller
import numpy as np
from scipy.sparse import issparse
from ._classmode cimport WeightingStrategy
{{for name_suffix in ["32", "64"]}}
from ._argkmin cimport ArgKmin{{name_suffix}}
from ._datasets_pair cimport DatasetsPair{{name_suffix}}
cdef class ArgKminClassMode{{name_suffix}}(ArgKmin{{name_suffix}}):
"""
{{name_suffix}}bit implementation of ArgKminClassMode.
"""
cdef:
const intp_t[:] Y_labels,
const intp_t[:] unique_Y_labels
float64_t[:, :] class_scores
cpp_map[intp_t, intp_t] labels_to_index
WeightingStrategy weight_type
@classmethod
def compute(
cls,
X,
Y,
intp_t k,
weights,
Y_labels,
unique_Y_labels,
str metric="euclidean",
chunk_size=None,
dict metric_kwargs=None,
str strategy=None,
):
"""Compute the argkmin reduction with Y_labels.
This classmethod is responsible for introspecting the arguments
values to dispatch to the most appropriate implementation of
:class:`ArgKminClassMode{{name_suffix}}`.
This allows decoupling the API entirely from the implementation details
whilst maintaining RAII: all temporarily allocated datastructures necessary
for the concrete implementation are therefore freed when this classmethod
returns.
No instance _must_ directly be created outside of this class method.
"""
# Use a generic implementation that handles most scipy
# metrics by computing the distances between 2 vectors at a time.
pda = ArgKminClassMode{{name_suffix}}(
datasets_pair=DatasetsPair{{name_suffix}}.get_for(X, Y, metric, metric_kwargs),
k=k,
chunk_size=chunk_size,
strategy=strategy,
weights=weights,
Y_labels=Y_labels,
unique_Y_labels=unique_Y_labels,
)
# Limit the number of threads in second level of nested parallelism for BLAS
# to avoid threads over-subscription (in GEMM for instance).
with _get_threadpool_controller().limit(limits=1, user_api="blas"):
if pda.execute_in_parallel_on_Y:
pda._parallel_on_Y()
else:
pda._parallel_on_X()
return pda._finalize_results()
def __init__(
self,
DatasetsPair{{name_suffix}} datasets_pair,
const intp_t[:] Y_labels,
const intp_t[:] unique_Y_labels,
chunk_size=None,
strategy=None,
intp_t k=1,
weights=None,
):
super().__init__(
datasets_pair=datasets_pair,
chunk_size=chunk_size,
strategy=strategy,
k=k,
)
if weights == "uniform":
self.weight_type = WeightingStrategy.uniform
elif weights == "distance":
self.weight_type = WeightingStrategy.distance
else:
self.weight_type = WeightingStrategy.callable
self.Y_labels = Y_labels
self.unique_Y_labels = unique_Y_labels
cdef intp_t idx, neighbor_class_idx
# Map from set of unique labels to their indices in `class_scores`
# Buffer used in building a histogram for one-pass weighted mode
self.class_scores = np.zeros(
(self.n_samples_X, unique_Y_labels.shape[0]), dtype=np.float64,
)
def _finalize_results(self):
probabilities = np.asarray(self.class_scores)
probabilities /= probabilities.sum(axis=1, keepdims=True)
return probabilities
cdef inline void weighted_histogram_mode(
self,
intp_t sample_index,
intp_t* indices,
float64_t* distances,
) noexcept nogil:
cdef:
intp_t neighbor_idx, neighbor_class_idx, label_index, multi_output_index
float64_t score_incr = 1
# TODO: Implement other WeightingStrategy values
bint use_distance_weighting = (
self.weight_type == WeightingStrategy.distance
)
# Iterate through the sample k-nearest neighbours
for neighbor_rank in range(self.k):
# Absolute indice of the neighbor_rank-th Nearest Neighbors
# in range [0, n_samples_Y)
# TODO: inspect if it worth permuting this condition
# and the for-loop above for improved branching.
if use_distance_weighting:
score_incr = 1 / distances[neighbor_rank]
neighbor_idx = indices[neighbor_rank]
neighbor_class_idx = self.Y_labels[neighbor_idx]
self.class_scores[sample_index][neighbor_class_idx] += score_incr
return
cdef void _parallel_on_X_prange_iter_finalize(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil:
cdef:
intp_t idx, sample_index
for idx in range(X_end - X_start):
# One-pass top-one weighted mode
# Compute the absolute index in [0, n_samples_X)
sample_index = X_start + idx
self.weighted_histogram_mode(
sample_index,
&self.heaps_indices_chunks[thread_num][idx * self.k],
&self.heaps_r_distances_chunks[thread_num][idx * self.k],
)
return
cdef void _parallel_on_Y_finalize(
self,
) noexcept nogil:
cdef:
intp_t sample_index, thread_num
with nogil, parallel(num_threads=self.chunks_n_threads):
# Deallocating temporary datastructures
for thread_num in prange(self.chunks_n_threads, schedule='static'):
free(self.heaps_r_distances_chunks[thread_num])
free(self.heaps_indices_chunks[thread_num])
for sample_index in prange(self.n_samples_X, schedule='static'):
self.weighted_histogram_mode(
sample_index,
&self.argkmin_indices[sample_index][0],
&self.argkmin_distances[sample_index][0],
)
return
{{endfor}}
from cython cimport floating, integral
from cython.parallel cimport parallel, prange
from libcpp.map cimport map as cpp_map, pair as cpp_pair
from libc.stdlib cimport free
from ...utils._typedefs cimport intp_t, float64_t
from ...utils.parallel import _get_threadpool_controller
import numpy as np
from scipy.sparse import issparse
from ._classmode cimport WeightingStrategy
{{for name_suffix in ["32", "64"]}}
from ._argkmin cimport ArgKmin{{name_suffix}}
from ._datasets_pair cimport DatasetsPair{{name_suffix}}
cdef class ArgKminClassMode{{name_suffix}}(ArgKmin{{name_suffix}}):
"""
{{name_suffix}}bit implementation of ArgKminClassMode.
"""
cdef:
const intp_t[:] Y_labels,
const intp_t[:] unique_Y_labels
float64_t[:, :] class_scores
cpp_map[intp_t, intp_t] labels_to_index
WeightingStrategy weight_type
@classmethod
def compute(
cls,
X,
Y,
intp_t k,
weights,
Y_labels,
unique_Y_labels,
str metric="euclidean",
chunk_size=None,
dict metric_kwargs=None,
str strategy=None,
):
"""Compute the argkmin reduction with Y_labels.
This classmethod is responsible for introspecting the arguments
values to dispatch to the most appropriate implementation of
:class:`ArgKminClassMode{{name_suffix}}`.
This allows decoupling the API entirely from the implementation details
whilst maintaining RAII: all temporarily allocated datastructures necessary
for the concrete implementation are therefore freed when this classmethod
returns.
No instance _must_ directly be created outside of this class method.
"""
# Use a generic implementation that handles most scipy
# metrics by computing the distances between 2 vectors at a time.
pda = ArgKminClassMode{{name_suffix}}(
datasets_pair=DatasetsPair{{name_suffix}}.get_for(X, Y, metric, metric_kwargs),
k=k,
chunk_size=chunk_size,
strategy=strategy,
weights=weights,
Y_labels=Y_labels,
unique_Y_labels=unique_Y_labels,
)
# Limit the number of threads in second level of nested parallelism for BLAS
# to avoid threads over-subscription (in GEMM for instance).
with _get_threadpool_controller().limit(limits=1, user_api="blas"):
if pda.execute_in_parallel_on_Y:
pda._parallel_on_Y()
else:
pda._parallel_on_X()
return pda._finalize_results()
def __init__(
self,
DatasetsPair{{name_suffix}} datasets_pair,
const intp_t[:] Y_labels,
const intp_t[:] unique_Y_labels,
chunk_size=None,
strategy=None,
intp_t k=1,
weights=None,
):
super().__init__(
datasets_pair=datasets_pair,
chunk_size=chunk_size,
strategy=strategy,
k=k,
)
if weights == "uniform":
self.weight_type = WeightingStrategy.uniform
elif weights == "distance":
self.weight_type = WeightingStrategy.distance
else:
self.weight_type = WeightingStrategy.callable
self.Y_labels = Y_labels
self.unique_Y_labels = unique_Y_labels
cdef intp_t idx, neighbor_class_idx
# Map from set of unique labels to their indices in `class_scores`
# Buffer used in building a histogram for one-pass weighted mode
self.class_scores = np.zeros(
(self.n_samples_X, unique_Y_labels.shape[0]), dtype=np.float64,
)
def _finalize_results(self):
probabilities = np.asarray(self.class_scores)
probabilities /= probabilities.sum(axis=1, keepdims=True)
return probabilities
cdef inline void weighted_histogram_mode(
self,
intp_t sample_index,
intp_t* indices,
float64_t* distances,
) noexcept nogil:
cdef:
intp_t neighbor_idx, neighbor_class_idx, label_index, multi_output_index
float64_t score_incr = 1
# TODO: Implement other WeightingStrategy values
bint use_distance_weighting = (
self.weight_type == WeightingStrategy.distance
)
# Iterate through the sample k-nearest neighbours
for neighbor_rank in range(self.k):
# Absolute indice of the neighbor_rank-th Nearest Neighbors
# in range [0, n_samples_Y)
# TODO: inspect if it worth permuting this condition
# and the for-loop above for improved branching.
if use_distance_weighting:
score_incr = 1 / distances[neighbor_rank]
neighbor_idx = indices[neighbor_rank]
neighbor_class_idx = self.Y_labels[neighbor_idx]
self.class_scores[sample_index][neighbor_class_idx] += score_incr
return
cdef void _parallel_on_X_prange_iter_finalize(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil:
cdef:
intp_t idx, sample_index
for idx in range(X_end - X_start):
# One-pass top-one weighted mode
# Compute the absolute index in [0, n_samples_X)
sample_index = X_start + idx
self.weighted_histogram_mode(
sample_index,
&self.heaps_indices_chunks[thread_num][idx * self.k],
&self.heaps_r_distances_chunks[thread_num][idx * self.k],
)
return
cdef void _parallel_on_Y_finalize(
self,
) noexcept nogil:
cdef:
intp_t sample_index, thread_num
with nogil, parallel(num_threads=self.chunks_n_threads):
# Deallocating temporary datastructures
for thread_num in prange(self.chunks_n_threads, schedule='static'):
free(self.heaps_r_distances_chunks[thread_num])
free(self.heaps_indices_chunks[thread_num])
for sample_index in prange(self.n_samples_X, schedule='static'):
self.weighted_histogram_mode(
sample_index,
&self.argkmin_indices[sample_index][0],
&self.argkmin_distances[sample_index][0],
)
return
{{endfor}}
@@ -1,135 +1,135 @@
from cython cimport final
from ...utils._typedefs cimport intp_t, float64_t
{{for name_suffix in ['64', '32']}}
from ._datasets_pair cimport DatasetsPair{{name_suffix}}
cpdef float64_t[::1] _sqeuclidean_row_norms{{name_suffix}}(
X,
intp_t num_threads,
)
cdef class BaseDistancesReduction{{name_suffix}}:
"""
Base float{{name_suffix}} implementation template of the pairwise-distances
reduction backends.
Implementations inherit from this template and may override the several
defined hooks as needed in order to easily extend functionality with
minimal redundant code.
"""
cdef:
readonly DatasetsPair{{name_suffix}} datasets_pair
# The number of threads that can be used is stored in effective_n_threads.
#
# The number of threads to use in the parallelization strategy
# (i.e. parallel_on_X or parallel_on_Y) can be smaller than effective_n_threads:
# for small datasets, fewer threads might be needed to loop over pair of chunks.
#
# Hence, the number of threads that _will_ be used for looping over chunks
# is stored in chunks_n_threads, allowing solely using what we need.
#
# Thus, an invariant is:
#
# chunks_n_threads <= effective_n_threads
#
intp_t effective_n_threads
intp_t chunks_n_threads
intp_t n_samples_chunk, chunk_size
intp_t n_samples_X, X_n_samples_chunk, X_n_chunks, X_n_samples_last_chunk
intp_t n_samples_Y, Y_n_samples_chunk, Y_n_chunks, Y_n_samples_last_chunk
bint execute_in_parallel_on_Y
@final
cdef void _parallel_on_X(self) noexcept nogil
@final
cdef void _parallel_on_Y(self) noexcept nogil
# Placeholder methods which have to be implemented
cdef void _compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
# Placeholder methods which can be implemented
cdef void compute_exact_distances(self) noexcept nogil
cdef void _parallel_on_X_parallel_init(
self,
intp_t thread_num,
) noexcept nogil
cdef void _parallel_on_X_init_chunk(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_X_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
cdef void _parallel_on_X_prange_iter_finalize(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_X_parallel_finalize(
self,
intp_t thread_num
) noexcept nogil
cdef void _parallel_on_Y_init(
self,
) noexcept nogil
cdef void _parallel_on_Y_parallel_init(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_Y_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
cdef void _parallel_on_Y_synchronize(
self,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_Y_finalize(
self,
) noexcept nogil
{{endfor}}
from cython cimport final
from ...utils._typedefs cimport intp_t, float64_t
{{for name_suffix in ['64', '32']}}
from ._datasets_pair cimport DatasetsPair{{name_suffix}}
cpdef float64_t[::1] _sqeuclidean_row_norms{{name_suffix}}(
X,
intp_t num_threads,
)
cdef class BaseDistancesReduction{{name_suffix}}:
"""
Base float{{name_suffix}} implementation template of the pairwise-distances
reduction backends.
Implementations inherit from this template and may override the several
defined hooks as needed in order to easily extend functionality with
minimal redundant code.
"""
cdef:
readonly DatasetsPair{{name_suffix}} datasets_pair
# The number of threads that can be used is stored in effective_n_threads.
#
# The number of threads to use in the parallelization strategy
# (i.e. parallel_on_X or parallel_on_Y) can be smaller than effective_n_threads:
# for small datasets, fewer threads might be needed to loop over pair of chunks.
#
# Hence, the number of threads that _will_ be used for looping over chunks
# is stored in chunks_n_threads, allowing solely using what we need.
#
# Thus, an invariant is:
#
# chunks_n_threads <= effective_n_threads
#
intp_t effective_n_threads
intp_t chunks_n_threads
intp_t n_samples_chunk, chunk_size
intp_t n_samples_X, X_n_samples_chunk, X_n_chunks, X_n_samples_last_chunk
intp_t n_samples_Y, Y_n_samples_chunk, Y_n_chunks, Y_n_samples_last_chunk
bint execute_in_parallel_on_Y
@final
cdef void _parallel_on_X(self) noexcept nogil
@final
cdef void _parallel_on_Y(self) noexcept nogil
# Placeholder methods which have to be implemented
cdef void _compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
# Placeholder methods which can be implemented
cdef void compute_exact_distances(self) noexcept nogil
cdef void _parallel_on_X_parallel_init(
self,
intp_t thread_num,
) noexcept nogil
cdef void _parallel_on_X_init_chunk(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_X_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
cdef void _parallel_on_X_prange_iter_finalize(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_X_parallel_finalize(
self,
intp_t thread_num
) noexcept nogil
cdef void _parallel_on_Y_init(
self,
) noexcept nogil
cdef void _parallel_on_Y_parallel_init(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_Y_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
cdef void _parallel_on_Y_synchronize(
self,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_Y_finalize(
self,
) noexcept nogil
{{endfor}}
@@ -1,5 +1,5 @@
cpdef enum WeightingStrategy:
uniform = 0
# TODO: Implement the following options in weighted_histogram_mode
distance = 1
callable = 2
cpdef enum WeightingStrategy:
uniform = 0
# TODO: Implement the following options in weighted_histogram_mode
distance = 1
callable = 2
@@ -1,67 +1,67 @@
{{py:
implementation_specific_values = [
# Values are the following ones:
#
# name_suffix, INPUT_DTYPE_t, INPUT_DTYPE
('64', 'DistanceMetric64', 'float64_t'),
('32', 'DistanceMetric32', 'float32_t')
]
}}
from ...utils._typedefs cimport float64_t, float32_t, int32_t, intp_t
from ...metrics._dist_metrics cimport DistanceMetric64, DistanceMetric32, DistanceMetric
{{for name_suffix, DistanceMetric, INPUT_DTYPE_t in implementation_specific_values}}
cdef class DatasetsPair{{name_suffix}}:
cdef:
{{DistanceMetric}} distance_metric
intp_t n_features
cdef intp_t n_samples_X(self) noexcept nogil
cdef intp_t n_samples_Y(self) noexcept nogil
cdef float64_t dist(self, intp_t i, intp_t j) noexcept nogil
cdef float64_t surrogate_dist(self, intp_t i, intp_t j) noexcept nogil
cdef class DenseDenseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
cdef:
const {{INPUT_DTYPE_t}}[:, ::1] X
const {{INPUT_DTYPE_t}}[:, ::1] Y
cdef class SparseSparseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
cdef:
const {{INPUT_DTYPE_t}}[:] X_data
const int32_t[::1] X_indices
const int32_t[::1] X_indptr
const {{INPUT_DTYPE_t}}[:] Y_data
const int32_t[::1] Y_indices
const int32_t[::1] Y_indptr
cdef class SparseDenseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
cdef:
const {{INPUT_DTYPE_t}}[:] X_data
const int32_t[::1] X_indices
const int32_t[::1] X_indptr
const {{INPUT_DTYPE_t}}[:] Y_data
const int32_t[::1] Y_indices
intp_t n_Y
cdef class DenseSparseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
cdef:
# As distance metrics are commutative, we can simply rely
# on the implementation of SparseDenseDatasetsPair and
# swap arguments.
DatasetsPair{{name_suffix}} datasets_pair
{{endfor}}
{{py:
implementation_specific_values = [
# Values are the following ones:
#
# name_suffix, INPUT_DTYPE_t, INPUT_DTYPE
('64', 'DistanceMetric64', 'float64_t'),
('32', 'DistanceMetric32', 'float32_t')
]
}}
from ...utils._typedefs cimport float64_t, float32_t, int32_t, intp_t
from ...metrics._dist_metrics cimport DistanceMetric64, DistanceMetric32, DistanceMetric
{{for name_suffix, DistanceMetric, INPUT_DTYPE_t in implementation_specific_values}}
cdef class DatasetsPair{{name_suffix}}:
cdef:
{{DistanceMetric}} distance_metric
intp_t n_features
cdef intp_t n_samples_X(self) noexcept nogil
cdef intp_t n_samples_Y(self) noexcept nogil
cdef float64_t dist(self, intp_t i, intp_t j) noexcept nogil
cdef float64_t surrogate_dist(self, intp_t i, intp_t j) noexcept nogil
cdef class DenseDenseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
cdef:
const {{INPUT_DTYPE_t}}[:, ::1] X
const {{INPUT_DTYPE_t}}[:, ::1] Y
cdef class SparseSparseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
cdef:
const {{INPUT_DTYPE_t}}[:] X_data
const int32_t[::1] X_indices
const int32_t[::1] X_indptr
const {{INPUT_DTYPE_t}}[:] Y_data
const int32_t[::1] Y_indices
const int32_t[::1] Y_indptr
cdef class SparseDenseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
cdef:
const {{INPUT_DTYPE_t}}[:] X_data
const int32_t[::1] X_indices
const int32_t[::1] X_indptr
const {{INPUT_DTYPE_t}}[:] Y_data
const int32_t[::1] Y_indices
intp_t n_Y
cdef class DenseSparseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
cdef:
# As distance metrics are commutative, we can simply rely
# on the implementation of SparseDenseDatasetsPair and
# swap arguments.
DatasetsPair{{name_suffix}} datasets_pair
{{endfor}}
@@ -1,406 +1,406 @@
import copy
{{py:
implementation_specific_values = [
# Values are the following ones:
#
# name_suffix, INPUT_DTYPE_t, INPUT_DTYPE
('64', 'DistanceMetric64', 'float64_t', 'np.float64'),
('32', 'DistanceMetric32', 'float32_t', 'np.float32')
]
}}
import numpy as np
from cython cimport final
from ...utils._typedefs cimport float64_t, float32_t, intp_t
from scipy.sparse import issparse, csr_matrix
{{for name_suffix, DistanceMetric, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
cdef class DatasetsPair{{name_suffix}}:
"""Abstract class which wraps a pair of datasets (X, Y).
This class allows computing distances between a single pair of rows of
of X and Y at a time given the pair of their indices (i, j). This class is
specialized for each metric thanks to the :func:`get_for` factory classmethod.
The handling of parallelization over chunks to compute the distances
and aggregation for several rows at a time is done in dedicated
subclasses of :class:`BaseDistancesReductionDispatcher` that in-turn rely on
subclasses of :class:`DatasetsPair` for each pair of rows in the data. The
goal is to make it possible to decouple the generic parallelization and
aggregation logic from metric-specific computation as much as possible.
X and Y can be stored as C-contiguous np.ndarrays or CSR matrices
in subclasses.
This class avoids the overhead of dispatching distance computations
to :class:`sklearn.metrics.DistanceMetric` based on the physical
representation of the vectors (sparse vs. dense). It makes use of
cython.final to remove the overhead of dispatching method calls.
Parameters
----------
distance_metric: {{DistanceMetric}}
The distance metric responsible for computing distances
between two vectors of (X, Y).
"""
@classmethod
def get_for(
cls,
X,
Y,
metric="euclidean",
dict metric_kwargs=None,
) -> DatasetsPair{{name_suffix}}:
"""Return the DatasetsPair implementation for the given arguments.
Parameters
----------
X : {ndarray, sparse matrix} of shape (n_samples_X, n_features)
Input data.
If provided as a ndarray, it must be C-contiguous.
If provided as a sparse matrix, it must be in CSR format.
Y : {ndarray, sparse matrix} of shape (n_samples_Y, n_features)
Input data.
If provided as a ndarray, it must be C-contiguous.
If provided as a sparse matrix, it must be in CSR format.
metric : str or DistanceMetric object, default='euclidean'
The distance metric to compute between rows of X and Y.
The default metric is a fast implementation of the Euclidean
metric. For a list of available metrics, see the documentation
of :class:`~sklearn.metrics.DistanceMetric`.
metric_kwargs : dict, default=None
Keyword arguments to pass to specified metric function.
Returns
-------
datasets_pair: DatasetsPair{{name_suffix}}
The suited DatasetsPair{{name_suffix}} implementation.
"""
# X_norm_squared and Y_norm_squared might be propagated
# down to DatasetsPairs via metrics_kwargs when the Euclidean
# specialisations can't be used.
# To prevent X_norm_squared and Y_norm_squared to be passed
# down to DistanceMetrics (whose constructors would raise
# a RuntimeError), we pop them here.
if metric_kwargs is not None:
# Copying metric_kwargs not to pop "X_norm_squared"
# and "Y_norm_squared" where they are used
metric_kwargs = copy.copy(metric_kwargs)
metric_kwargs.pop("X_norm_squared", None)
metric_kwargs.pop("Y_norm_squared", None)
cdef:
{{DistanceMetric}} distance_metric = DistanceMetric.get_metric(
metric,
{{INPUT_DTYPE}},
**(metric_kwargs or {})
)
# Metric-specific checks that do not replace nor duplicate `check_array`.
distance_metric._validate_data(X)
distance_metric._validate_data(Y)
X_is_sparse = issparse(X)
Y_is_sparse = issparse(Y)
if not X_is_sparse and not Y_is_sparse:
return DenseDenseDatasetsPair{{name_suffix}}(X, Y, distance_metric)
if X_is_sparse and Y_is_sparse:
return SparseSparseDatasetsPair{{name_suffix}}(X, Y, distance_metric)
if X_is_sparse and not Y_is_sparse:
return SparseDenseDatasetsPair{{name_suffix}}(X, Y, distance_metric)
return DenseSparseDatasetsPair{{name_suffix}}(X, Y, distance_metric)
@classmethod
def unpack_csr_matrix(cls, X: csr_matrix):
"""Ensure that the CSR matrix is indexed with np.int32."""
X_data = np.asarray(X.data, dtype={{INPUT_DTYPE}})
X_indices = np.asarray(X.indices, dtype=np.int32)
X_indptr = np.asarray(X.indptr, dtype=np.int32)
return X_data, X_indices, X_indptr
def __init__(self, {{DistanceMetric}} distance_metric, intp_t n_features):
self.distance_metric = distance_metric
self.n_features = n_features
cdef intp_t n_samples_X(self) noexcept nogil:
"""Number of samples in X."""
# This is a abstract method.
# This _must_ always be overwritten in subclasses.
# TODO: add "with gil: raise" here when supporting Cython 3.0
return -999
cdef intp_t n_samples_Y(self) noexcept nogil:
"""Number of samples in Y."""
# This is a abstract method.
# This _must_ always be overwritten in subclasses.
# TODO: add "with gil: raise" here when supporting Cython 3.0
return -999
cdef float64_t surrogate_dist(self, intp_t i, intp_t j) noexcept nogil:
return self.dist(i, j)
cdef float64_t dist(self, intp_t i, intp_t j) noexcept nogil:
# This is a abstract method.
# This _must_ always be overwritten in subclasses.
# TODO: add "with gil: raise" here when supporting Cython 3.0
return -1
@final
cdef class DenseDenseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
"""Compute distances between row vectors of two arrays.
Parameters
----------
X: ndarray of shape (n_samples_X, n_features)
Rows represent vectors. Must be C-contiguous.
Y: ndarray of shape (n_samples_Y, n_features)
Rows represent vectors. Must be C-contiguous.
distance_metric: DistanceMetric
The distance metric responsible for computing distances
between two row vectors of (X, Y).
"""
def __init__(
self,
const {{INPUT_DTYPE_t}}[:, ::1] X,
const {{INPUT_DTYPE_t}}[:, ::1] Y,
{{DistanceMetric}} distance_metric,
):
super().__init__(distance_metric, n_features=X.shape[1])
# Arrays have already been checked
self.X = X
self.Y = Y
@final
cdef intp_t n_samples_X(self) noexcept nogil:
return self.X.shape[0]
@final
cdef intp_t n_samples_Y(self) noexcept nogil:
return self.Y.shape[0]
@final
cdef float64_t surrogate_dist(self, intp_t i, intp_t j) noexcept nogil:
return self.distance_metric.rdist(&self.X[i, 0], &self.Y[j, 0], self.n_features)
@final
cdef float64_t dist(self, intp_t i, intp_t j) noexcept nogil:
return self.distance_metric.dist(&self.X[i, 0], &self.Y[j, 0], self.n_features)
@final
cdef class SparseSparseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
"""Compute distances between vectors of two CSR matrices.
Parameters
----------
X: sparse matrix of shape (n_samples_X, n_features)
Rows represent vectors. Must be in CSR format.
Y: sparse matrix of shape (n_samples_Y, n_features)
Rows represent vectors. Must be in CSR format.
distance_metric: DistanceMetric
The distance metric responsible for computing distances
between two vectors of (X, Y).
"""
def __init__(self, X, Y, {{DistanceMetric}} distance_metric):
super().__init__(distance_metric, n_features=X.shape[1])
self.X_data, self.X_indices, self.X_indptr = self.unpack_csr_matrix(X)
self.Y_data, self.Y_indices, self.Y_indptr = self.unpack_csr_matrix(Y)
@final
cdef intp_t n_samples_X(self) noexcept nogil:
return self.X_indptr.shape[0] - 1
@final
cdef intp_t n_samples_Y(self) noexcept nogil:
return self.Y_indptr.shape[0] - 1
@final
cdef float64_t surrogate_dist(self, intp_t i, intp_t j) noexcept nogil:
return self.distance_metric.rdist_csr(
x1_data=&self.X_data[0],
x1_indices=&self.X_indices[0],
x2_data=&self.Y_data[0],
x2_indices=&self.Y_indices[0],
x1_start=self.X_indptr[i],
x1_end=self.X_indptr[i + 1],
x2_start=self.Y_indptr[j],
x2_end=self.Y_indptr[j + 1],
size=self.n_features,
)
@final
cdef float64_t dist(self, intp_t i, intp_t j) noexcept nogil:
return self.distance_metric.dist_csr(
x1_data=&self.X_data[0],
x1_indices=&self.X_indices[0],
x2_data=&self.Y_data[0],
x2_indices=&self.Y_indices[0],
x1_start=self.X_indptr[i],
x1_end=self.X_indptr[i + 1],
x2_start=self.Y_indptr[j],
x2_end=self.Y_indptr[j + 1],
size=self.n_features,
)
@final
cdef class SparseDenseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
"""Compute distances between vectors of a CSR matrix and a dense array.
Parameters
----------
X: sparse matrix of shape (n_samples_X, n_features)
Rows represent vectors. Must be in CSR format.
Y: ndarray of shape (n_samples_Y, n_features)
Rows represent vectors. Must be C-contiguous.
distance_metric: DistanceMetric
The distance metric responsible for computing distances
between two vectors of (X, Y).
"""
def __init__(self, X, Y, {{DistanceMetric}} distance_metric):
super().__init__(distance_metric, n_features=X.shape[1])
self.X_data, self.X_indices, self.X_indptr = self.unpack_csr_matrix(X)
# We support the sparse-dense case by using the sparse-sparse interfaces
# of `DistanceMetric` (namely `DistanceMetric.{dist_csr,rdist_csr}`) to
# avoid introducing a new complex set of interfaces. In this case, we
# need to convert `Y` (the dense array) into a CSR matrix.
#
# Here we motive using another simpler CSR representation to use for `Y`.
#
# If we were to use the usual CSR representation for `Y`, storing all
# the columns indices in `indices` would have required allocating an
# array of n_samples × n_features elements with repeated contiguous
# integers from 0 to n_features - 1. This would have been very wasteful
# from a memory point of view. This alternative representation just uses
# the necessary amount of information needed and only necessitates
# shifting the address of `data` before calling the CSR × CSR routines.
#
# In this representation:
#
# - the `data` array is the original dense array, `Y`, whose first
# element's address is shifted before calling the CSR × CSR routine
#
# - the `indices` array is a single row of `n_features` elements:
#
# [0, 1, ..., n_features-1]
#
# - the `indptr` array is not materialised as the indices pointers'
# offset is constant (the offset equals `n_features`). Moreover, as
# `data` is shifted, constant `start` and `end` indices pointers
# respectively equalling 0 and n_features are used.
# Y array already has been checked here
self.n_Y = Y.shape[0]
self.Y_data = np.ravel(Y)
self.Y_indices = np.arange(self.n_features, dtype=np.int32)
@final
cdef intp_t n_samples_X(self) noexcept nogil:
return self.X_indptr.shape[0] - 1
@final
cdef intp_t n_samples_Y(self) noexcept nogil:
return self.n_Y
@final
cdef float64_t surrogate_dist(self, intp_t i, intp_t j) noexcept nogil:
return self.distance_metric.rdist_csr(
x1_data=&self.X_data[0],
x1_indices=&self.X_indices[0],
# Increment the data pointer such that x2_start=0 is aligned with the
# j-th row
x2_data=&self.Y_data[0] + j * self.n_features,
x2_indices=&self.Y_indices[0],
x1_start=self.X_indptr[i],
x1_end=self.X_indptr[i + 1],
x2_start=0,
x2_end=self.n_features,
size=self.n_features,
)
@final
cdef float64_t dist(self, intp_t i, intp_t j) noexcept nogil:
return self.distance_metric.dist_csr(
x1_data=&self.X_data[0],
x1_indices=&self.X_indices[0],
# Increment the data pointer such that x2_start=0 is aligned with the
# j-th row
x2_data=&self.Y_data[0] + j * self.n_features,
x2_indices=&self.Y_indices[0],
x1_start=self.X_indptr[i],
x1_end=self.X_indptr[i + 1],
x2_start=0,
x2_end=self.n_features,
size=self.n_features,
)
@final
cdef class DenseSparseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
"""Compute distances between vectors of a dense array and a CSR matrix.
Parameters
----------
X: ndarray of shape (n_samples_X, n_features)
Rows represent vectors. Must be C-contiguous.
Y: sparse matrix of shape (n_samples_Y, n_features)
Rows represent vectors. Must be in CSR format.
distance_metric: DistanceMetric
The distance metric responsible for computing distances
between two vectors of (X, Y).
"""
def __init__(self, X, Y, {{DistanceMetric}} distance_metric):
super().__init__(distance_metric, n_features=X.shape[1])
# Swapping arguments on the constructor
self.datasets_pair = SparseDenseDatasetsPair{{name_suffix}}(Y, X, distance_metric)
@final
cdef intp_t n_samples_X(self) noexcept nogil:
# Swapping interface
return self.datasets_pair.n_samples_Y()
@final
cdef intp_t n_samples_Y(self) noexcept nogil:
# Swapping interface
return self.datasets_pair.n_samples_X()
@final
cdef float64_t surrogate_dist(self, intp_t i, intp_t j) noexcept nogil:
# Swapping arguments on the same interface
return self.datasets_pair.surrogate_dist(j, i)
@final
cdef float64_t dist(self, intp_t i, intp_t j) noexcept nogil:
# Swapping arguments on the same interface
return self.datasets_pair.dist(j, i)
{{endfor}}
import copy
{{py:
implementation_specific_values = [
# Values are the following ones:
#
# name_suffix, INPUT_DTYPE_t, INPUT_DTYPE
('64', 'DistanceMetric64', 'float64_t', 'np.float64'),
('32', 'DistanceMetric32', 'float32_t', 'np.float32')
]
}}
import numpy as np
from cython cimport final
from ...utils._typedefs cimport float64_t, float32_t, intp_t
from scipy.sparse import issparse, csr_matrix
{{for name_suffix, DistanceMetric, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
cdef class DatasetsPair{{name_suffix}}:
"""Abstract class which wraps a pair of datasets (X, Y).
This class allows computing distances between a single pair of rows of
of X and Y at a time given the pair of their indices (i, j). This class is
specialized for each metric thanks to the :func:`get_for` factory classmethod.
The handling of parallelization over chunks to compute the distances
and aggregation for several rows at a time is done in dedicated
subclasses of :class:`BaseDistancesReductionDispatcher` that in-turn rely on
subclasses of :class:`DatasetsPair` for each pair of rows in the data. The
goal is to make it possible to decouple the generic parallelization and
aggregation logic from metric-specific computation as much as possible.
X and Y can be stored as C-contiguous np.ndarrays or CSR matrices
in subclasses.
This class avoids the overhead of dispatching distance computations
to :class:`sklearn.metrics.DistanceMetric` based on the physical
representation of the vectors (sparse vs. dense). It makes use of
cython.final to remove the overhead of dispatching method calls.
Parameters
----------
distance_metric: {{DistanceMetric}}
The distance metric responsible for computing distances
between two vectors of (X, Y).
"""
@classmethod
def get_for(
cls,
X,
Y,
metric="euclidean",
dict metric_kwargs=None,
) -> DatasetsPair{{name_suffix}}:
"""Return the DatasetsPair implementation for the given arguments.
Parameters
----------
X : {ndarray, sparse matrix} of shape (n_samples_X, n_features)
Input data.
If provided as a ndarray, it must be C-contiguous.
If provided as a sparse matrix, it must be in CSR format.
Y : {ndarray, sparse matrix} of shape (n_samples_Y, n_features)
Input data.
If provided as a ndarray, it must be C-contiguous.
If provided as a sparse matrix, it must be in CSR format.
metric : str or DistanceMetric object, default='euclidean'
The distance metric to compute between rows of X and Y.
The default metric is a fast implementation of the Euclidean
metric. For a list of available metrics, see the documentation
of :class:`~sklearn.metrics.DistanceMetric`.
metric_kwargs : dict, default=None
Keyword arguments to pass to specified metric function.
Returns
-------
datasets_pair: DatasetsPair{{name_suffix}}
The suited DatasetsPair{{name_suffix}} implementation.
"""
# X_norm_squared and Y_norm_squared might be propagated
# down to DatasetsPairs via metrics_kwargs when the Euclidean
# specialisations can't be used.
# To prevent X_norm_squared and Y_norm_squared to be passed
# down to DistanceMetrics (whose constructors would raise
# a RuntimeError), we pop them here.
if metric_kwargs is not None:
# Copying metric_kwargs not to pop "X_norm_squared"
# and "Y_norm_squared" where they are used
metric_kwargs = copy.copy(metric_kwargs)
metric_kwargs.pop("X_norm_squared", None)
metric_kwargs.pop("Y_norm_squared", None)
cdef:
{{DistanceMetric}} distance_metric = DistanceMetric.get_metric(
metric,
{{INPUT_DTYPE}},
**(metric_kwargs or {})
)
# Metric-specific checks that do not replace nor duplicate `check_array`.
distance_metric._validate_data(X)
distance_metric._validate_data(Y)
X_is_sparse = issparse(X)
Y_is_sparse = issparse(Y)
if not X_is_sparse and not Y_is_sparse:
return DenseDenseDatasetsPair{{name_suffix}}(X, Y, distance_metric)
if X_is_sparse and Y_is_sparse:
return SparseSparseDatasetsPair{{name_suffix}}(X, Y, distance_metric)
if X_is_sparse and not Y_is_sparse:
return SparseDenseDatasetsPair{{name_suffix}}(X, Y, distance_metric)
return DenseSparseDatasetsPair{{name_suffix}}(X, Y, distance_metric)
@classmethod
def unpack_csr_matrix(cls, X: csr_matrix):
"""Ensure that the CSR matrix is indexed with np.int32."""
X_data = np.asarray(X.data, dtype={{INPUT_DTYPE}})
X_indices = np.asarray(X.indices, dtype=np.int32)
X_indptr = np.asarray(X.indptr, dtype=np.int32)
return X_data, X_indices, X_indptr
def __init__(self, {{DistanceMetric}} distance_metric, intp_t n_features):
self.distance_metric = distance_metric
self.n_features = n_features
cdef intp_t n_samples_X(self) noexcept nogil:
"""Number of samples in X."""
# This is a abstract method.
# This _must_ always be overwritten in subclasses.
# TODO: add "with gil: raise" here when supporting Cython 3.0
return -999
cdef intp_t n_samples_Y(self) noexcept nogil:
"""Number of samples in Y."""
# This is a abstract method.
# This _must_ always be overwritten in subclasses.
# TODO: add "with gil: raise" here when supporting Cython 3.0
return -999
cdef float64_t surrogate_dist(self, intp_t i, intp_t j) noexcept nogil:
return self.dist(i, j)
cdef float64_t dist(self, intp_t i, intp_t j) noexcept nogil:
# This is a abstract method.
# This _must_ always be overwritten in subclasses.
# TODO: add "with gil: raise" here when supporting Cython 3.0
return -1
@final
cdef class DenseDenseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
"""Compute distances between row vectors of two arrays.
Parameters
----------
X: ndarray of shape (n_samples_X, n_features)
Rows represent vectors. Must be C-contiguous.
Y: ndarray of shape (n_samples_Y, n_features)
Rows represent vectors. Must be C-contiguous.
distance_metric: DistanceMetric
The distance metric responsible for computing distances
between two row vectors of (X, Y).
"""
def __init__(
self,
const {{INPUT_DTYPE_t}}[:, ::1] X,
const {{INPUT_DTYPE_t}}[:, ::1] Y,
{{DistanceMetric}} distance_metric,
):
super().__init__(distance_metric, n_features=X.shape[1])
# Arrays have already been checked
self.X = X
self.Y = Y
@final
cdef intp_t n_samples_X(self) noexcept nogil:
return self.X.shape[0]
@final
cdef intp_t n_samples_Y(self) noexcept nogil:
return self.Y.shape[0]
@final
cdef float64_t surrogate_dist(self, intp_t i, intp_t j) noexcept nogil:
return self.distance_metric.rdist(&self.X[i, 0], &self.Y[j, 0], self.n_features)
@final
cdef float64_t dist(self, intp_t i, intp_t j) noexcept nogil:
return self.distance_metric.dist(&self.X[i, 0], &self.Y[j, 0], self.n_features)
@final
cdef class SparseSparseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
"""Compute distances between vectors of two CSR matrices.
Parameters
----------
X: sparse matrix of shape (n_samples_X, n_features)
Rows represent vectors. Must be in CSR format.
Y: sparse matrix of shape (n_samples_Y, n_features)
Rows represent vectors. Must be in CSR format.
distance_metric: DistanceMetric
The distance metric responsible for computing distances
between two vectors of (X, Y).
"""
def __init__(self, X, Y, {{DistanceMetric}} distance_metric):
super().__init__(distance_metric, n_features=X.shape[1])
self.X_data, self.X_indices, self.X_indptr = self.unpack_csr_matrix(X)
self.Y_data, self.Y_indices, self.Y_indptr = self.unpack_csr_matrix(Y)
@final
cdef intp_t n_samples_X(self) noexcept nogil:
return self.X_indptr.shape[0] - 1
@final
cdef intp_t n_samples_Y(self) noexcept nogil:
return self.Y_indptr.shape[0] - 1
@final
cdef float64_t surrogate_dist(self, intp_t i, intp_t j) noexcept nogil:
return self.distance_metric.rdist_csr(
x1_data=&self.X_data[0],
x1_indices=&self.X_indices[0],
x2_data=&self.Y_data[0],
x2_indices=&self.Y_indices[0],
x1_start=self.X_indptr[i],
x1_end=self.X_indptr[i + 1],
x2_start=self.Y_indptr[j],
x2_end=self.Y_indptr[j + 1],
size=self.n_features,
)
@final
cdef float64_t dist(self, intp_t i, intp_t j) noexcept nogil:
return self.distance_metric.dist_csr(
x1_data=&self.X_data[0],
x1_indices=&self.X_indices[0],
x2_data=&self.Y_data[0],
x2_indices=&self.Y_indices[0],
x1_start=self.X_indptr[i],
x1_end=self.X_indptr[i + 1],
x2_start=self.Y_indptr[j],
x2_end=self.Y_indptr[j + 1],
size=self.n_features,
)
@final
cdef class SparseDenseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
"""Compute distances between vectors of a CSR matrix and a dense array.
Parameters
----------
X: sparse matrix of shape (n_samples_X, n_features)
Rows represent vectors. Must be in CSR format.
Y: ndarray of shape (n_samples_Y, n_features)
Rows represent vectors. Must be C-contiguous.
distance_metric: DistanceMetric
The distance metric responsible for computing distances
between two vectors of (X, Y).
"""
def __init__(self, X, Y, {{DistanceMetric}} distance_metric):
super().__init__(distance_metric, n_features=X.shape[1])
self.X_data, self.X_indices, self.X_indptr = self.unpack_csr_matrix(X)
# We support the sparse-dense case by using the sparse-sparse interfaces
# of `DistanceMetric` (namely `DistanceMetric.{dist_csr,rdist_csr}`) to
# avoid introducing a new complex set of interfaces. In this case, we
# need to convert `Y` (the dense array) into a CSR matrix.
#
# Here we motive using another simpler CSR representation to use for `Y`.
#
# If we were to use the usual CSR representation for `Y`, storing all
# the columns indices in `indices` would have required allocating an
# array of n_samples × n_features elements with repeated contiguous
# integers from 0 to n_features - 1. This would have been very wasteful
# from a memory point of view. This alternative representation just uses
# the necessary amount of information needed and only necessitates
# shifting the address of `data` before calling the CSR × CSR routines.
#
# In this representation:
#
# - the `data` array is the original dense array, `Y`, whose first
# element's address is shifted before calling the CSR × CSR routine
#
# - the `indices` array is a single row of `n_features` elements:
#
# [0, 1, ..., n_features-1]
#
# - the `indptr` array is not materialised as the indices pointers'
# offset is constant (the offset equals `n_features`). Moreover, as
# `data` is shifted, constant `start` and `end` indices pointers
# respectively equalling 0 and n_features are used.
# Y array already has been checked here
self.n_Y = Y.shape[0]
self.Y_data = np.ravel(Y)
self.Y_indices = np.arange(self.n_features, dtype=np.int32)
@final
cdef intp_t n_samples_X(self) noexcept nogil:
return self.X_indptr.shape[0] - 1
@final
cdef intp_t n_samples_Y(self) noexcept nogil:
return self.n_Y
@final
cdef float64_t surrogate_dist(self, intp_t i, intp_t j) noexcept nogil:
return self.distance_metric.rdist_csr(
x1_data=&self.X_data[0],
x1_indices=&self.X_indices[0],
# Increment the data pointer such that x2_start=0 is aligned with the
# j-th row
x2_data=&self.Y_data[0] + j * self.n_features,
x2_indices=&self.Y_indices[0],
x1_start=self.X_indptr[i],
x1_end=self.X_indptr[i + 1],
x2_start=0,
x2_end=self.n_features,
size=self.n_features,
)
@final
cdef float64_t dist(self, intp_t i, intp_t j) noexcept nogil:
return self.distance_metric.dist_csr(
x1_data=&self.X_data[0],
x1_indices=&self.X_indices[0],
# Increment the data pointer such that x2_start=0 is aligned with the
# j-th row
x2_data=&self.Y_data[0] + j * self.n_features,
x2_indices=&self.Y_indices[0],
x1_start=self.X_indptr[i],
x1_end=self.X_indptr[i + 1],
x2_start=0,
x2_end=self.n_features,
size=self.n_features,
)
@final
cdef class DenseSparseDatasetsPair{{name_suffix}}(DatasetsPair{{name_suffix}}):
"""Compute distances between vectors of a dense array and a CSR matrix.
Parameters
----------
X: ndarray of shape (n_samples_X, n_features)
Rows represent vectors. Must be C-contiguous.
Y: sparse matrix of shape (n_samples_Y, n_features)
Rows represent vectors. Must be in CSR format.
distance_metric: DistanceMetric
The distance metric responsible for computing distances
between two vectors of (X, Y).
"""
def __init__(self, X, Y, {{DistanceMetric}} distance_metric):
super().__init__(distance_metric, n_features=X.shape[1])
# Swapping arguments on the constructor
self.datasets_pair = SparseDenseDatasetsPair{{name_suffix}}(Y, X, distance_metric)
@final
cdef intp_t n_samples_X(self) noexcept nogil:
# Swapping interface
return self.datasets_pair.n_samples_Y()
@final
cdef intp_t n_samples_Y(self) noexcept nogil:
# Swapping interface
return self.datasets_pair.n_samples_X()
@final
cdef float64_t surrogate_dist(self, intp_t i, intp_t j) noexcept nogil:
# Swapping arguments on the same interface
return self.datasets_pair.surrogate_dist(j, i)
@final
cdef float64_t dist(self, intp_t i, intp_t j) noexcept nogil:
# Swapping arguments on the same interface
return self.datasets_pair.dist(j, i)
{{endfor}}
@@ -1,228 +1,228 @@
{{py:
implementation_specific_values = [
# Values are the following ones:
#
# name_suffix, upcast_to_float64, INPUT_DTYPE_t, INPUT_DTYPE
#
# We also use the float64 dtype and C-type names as defined in
# `sklearn.utils._typedefs` to maintain consistency.
#
('64', False, 'float64_t', 'np.float64'),
('32', True, 'float32_t', 'np.float32')
]
}}
from libcpp.vector cimport vector
from ...utils._typedefs cimport float64_t, float32_t, int32_t, intp_t
cdef void _middle_term_sparse_sparse_64(
const float64_t[:] X_data,
const int32_t[:] X_indices,
const int32_t[:] X_indptr,
intp_t X_start,
intp_t X_end,
const float64_t[:] Y_data,
const int32_t[:] Y_indices,
const int32_t[:] Y_indptr,
intp_t Y_start,
intp_t Y_end,
float64_t * D,
) noexcept nogil
{{for name_suffix, upcast_to_float64, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
cdef class MiddleTermComputer{{name_suffix}}:
cdef:
intp_t effective_n_threads
intp_t chunks_n_threads
intp_t dist_middle_terms_chunks_size
intp_t n_features
intp_t chunk_size
# Buffers for the `-2 * X_c @ Y_c.T` term computed via GEMM
vector[vector[float64_t]] dist_middle_terms_chunks
cdef void _parallel_on_X_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
cdef void _parallel_on_X_parallel_init(self, intp_t thread_num) noexcept nogil
cdef void _parallel_on_X_init_chunk(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_Y_init(self) noexcept nogil
cdef void _parallel_on_Y_parallel_init(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_Y_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num
) noexcept nogil
cdef float64_t * _compute_dist_middle_terms(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
cdef class DenseDenseMiddleTermComputer{{name_suffix}}(MiddleTermComputer{{name_suffix}}):
cdef:
const {{INPUT_DTYPE_t}}[:, ::1] X
const {{INPUT_DTYPE_t}}[:, ::1] Y
{{if upcast_to_float64}}
# Buffers for upcasting chunks of X and Y from 32bit to 64bit
vector[vector[float64_t]] X_c_upcast
vector[vector[float64_t]] Y_c_upcast
{{endif}}
cdef void _parallel_on_X_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
cdef void _parallel_on_X_init_chunk(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_Y_parallel_init(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_Y_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num
) noexcept nogil
cdef float64_t * _compute_dist_middle_terms(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
cdef class SparseSparseMiddleTermComputer{{name_suffix}}(MiddleTermComputer{{name_suffix}}):
cdef:
const float64_t[:] X_data
const int32_t[:] X_indices
const int32_t[:] X_indptr
const float64_t[:] Y_data
const int32_t[:] Y_indices
const int32_t[:] Y_indptr
cdef void _parallel_on_X_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num
) noexcept nogil
cdef void _parallel_on_Y_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num
) noexcept nogil
cdef float64_t * _compute_dist_middle_terms(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
cdef class SparseDenseMiddleTermComputer{{name_suffix}}(MiddleTermComputer{{name_suffix}}):
cdef:
const float64_t[:] X_data
const int32_t[:] X_indices
const int32_t[:] X_indptr
const {{INPUT_DTYPE_t}}[:, ::1] Y
# We treat the dense-sparse case with the sparse-dense case by simply
# treating the dist_middle_terms as F-ordered and by swapping arguments.
# This attribute is meant to encode the case and adapt the logic
# accordingly.
bint c_ordered_middle_term
cdef void _parallel_on_X_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num
) noexcept nogil
cdef void _parallel_on_Y_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num
) noexcept nogil
cdef float64_t * _compute_dist_middle_terms(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
{{endfor}}
{{py:
implementation_specific_values = [
# Values are the following ones:
#
# name_suffix, upcast_to_float64, INPUT_DTYPE_t, INPUT_DTYPE
#
# We also use the float64 dtype and C-type names as defined in
# `sklearn.utils._typedefs` to maintain consistency.
#
('64', False, 'float64_t', 'np.float64'),
('32', True, 'float32_t', 'np.float32')
]
}}
from libcpp.vector cimport vector
from ...utils._typedefs cimport float64_t, float32_t, int32_t, intp_t
cdef void _middle_term_sparse_sparse_64(
const float64_t[:] X_data,
const int32_t[:] X_indices,
const int32_t[:] X_indptr,
intp_t X_start,
intp_t X_end,
const float64_t[:] Y_data,
const int32_t[:] Y_indices,
const int32_t[:] Y_indptr,
intp_t Y_start,
intp_t Y_end,
float64_t * D,
) noexcept nogil
{{for name_suffix, upcast_to_float64, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
cdef class MiddleTermComputer{{name_suffix}}:
cdef:
intp_t effective_n_threads
intp_t chunks_n_threads
intp_t dist_middle_terms_chunks_size
intp_t n_features
intp_t chunk_size
# Buffers for the `-2 * X_c @ Y_c.T` term computed via GEMM
vector[vector[float64_t]] dist_middle_terms_chunks
cdef void _parallel_on_X_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
cdef void _parallel_on_X_parallel_init(self, intp_t thread_num) noexcept nogil
cdef void _parallel_on_X_init_chunk(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_Y_init(self) noexcept nogil
cdef void _parallel_on_Y_parallel_init(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_Y_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num
) noexcept nogil
cdef float64_t * _compute_dist_middle_terms(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
cdef class DenseDenseMiddleTermComputer{{name_suffix}}(MiddleTermComputer{{name_suffix}}):
cdef:
const {{INPUT_DTYPE_t}}[:, ::1] X
const {{INPUT_DTYPE_t}}[:, ::1] Y
{{if upcast_to_float64}}
# Buffers for upcasting chunks of X and Y from 32bit to 64bit
vector[vector[float64_t]] X_c_upcast
vector[vector[float64_t]] Y_c_upcast
{{endif}}
cdef void _parallel_on_X_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
cdef void _parallel_on_X_init_chunk(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_Y_parallel_init(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil
cdef void _parallel_on_Y_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num
) noexcept nogil
cdef float64_t * _compute_dist_middle_terms(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
cdef class SparseSparseMiddleTermComputer{{name_suffix}}(MiddleTermComputer{{name_suffix}}):
cdef:
const float64_t[:] X_data
const int32_t[:] X_indices
const int32_t[:] X_indptr
const float64_t[:] Y_data
const int32_t[:] Y_indices
const int32_t[:] Y_indptr
cdef void _parallel_on_X_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num
) noexcept nogil
cdef void _parallel_on_Y_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num
) noexcept nogil
cdef float64_t * _compute_dist_middle_terms(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
cdef class SparseDenseMiddleTermComputer{{name_suffix}}(MiddleTermComputer{{name_suffix}}):
cdef:
const float64_t[:] X_data
const int32_t[:] X_indices
const int32_t[:] X_indptr
const {{INPUT_DTYPE_t}}[:, ::1] Y
# We treat the dense-sparse case with the sparse-dense case by simply
# treating the dist_middle_terms as F-ordered and by swapping arguments.
# This attribute is meant to encode the case and adapt the logic
# accordingly.
bint c_ordered_middle_term
cdef void _parallel_on_X_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num
) noexcept nogil
cdef void _parallel_on_Y_pre_compute_and_reduce_distances_on_chunks(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num
) noexcept nogil
cdef float64_t * _compute_dist_middle_terms(
self,
intp_t X_start,
intp_t X_end,
intp_t Y_start,
intp_t Y_end,
intp_t thread_num,
) noexcept nogil
{{endfor}}
@@ -1,90 +1,90 @@
cimport numpy as cnp
from libcpp.memory cimport shared_ptr
from libcpp.vector cimport vector
from cython cimport final
from ...utils._typedefs cimport intp_t, float64_t
cnp.import_array()
######################
## std::vector to np.ndarray coercion
# As type covariance is not supported for C++ containers via Cython,
# we need to redefine fused types.
ctypedef fused vector_double_intp_t:
vector[intp_t]
vector[float64_t]
ctypedef fused vector_vector_double_intp_t:
vector[vector[intp_t]]
vector[vector[float64_t]]
cdef cnp.ndarray[object, ndim=1] coerce_vectors_to_nd_arrays(
shared_ptr[vector_vector_double_intp_t] vecs
)
#####################
{{for name_suffix in ['64', '32']}}
from ._base cimport BaseDistancesReduction{{name_suffix}}
from ._middle_term_computer cimport MiddleTermComputer{{name_suffix}}
cdef class RadiusNeighbors{{name_suffix}}(BaseDistancesReduction{{name_suffix}}):
"""float{{name_suffix}} implementation of the RadiusNeighbors."""
cdef:
float64_t radius
# DistanceMetric{{name_suffix}} compute rank-preserving surrogate distance via rdist
# which are proxies necessitating less computations.
# We get the equivalent for the radius to be able to compare it against
# vectors' rank-preserving surrogate distances.
float64_t r_radius
# Neighbors indices and distances are returned as np.ndarrays of np.ndarrays.
#
# For this implementation, we want resizable buffers which we will wrap
# into numpy arrays at the end. std::vector comes as a handy container
# for interacting efficiently with resizable buffers.
#
# Though it is possible to access their buffer address with
# std::vector::data, they can't be stolen: buffers lifetime
# is tied to their std::vector and are deallocated when
# std::vectors are.
#
# To solve this, we dynamically allocate std::vectors and then
# encapsulate them in a StdVectorSentinel responsible for
# freeing them when the associated np.ndarray is freed.
#
# Shared pointers (defined via shared_ptr) are use for safer memory management.
# Unique pointers (defined via unique_ptr) can't be used as datastructures
# are shared across threads for parallel_on_X; see _parallel_on_X_init_chunk.
shared_ptr[vector[vector[intp_t]]] neigh_indices
shared_ptr[vector[vector[float64_t]]] neigh_distances
# Used as array of pointers to private datastructures used in threads.
vector[shared_ptr[vector[vector[intp_t]]]] neigh_indices_chunks
vector[shared_ptr[vector[vector[float64_t]]]] neigh_distances_chunks
bint sort_results
@final
cdef void _merge_vectors(
self,
intp_t idx,
intp_t num_threads,
) noexcept nogil
cdef class EuclideanRadiusNeighbors{{name_suffix}}(RadiusNeighbors{{name_suffix}}):
"""EuclideanDistance-specialisation of RadiusNeighbors{{name_suffix}}."""
cdef:
MiddleTermComputer{{name_suffix}} middle_term_computer
const float64_t[::1] X_norm_squared
const float64_t[::1] Y_norm_squared
bint use_squared_distances
{{endfor}}
cimport numpy as cnp
from libcpp.memory cimport shared_ptr
from libcpp.vector cimport vector
from cython cimport final
from ...utils._typedefs cimport intp_t, float64_t
cnp.import_array()
######################
## std::vector to np.ndarray coercion
# As type covariance is not supported for C++ containers via Cython,
# we need to redefine fused types.
ctypedef fused vector_double_intp_t:
vector[intp_t]
vector[float64_t]
ctypedef fused vector_vector_double_intp_t:
vector[vector[intp_t]]
vector[vector[float64_t]]
cdef cnp.ndarray[object, ndim=1] coerce_vectors_to_nd_arrays(
shared_ptr[vector_vector_double_intp_t] vecs
)
#####################
{{for name_suffix in ['64', '32']}}
from ._base cimport BaseDistancesReduction{{name_suffix}}
from ._middle_term_computer cimport MiddleTermComputer{{name_suffix}}
cdef class RadiusNeighbors{{name_suffix}}(BaseDistancesReduction{{name_suffix}}):
"""float{{name_suffix}} implementation of the RadiusNeighbors."""
cdef:
float64_t radius
# DistanceMetric{{name_suffix}} compute rank-preserving surrogate distance via rdist
# which are proxies necessitating less computations.
# We get the equivalent for the radius to be able to compare it against
# vectors' rank-preserving surrogate distances.
float64_t r_radius
# Neighbors indices and distances are returned as np.ndarrays of np.ndarrays.
#
# For this implementation, we want resizable buffers which we will wrap
# into numpy arrays at the end. std::vector comes as a handy container
# for interacting efficiently with resizable buffers.
#
# Though it is possible to access their buffer address with
# std::vector::data, they can't be stolen: buffers lifetime
# is tied to their std::vector and are deallocated when
# std::vectors are.
#
# To solve this, we dynamically allocate std::vectors and then
# encapsulate them in a StdVectorSentinel responsible for
# freeing them when the associated np.ndarray is freed.
#
# Shared pointers (defined via shared_ptr) are use for safer memory management.
# Unique pointers (defined via unique_ptr) can't be used as datastructures
# are shared across threads for parallel_on_X; see _parallel_on_X_init_chunk.
shared_ptr[vector[vector[intp_t]]] neigh_indices
shared_ptr[vector[vector[float64_t]]] neigh_distances
# Used as array of pointers to private datastructures used in threads.
vector[shared_ptr[vector[vector[intp_t]]]] neigh_indices_chunks
vector[shared_ptr[vector[vector[float64_t]]]] neigh_distances_chunks
bint sort_results
@final
cdef void _merge_vectors(
self,
intp_t idx,
intp_t num_threads,
) noexcept nogil
cdef class EuclideanRadiusNeighbors{{name_suffix}}(RadiusNeighbors{{name_suffix}}):
"""EuclideanDistance-specialisation of RadiusNeighbors{{name_suffix}}."""
cdef:
MiddleTermComputer{{name_suffix}} middle_term_computer
const float64_t[::1] X_norm_squared
const float64_t[::1] Y_norm_squared
bint use_squared_distances
{{endfor}}
@@ -1,217 +1,217 @@
import warnings
from cython cimport floating, final, integral
from cython.operator cimport dereference as deref
from cython.parallel cimport parallel, prange
from ._classmode cimport WeightingStrategy
from ...utils._typedefs cimport intp_t, float64_t, uint8_t
import numpy as np
from scipy.sparse import issparse
from ...utils.parallel import _get_threadpool_controller
{{for name_suffix in ["32", "64"]}}
from ._radius_neighbors cimport RadiusNeighbors{{name_suffix}}
from ._datasets_pair cimport DatasetsPair{{name_suffix}}
cdef class RadiusNeighborsClassMode{{name_suffix}}(RadiusNeighbors{{name_suffix}}):
"""
{{name_suffix}}bit implementation of RadiusNeighborsClassMode.
"""
cdef:
const intp_t[::1] Y_labels
const intp_t[::1] unique_Y_labels
intp_t outlier_label_index
bint outlier_label_exists
bint outliers_exist
uint8_t[::1] outliers
object outlier_label
float64_t[:, ::1] class_scores
WeightingStrategy weight_type
@classmethod
def compute(
cls,
X,
Y,
float64_t radius,
weights,
Y_labels,
unique_Y_labels,
outlier_label=None,
str metric="euclidean",
chunk_size=None,
dict metric_kwargs=None,
str strategy=None,
):
# Use a generic implementation that handles most scipy
# metrics by computing the distances between 2 vectors at a time.
pda = RadiusNeighborsClassMode{{name_suffix}}(
datasets_pair=DatasetsPair{{name_suffix}}.get_for(X, Y, metric, metric_kwargs),
radius=radius,
chunk_size=chunk_size,
strategy=strategy,
weights=weights,
Y_labels=Y_labels,
unique_Y_labels=unique_Y_labels,
outlier_label=outlier_label,
)
# Limit the number of threads in second level of nested parallelism for BLAS
# to avoid threads over-subscription (in GEMM for instance).
with _get_threadpool_controller().limit(limits=1, user_api="blas"):
if pda.execute_in_parallel_on_Y:
pda._parallel_on_Y()
else:
pda._parallel_on_X()
return pda._finalize_results()
def __init__(
self,
DatasetsPair{{name_suffix}} datasets_pair,
const intp_t[::1] Y_labels,
const intp_t[::1] unique_Y_labels,
float64_t radius,
chunk_size=None,
strategy=None,
weights=None,
outlier_label=None,
):
super().__init__(
datasets_pair=datasets_pair,
chunk_size=chunk_size,
strategy=strategy,
radius=radius,
)
if weights == "uniform":
self.weight_type = WeightingStrategy.uniform
elif weights == "distance":
self.weight_type = WeightingStrategy.distance
else:
self.weight_type = WeightingStrategy.callable
self.Y_labels = Y_labels
self.unique_Y_labels = unique_Y_labels
self.outlier_label_index = -1
self.outliers_exist = False
self.outlier_label = outlier_label
self.outliers = np.zeros(self.n_samples_X, dtype=np.bool_)
cdef intp_t idx
if self.outlier_label is not None:
for idx in range(self.unique_Y_labels.shape[0]):
if self.unique_Y_labels[idx] == outlier_label:
self.outlier_label_index = idx
# Map from set of unique labels to their indices in `class_scores`
# Buffer used in building a histogram for one-pass weighted mode
self.class_scores = np.zeros(
(self.n_samples_X, unique_Y_labels.shape[0]), dtype=np.float64,
)
cdef inline void weighted_histogram_mode(
self,
intp_t sample_index,
intp_t sample_n_neighbors,
intp_t* indices,
float64_t* distances,
) noexcept nogil:
cdef:
intp_t neighbor_idx, neighbor_class_idx, label_index
float64_t score_incr = 1
bint use_distance_weighting = (
self.weight_type == WeightingStrategy.distance
)
if sample_n_neighbors == 0:
self.outliers_exist = True
self.outliers[sample_index] = True
if self.outlier_label_index >= 0:
self.class_scores[sample_index][self.outlier_label_index] = score_incr
return
# Iterate over the neighbors. This can be different for
# each of the samples as they are based on the radius.
for neighbor_rank in range(sample_n_neighbors):
if use_distance_weighting:
score_incr = 1 / distances[neighbor_rank]
neighbor_idx = indices[neighbor_rank]
neighbor_class_idx = self.Y_labels[neighbor_idx]
self.class_scores[sample_index][neighbor_class_idx] += score_incr
return
@final
cdef void _parallel_on_X_prange_iter_finalize(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil:
cdef:
intp_t idx
for idx in range(X_start, X_end):
self.weighted_histogram_mode(
sample_index=idx,
sample_n_neighbors=deref(self.neigh_indices)[idx].size(),
indices=deref(self.neigh_indices)[idx].data(),
distances=deref(self.neigh_distances)[idx].data(),
)
return
@final
cdef void _parallel_on_Y_finalize(
self,
) noexcept nogil:
cdef:
intp_t idx
with nogil, parallel(num_threads=self.effective_n_threads):
# Merge vectors used in threads into the main ones.
# This is done in parallel sample-wise (no need for locks).
for idx in prange(self.n_samples_X, schedule='static'):
self._merge_vectors(idx, self.chunks_n_threads)
for idx in prange(self.n_samples_X, schedule='static'):
self.weighted_histogram_mode(
sample_index=idx,
sample_n_neighbors=deref(self.neigh_indices)[idx].size(),
indices=deref(self.neigh_indices)[idx].data(),
distances=deref(self.neigh_distances)[idx].data(),
)
return
def _finalize_results(self):
if self.outliers_exist and self.outlier_label is None:
raise ValueError(
"No neighbors found for test samples %r, "
"you can try using larger radius, "
"giving a label for outliers, "
"or considering removing them from your dataset."
% np.where(self.outliers)[0]
)
if self.outliers_exist and self.outlier_label_index < 0:
warnings.warn(
"Outlier label %s is not in training "
"classes. All class probabilities of "
"outliers will be assigned with 0."
% self.outlier_label
)
probabilities = np.asarray(self.class_scores)
normalizer = probabilities.sum(axis=1, keepdims=True)
normalizer[normalizer == 0.0] = 1.0
probabilities /= normalizer
return probabilities
{{endfor}}
import warnings
from cython cimport floating, final, integral
from cython.operator cimport dereference as deref
from cython.parallel cimport parallel, prange
from ._classmode cimport WeightingStrategy
from ...utils._typedefs cimport intp_t, float64_t, uint8_t
import numpy as np
from scipy.sparse import issparse
from ...utils.parallel import _get_threadpool_controller
{{for name_suffix in ["32", "64"]}}
from ._radius_neighbors cimport RadiusNeighbors{{name_suffix}}
from ._datasets_pair cimport DatasetsPair{{name_suffix}}
cdef class RadiusNeighborsClassMode{{name_suffix}}(RadiusNeighbors{{name_suffix}}):
"""
{{name_suffix}}bit implementation of RadiusNeighborsClassMode.
"""
cdef:
const intp_t[::1] Y_labels
const intp_t[::1] unique_Y_labels
intp_t outlier_label_index
bint outlier_label_exists
bint outliers_exist
uint8_t[::1] outliers
object outlier_label
float64_t[:, ::1] class_scores
WeightingStrategy weight_type
@classmethod
def compute(
cls,
X,
Y,
float64_t radius,
weights,
Y_labels,
unique_Y_labels,
outlier_label=None,
str metric="euclidean",
chunk_size=None,
dict metric_kwargs=None,
str strategy=None,
):
# Use a generic implementation that handles most scipy
# metrics by computing the distances between 2 vectors at a time.
pda = RadiusNeighborsClassMode{{name_suffix}}(
datasets_pair=DatasetsPair{{name_suffix}}.get_for(X, Y, metric, metric_kwargs),
radius=radius,
chunk_size=chunk_size,
strategy=strategy,
weights=weights,
Y_labels=Y_labels,
unique_Y_labels=unique_Y_labels,
outlier_label=outlier_label,
)
# Limit the number of threads in second level of nested parallelism for BLAS
# to avoid threads over-subscription (in GEMM for instance).
with _get_threadpool_controller().limit(limits=1, user_api="blas"):
if pda.execute_in_parallel_on_Y:
pda._parallel_on_Y()
else:
pda._parallel_on_X()
return pda._finalize_results()
def __init__(
self,
DatasetsPair{{name_suffix}} datasets_pair,
const intp_t[::1] Y_labels,
const intp_t[::1] unique_Y_labels,
float64_t radius,
chunk_size=None,
strategy=None,
weights=None,
outlier_label=None,
):
super().__init__(
datasets_pair=datasets_pair,
chunk_size=chunk_size,
strategy=strategy,
radius=radius,
)
if weights == "uniform":
self.weight_type = WeightingStrategy.uniform
elif weights == "distance":
self.weight_type = WeightingStrategy.distance
else:
self.weight_type = WeightingStrategy.callable
self.Y_labels = Y_labels
self.unique_Y_labels = unique_Y_labels
self.outlier_label_index = -1
self.outliers_exist = False
self.outlier_label = outlier_label
self.outliers = np.zeros(self.n_samples_X, dtype=np.bool_)
cdef intp_t idx
if self.outlier_label is not None:
for idx in range(self.unique_Y_labels.shape[0]):
if self.unique_Y_labels[idx] == outlier_label:
self.outlier_label_index = idx
# Map from set of unique labels to their indices in `class_scores`
# Buffer used in building a histogram for one-pass weighted mode
self.class_scores = np.zeros(
(self.n_samples_X, unique_Y_labels.shape[0]), dtype=np.float64,
)
cdef inline void weighted_histogram_mode(
self,
intp_t sample_index,
intp_t sample_n_neighbors,
intp_t* indices,
float64_t* distances,
) noexcept nogil:
cdef:
intp_t neighbor_idx, neighbor_class_idx, label_index
float64_t score_incr = 1
bint use_distance_weighting = (
self.weight_type == WeightingStrategy.distance
)
if sample_n_neighbors == 0:
self.outliers_exist = True
self.outliers[sample_index] = True
if self.outlier_label_index >= 0:
self.class_scores[sample_index][self.outlier_label_index] = score_incr
return
# Iterate over the neighbors. This can be different for
# each of the samples as they are based on the radius.
for neighbor_rank in range(sample_n_neighbors):
if use_distance_weighting:
score_incr = 1 / distances[neighbor_rank]
neighbor_idx = indices[neighbor_rank]
neighbor_class_idx = self.Y_labels[neighbor_idx]
self.class_scores[sample_index][neighbor_class_idx] += score_incr
return
@final
cdef void _parallel_on_X_prange_iter_finalize(
self,
intp_t thread_num,
intp_t X_start,
intp_t X_end,
) noexcept nogil:
cdef:
intp_t idx
for idx in range(X_start, X_end):
self.weighted_histogram_mode(
sample_index=idx,
sample_n_neighbors=deref(self.neigh_indices)[idx].size(),
indices=deref(self.neigh_indices)[idx].data(),
distances=deref(self.neigh_distances)[idx].data(),
)
return
@final
cdef void _parallel_on_Y_finalize(
self,
) noexcept nogil:
cdef:
intp_t idx
with nogil, parallel(num_threads=self.effective_n_threads):
# Merge vectors used in threads into the main ones.
# This is done in parallel sample-wise (no need for locks).
for idx in prange(self.n_samples_X, schedule='static'):
self._merge_vectors(idx, self.chunks_n_threads)
for idx in prange(self.n_samples_X, schedule='static'):
self.weighted_histogram_mode(
sample_index=idx,
sample_n_neighbors=deref(self.neigh_indices)[idx].size(),
indices=deref(self.neigh_indices)[idx].data(),
distances=deref(self.neigh_distances)[idx].data(),
)
return
def _finalize_results(self):
if self.outliers_exist and self.outlier_label is None:
raise ValueError(
"No neighbors found for test samples %r, "
"you can try using larger radius, "
"giving a label for outliers, "
"or considering removing them from your dataset."
% np.where(self.outliers)[0]
)
if self.outliers_exist and self.outlier_label_index < 0:
warnings.warn(
"Outlier label %s is not in training "
"classes. All class probabilities of "
"outliers will be assigned with 0."
% self.outlier_label
)
probabilities = np.asarray(self.class_scores)
normalizer = probabilities.sum(axis=1, keepdims=True)
normalizer[normalizer == 0.0] = 1.0
probabilities /= normalizer
return probabilities
{{endfor}}
@@ -1,193 +1,193 @@
# Note: the dependencies between different Cython files in
# _pairwise_distances_reduction is probably one of the most involved in
# scikit-learn. If you change this file make sure you build from scratch:
# rm -rf build; make dev-meson
# run a command like this:
# ninja -C build/cp312 -t missingdeps
# and make sure that the output is something like:
# No missing dependencies on generated files found.
# _pairwise_distances_reduction is cimported from other subpackages so this is
# needed for the cimport to work
_pairwise_distances_reduction_cython_tree = [
fs.copyfile('__init__.py'),
# We are in a sub-module of metrics, so we always need to have
# sklearn/metrics/__init__.py copied to the build directory to avoid the
# error:
# relative cimport beyond main package is not allowed
metrics_cython_tree
]
_classmode_pxd = fs.copyfile('_classmode.pxd')
_datasets_pair_pxd = custom_target(
'_datasets_pair_pxd',
output: '_datasets_pair.pxd',
input: '_datasets_pair.pxd.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@']
)
_datasets_pair_pyx = custom_target(
'_datasets_pair_pyx',
output: '_datasets_pair.pyx',
input: '_datasets_pair.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [_datasets_pair_pxd, _pairwise_distances_reduction_cython_tree, utils_cython_tree],
)
_datasets_pair = py.extension_module(
'_datasets_pair',
cython_gen_cpp.process(_datasets_pair_pyx),
dependencies: [np_dep],
subdir: 'sklearn/metrics/_pairwise_distances_reduction',
install: true
)
_base_pxd = custom_target(
'_base_pxd',
output: '_base.pxd',
input: '_base.pxd.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@']
)
_base_pyx = custom_target(
'_base_pyx',
output: '_base.pyx',
input: '_base.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [_base_pxd, _pairwise_distances_reduction_cython_tree,
_datasets_pair_pxd, utils_cython_tree],
)
_base = py.extension_module(
'_base',
cython_gen_cpp.process(_base_pyx),
dependencies: [np_dep, openmp_dep],
subdir: 'sklearn/metrics/_pairwise_distances_reduction',
install: true
)
_middle_term_computer_pxd = custom_target(
'_middle_term_computer_pxd',
output: '_middle_term_computer.pxd',
input: '_middle_term_computer.pxd.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@']
)
_middle_term_computer_pyx = custom_target(
'_middle_term_computer_pyx',
output: '_middle_term_computer.pyx',
input: '_middle_term_computer.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [_middle_term_computer_pxd,
_pairwise_distances_reduction_cython_tree,
utils_cython_tree],
)
_middle_term_computer = py.extension_module(
'_middle_term_computer',
cython_gen_cpp.process(_middle_term_computer_pyx),
dependencies: [np_dep],
subdir: 'sklearn/metrics/_pairwise_distances_reduction',
install: true
)
_argkmin_pxd = custom_target(
'_argkmin_pxd',
output: '_argkmin.pxd',
input: '_argkmin.pxd.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@']
)
_argkmin_pyx = custom_target(
'_argkmin_pyx',
output: '_argkmin.pyx',
input: '_argkmin.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [_argkmin_pxd,
_pairwise_distances_reduction_cython_tree,
_datasets_pair_pxd, _base_pxd, _middle_term_computer_pxd],
)
_argkmin = py.extension_module(
'_argkmin',
cython_gen_cpp.process(_argkmin_pyx),
dependencies: [np_dep, openmp_dep],
subdir: 'sklearn/metrics/_pairwise_distances_reduction',
install: true
)
_radius_neighbors_pxd = custom_target(
'_radius_neighbors_pxd',
output: '_radius_neighbors.pxd',
input: '_radius_neighbors.pxd.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@']
)
_radius_neighbors_pyx = custom_target(
'_radius_neighbors_pyx',
output: '_radius_neighbors.pyx',
input: '_radius_neighbors.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [_radius_neighbors_pxd,
_datasets_pair_pxd, _base_pxd, _middle_term_computer_pxd,
_pairwise_distances_reduction_cython_tree, utils_cython_tree],
)
_radius_neighbors = py.extension_module(
'_radius_neighbors',
cython_gen_cpp.process(_radius_neighbors_pyx),
dependencies: [np_dep, openmp_dep],
subdir: 'sklearn/metrics/_pairwise_distances_reduction',
install: true
)
_argkmin_classmode_pyx = custom_target(
'_argkmin_classmode_pyx',
output: '_argkmin_classmode.pyx',
input: '_argkmin_classmode.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [_classmode_pxd,
_argkmin_pxd, _pairwise_distances_reduction_cython_tree,
_datasets_pair_pxd, _base_pxd, _middle_term_computer_pxd, utils_cython_tree],
)
_argkmin_classmode = py.extension_module(
'_argkmin_classmode',
cython_gen_cpp.process(_argkmin_classmode_pyx),
dependencies: [np_dep, openmp_dep],
# XXX: for some reason -fno-sized-deallocation is needed otherwise there is
# an error with undefined symbol _ZdlPv at import time in manylinux wheels.
# See https://github.com/scikit-learn/scikit-learn/issues/28596 for more details.
cpp_args: ['-fno-sized-deallocation'],
subdir: 'sklearn/metrics/_pairwise_distances_reduction',
install: true
)
_radius_neighbors_classmode_pyx = custom_target(
'_radius_neighbors_classmode_pyx',
output: '_radius_neighbors_classmode.pyx',
input: '_radius_neighbors_classmode.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [_classmode_pxd,
_middle_term_computer_pxd, _radius_neighbors_pxd,
_pairwise_distances_reduction_cython_tree,
_datasets_pair_pxd, _base_pxd, utils_cython_tree],
)
_radius_neighbors_classmode = py.extension_module(
'_radius_neighbors_classmode',
cython_gen_cpp.process(_radius_neighbors_classmode_pyx),
dependencies: [np_dep, openmp_dep],
subdir: 'sklearn/metrics/_pairwise_distances_reduction',
install: true
)
# Note: the dependencies between different Cython files in
# _pairwise_distances_reduction is probably one of the most involved in
# scikit-learn. If you change this file make sure you build from scratch:
# rm -rf build; make dev-meson
# run a command like this:
# ninja -C build/cp312 -t missingdeps
# and make sure that the output is something like:
# No missing dependencies on generated files found.
# _pairwise_distances_reduction is cimported from other subpackages so this is
# needed for the cimport to work
_pairwise_distances_reduction_cython_tree = [
fs.copyfile('__init__.py'),
# We are in a sub-module of metrics, so we always need to have
# sklearn/metrics/__init__.py copied to the build directory to avoid the
# error:
# relative cimport beyond main package is not allowed
metrics_cython_tree
]
_classmode_pxd = fs.copyfile('_classmode.pxd')
_datasets_pair_pxd = custom_target(
'_datasets_pair_pxd',
output: '_datasets_pair.pxd',
input: '_datasets_pair.pxd.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@']
)
_datasets_pair_pyx = custom_target(
'_datasets_pair_pyx',
output: '_datasets_pair.pyx',
input: '_datasets_pair.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [_datasets_pair_pxd, _pairwise_distances_reduction_cython_tree, utils_cython_tree],
)
_datasets_pair = py.extension_module(
'_datasets_pair',
cython_gen_cpp.process(_datasets_pair_pyx),
dependencies: [np_dep],
subdir: 'sklearn/metrics/_pairwise_distances_reduction',
install: true
)
_base_pxd = custom_target(
'_base_pxd',
output: '_base.pxd',
input: '_base.pxd.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@']
)
_base_pyx = custom_target(
'_base_pyx',
output: '_base.pyx',
input: '_base.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [_base_pxd, _pairwise_distances_reduction_cython_tree,
_datasets_pair_pxd, utils_cython_tree],
)
_base = py.extension_module(
'_base',
cython_gen_cpp.process(_base_pyx),
dependencies: [np_dep, openmp_dep],
subdir: 'sklearn/metrics/_pairwise_distances_reduction',
install: true
)
_middle_term_computer_pxd = custom_target(
'_middle_term_computer_pxd',
output: '_middle_term_computer.pxd',
input: '_middle_term_computer.pxd.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@']
)
_middle_term_computer_pyx = custom_target(
'_middle_term_computer_pyx',
output: '_middle_term_computer.pyx',
input: '_middle_term_computer.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [_middle_term_computer_pxd,
_pairwise_distances_reduction_cython_tree,
utils_cython_tree],
)
_middle_term_computer = py.extension_module(
'_middle_term_computer',
cython_gen_cpp.process(_middle_term_computer_pyx),
dependencies: [np_dep],
subdir: 'sklearn/metrics/_pairwise_distances_reduction',
install: true
)
_argkmin_pxd = custom_target(
'_argkmin_pxd',
output: '_argkmin.pxd',
input: '_argkmin.pxd.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@']
)
_argkmin_pyx = custom_target(
'_argkmin_pyx',
output: '_argkmin.pyx',
input: '_argkmin.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [_argkmin_pxd,
_pairwise_distances_reduction_cython_tree,
_datasets_pair_pxd, _base_pxd, _middle_term_computer_pxd],
)
_argkmin = py.extension_module(
'_argkmin',
cython_gen_cpp.process(_argkmin_pyx),
dependencies: [np_dep, openmp_dep],
subdir: 'sklearn/metrics/_pairwise_distances_reduction',
install: true
)
_radius_neighbors_pxd = custom_target(
'_radius_neighbors_pxd',
output: '_radius_neighbors.pxd',
input: '_radius_neighbors.pxd.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@']
)
_radius_neighbors_pyx = custom_target(
'_radius_neighbors_pyx',
output: '_radius_neighbors.pyx',
input: '_radius_neighbors.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [_radius_neighbors_pxd,
_datasets_pair_pxd, _base_pxd, _middle_term_computer_pxd,
_pairwise_distances_reduction_cython_tree, utils_cython_tree],
)
_radius_neighbors = py.extension_module(
'_radius_neighbors',
cython_gen_cpp.process(_radius_neighbors_pyx),
dependencies: [np_dep, openmp_dep],
subdir: 'sklearn/metrics/_pairwise_distances_reduction',
install: true
)
_argkmin_classmode_pyx = custom_target(
'_argkmin_classmode_pyx',
output: '_argkmin_classmode.pyx',
input: '_argkmin_classmode.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [_classmode_pxd,
_argkmin_pxd, _pairwise_distances_reduction_cython_tree,
_datasets_pair_pxd, _base_pxd, _middle_term_computer_pxd, utils_cython_tree],
)
_argkmin_classmode = py.extension_module(
'_argkmin_classmode',
cython_gen_cpp.process(_argkmin_classmode_pyx),
dependencies: [np_dep, openmp_dep],
# XXX: for some reason -fno-sized-deallocation is needed otherwise there is
# an error with undefined symbol _ZdlPv at import time in manylinux wheels.
# See https://github.com/scikit-learn/scikit-learn/issues/28596 for more details.
cpp_args: ['-fno-sized-deallocation'],
subdir: 'sklearn/metrics/_pairwise_distances_reduction',
install: true
)
_radius_neighbors_classmode_pyx = custom_target(
'_radius_neighbors_classmode_pyx',
output: '_radius_neighbors_classmode.pyx',
input: '_radius_neighbors_classmode.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [_classmode_pxd,
_middle_term_computer_pxd, _radius_neighbors_pxd,
_pairwise_distances_reduction_cython_tree,
_datasets_pair_pxd, _base_pxd, utils_cython_tree],
)
_radius_neighbors_classmode = py.extension_module(
'_radius_neighbors_classmode',
cython_gen_cpp.process(_radius_neighbors_classmode_pyx),
dependencies: [np_dep, openmp_dep],
subdir: 'sklearn/metrics/_pairwise_distances_reduction',
install: true
)
@@ -1,107 +1,107 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from cython cimport floating
from cython.parallel cimport prange
from libc.math cimport fabs
from ..utils._typedefs cimport intp_t
from ..utils._openmp_helpers import _openmp_effective_n_threads
def _chi2_kernel_fast(floating[:, :] X,
floating[:, :] Y,
floating[:, :] result):
cdef intp_t i, j, k
cdef intp_t n_samples_X = X.shape[0]
cdef intp_t n_samples_Y = Y.shape[0]
cdef intp_t n_features = X.shape[1]
cdef double res, nom, denom
with nogil:
for i in range(n_samples_X):
for j in range(n_samples_Y):
res = 0
for k in range(n_features):
denom = (X[i, k] - Y[j, k])
nom = (X[i, k] + Y[j, k])
if nom != 0:
res += denom * denom / nom
result[i, j] = -res
def _sparse_manhattan(
const floating[::1] X_data,
const int[:] X_indices,
const int[:] X_indptr,
const floating[::1] Y_data,
const int[:] Y_indices,
const int[:] Y_indptr,
double[:, ::1] D,
):
"""Pairwise L1 distances for CSR matrices.
Usage:
>>> D = np.zeros(X.shape[0], Y.shape[0])
>>> _sparse_manhattan(X.data, X.indices, X.indptr,
... Y.data, Y.indices, Y.indptr,
... D)
"""
cdef intp_t px, py, i, j, ix, iy
cdef double d = 0.0
cdef int m = D.shape[0]
cdef int n = D.shape[1]
cdef int X_indptr_end = 0
cdef int Y_indptr_end = 0
cdef int num_threads = _openmp_effective_n_threads()
# We scan the matrices row by row.
# Given row px in X and row py in Y, we find the positions (i and j
# respectively), in .indices where the indices for the two rows start.
# If the indices (ix and iy) are the same, the corresponding data values
# are processed and the cursors i and j are advanced.
# If not, the lowest index is considered. Its associated data value is
# processed and its cursor is advanced.
# We proceed like this until one of the cursors hits the end for its row.
# Then we process all remaining data values in the other row.
# Below the avoidance of inplace operators is intentional.
# When prange is used, the inplace operator has a special meaning, i.e. it
# signals a "reduction"
for px in prange(m, nogil=True, num_threads=num_threads):
X_indptr_end = X_indptr[px + 1]
for py in range(n):
Y_indptr_end = Y_indptr[py + 1]
i = X_indptr[px]
j = Y_indptr[py]
d = 0.0
while i < X_indptr_end and j < Y_indptr_end:
ix = X_indices[i]
iy = Y_indices[j]
if ix == iy:
d = d + fabs(X_data[i] - Y_data[j])
i = i + 1
j = j + 1
elif ix < iy:
d = d + fabs(X_data[i])
i = i + 1
else:
d = d + fabs(Y_data[j])
j = j + 1
if i == X_indptr_end:
while j < Y_indptr_end:
d = d + fabs(Y_data[j])
j = j + 1
else:
while i < X_indptr_end:
d = d + fabs(X_data[i])
i = i + 1
D[px, py] = d
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from cython cimport floating
from cython.parallel cimport prange
from libc.math cimport fabs
from ..utils._typedefs cimport intp_t
from ..utils._openmp_helpers import _openmp_effective_n_threads
def _chi2_kernel_fast(floating[:, :] X,
floating[:, :] Y,
floating[:, :] result):
cdef intp_t i, j, k
cdef intp_t n_samples_X = X.shape[0]
cdef intp_t n_samples_Y = Y.shape[0]
cdef intp_t n_features = X.shape[1]
cdef double res, nom, denom
with nogil:
for i in range(n_samples_X):
for j in range(n_samples_Y):
res = 0
for k in range(n_features):
denom = (X[i, k] - Y[j, k])
nom = (X[i, k] + Y[j, k])
if nom != 0:
res += denom * denom / nom
result[i, j] = -res
def _sparse_manhattan(
const floating[::1] X_data,
const int[:] X_indices,
const int[:] X_indptr,
const floating[::1] Y_data,
const int[:] Y_indices,
const int[:] Y_indptr,
double[:, ::1] D,
):
"""Pairwise L1 distances for CSR matrices.
Usage:
>>> D = np.zeros(X.shape[0], Y.shape[0])
>>> _sparse_manhattan(X.data, X.indices, X.indptr,
... Y.data, Y.indices, Y.indptr,
... D)
"""
cdef intp_t px, py, i, j, ix, iy
cdef double d = 0.0
cdef int m = D.shape[0]
cdef int n = D.shape[1]
cdef int X_indptr_end = 0
cdef int Y_indptr_end = 0
cdef int num_threads = _openmp_effective_n_threads()
# We scan the matrices row by row.
# Given row px in X and row py in Y, we find the positions (i and j
# respectively), in .indices where the indices for the two rows start.
# If the indices (ix and iy) are the same, the corresponding data values
# are processed and the cursors i and j are advanced.
# If not, the lowest index is considered. Its associated data value is
# processed and its cursor is advanced.
# We proceed like this until one of the cursors hits the end for its row.
# Then we process all remaining data values in the other row.
# Below the avoidance of inplace operators is intentional.
# When prange is used, the inplace operator has a special meaning, i.e. it
# signals a "reduction"
for px in prange(m, nogil=True, num_threads=num_threads):
X_indptr_end = X_indptr[px + 1]
for py in range(n):
Y_indptr_end = Y_indptr[py + 1]
i = X_indptr[px]
j = Y_indptr[py]
d = 0.0
while i < X_indptr_end and j < Y_indptr_end:
ix = X_indices[i]
iy = Y_indices[j]
if ix == iy:
d = d + fabs(X_data[i] - Y_data[j])
i = i + 1
j = j + 1
elif ix < iy:
d = d + fabs(X_data[i])
i = i + 1
else:
d = d + fabs(Y_data[j])
j = j + 1
if i == X_indptr_end:
while j < Y_indptr_end:
d = d + fabs(Y_data[j])
j = j + 1
else:
while i < X_indptr_end:
d = d + fabs(X_data[i])
i = i + 1
D[px, py] = d
@@ -1,69 +1,69 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from libc.math cimport exp, lgamma
from ...utils._typedefs cimport float64_t, int64_t
import numpy as np
from scipy.special import gammaln
def expected_mutual_information(contingency, int64_t n_samples):
"""Calculate the expected mutual information for two labelings."""
cdef:
float64_t emi = 0
int64_t n_rows, n_cols
float64_t term2, term3, gln
int64_t[::1] a_view, b_view
float64_t[::1] term1
float64_t[::1] gln_a, gln_b, gln_Na, gln_Nb, gln_Nnij, log_Nnij
float64_t[::1] log_a, log_b
Py_ssize_t i, j, nij
int64_t start, end
n_rows, n_cols = contingency.shape
a = np.ravel(contingency.sum(axis=1).astype(np.int64, copy=False))
b = np.ravel(contingency.sum(axis=0).astype(np.int64, copy=False))
a_view = a
b_view = b
# any labelling with zero entropy implies EMI = 0
if a.size == 1 or b.size == 1:
return 0.0
# There are three major terms to the EMI equation, which are multiplied to
# and then summed over varying nij values.
# While nijs[0] will never be used, having it simplifies the indexing.
nijs = np.arange(0, max(np.max(a), np.max(b)) + 1, dtype='float')
nijs[0] = 1 # Stops divide by zero warnings. As its not used, no issue.
# term1 is nij / N
term1 = nijs / n_samples
# term2 is log((N*nij) / (a * b)) == log(N * nij) - log(a * b)
log_a = np.log(a)
log_b = np.log(b)
# term2 uses log(N * nij) = log(N) + log(nij)
log_Nnij = np.log(n_samples) + np.log(nijs)
# term3 is large, and involved many factorials. Calculate these in log
# space to stop overflows.
gln_a = gammaln(a + 1)
gln_b = gammaln(b + 1)
gln_Na = gammaln(n_samples - a + 1)
gln_Nb = gammaln(n_samples - b + 1)
gln_Nnij = gammaln(nijs + 1) + gammaln(n_samples + 1)
# emi itself is a summation over the various values.
for i in range(n_rows):
for j in range(n_cols):
start = max(1, a_view[i] - n_samples + b_view[j])
end = min(a_view[i], b_view[j]) + 1
for nij in range(start, end):
term2 = log_Nnij[nij] - log_a[i] - log_b[j]
# Numerators are positive, denominators are negative.
gln = (gln_a[i] + gln_b[j] + gln_Na[i] + gln_Nb[j]
- gln_Nnij[nij] - lgamma(a_view[i] - nij + 1)
- lgamma(b_view[j] - nij + 1)
- lgamma(n_samples - a_view[i] - b_view[j] + nij + 1))
term3 = exp(gln)
emi += (term1[nij] * term2 * term3)
return emi
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from libc.math cimport exp, lgamma
from ...utils._typedefs cimport float64_t, int64_t
import numpy as np
from scipy.special import gammaln
def expected_mutual_information(contingency, int64_t n_samples):
"""Calculate the expected mutual information for two labelings."""
cdef:
float64_t emi = 0
int64_t n_rows, n_cols
float64_t term2, term3, gln
int64_t[::1] a_view, b_view
float64_t[::1] term1
float64_t[::1] gln_a, gln_b, gln_Na, gln_Nb, gln_Nnij, log_Nnij
float64_t[::1] log_a, log_b
Py_ssize_t i, j, nij
int64_t start, end
n_rows, n_cols = contingency.shape
a = np.ravel(contingency.sum(axis=1).astype(np.int64, copy=False))
b = np.ravel(contingency.sum(axis=0).astype(np.int64, copy=False))
a_view = a
b_view = b
# any labelling with zero entropy implies EMI = 0
if a.size == 1 or b.size == 1:
return 0.0
# There are three major terms to the EMI equation, which are multiplied to
# and then summed over varying nij values.
# While nijs[0] will never be used, having it simplifies the indexing.
nijs = np.arange(0, max(np.max(a), np.max(b)) + 1, dtype='float')
nijs[0] = 1 # Stops divide by zero warnings. As its not used, no issue.
# term1 is nij / N
term1 = nijs / n_samples
# term2 is log((N*nij) / (a * b)) == log(N * nij) - log(a * b)
log_a = np.log(a)
log_b = np.log(b)
# term2 uses log(N * nij) = log(N) + log(nij)
log_Nnij = np.log(n_samples) + np.log(nijs)
# term3 is large, and involved many factorials. Calculate these in log
# space to stop overflows.
gln_a = gammaln(a + 1)
gln_b = gammaln(b + 1)
gln_Na = gammaln(n_samples - a + 1)
gln_Nb = gammaln(n_samples - b + 1)
gln_Nnij = gammaln(nijs + 1) + gammaln(n_samples + 1)
# emi itself is a summation over the various values.
for i in range(n_rows):
for j in range(n_cols):
start = max(1, a_view[i] - n_samples + b_view[j])
end = min(a_view[i], b_view[j]) + 1
for nij in range(start, end):
term2 = log_Nnij[nij] - log_a[i] - log_b[j]
# Numerators are positive, denominators are negative.
gln = (gln_a[i] + gln_b[j] + gln_Na[i] + gln_Nb[j]
- gln_Nnij[nij] - lgamma(a_view[i] - nij + 1)
- lgamma(b_view[j] - nij + 1)
- lgamma(n_samples - a_view[i] - b_view[j] + nij + 1))
term3 = exp(gln)
emi += (term1[nij] * term2 * term3)
return emi
@@ -1,6 +1,6 @@
py.extension_module(
'_expected_mutual_info_fast',
cython_gen.process('_expected_mutual_info_fast.pyx'),
subdir: 'sklearn/metrics/cluster',
install: true
)
py.extension_module(
'_expected_mutual_info_fast',
cython_gen.process('_expected_mutual_info_fast.pyx'),
subdir: 'sklearn/metrics/cluster',
install: true
)
@@ -1,49 +1,49 @@
# Metrics is cimported from other subpackages so this is needed for the cimport
# to work
metrics_cython_tree = [
fs.copyfile('__init__.py')
]
# Some metrics code cimports code from utils, we may as well copy all the necessary files
metrics_cython_tree += utils_cython_tree
_dist_metrics_pxd = custom_target(
'_dist_metrics_pxd',
output: '_dist_metrics.pxd',
input: '_dist_metrics.pxd.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# Need to install the generated pxd because it is needed in other subpackages
# Cython code, e.g. sklearn.cluster
install_dir: sklearn_dir / 'metrics',
install: true,
)
metrics_cython_tree += [_dist_metrics_pxd]
_dist_metrics_pyx = custom_target(
'_dist_metrics_pyx',
output: '_dist_metrics.pyx',
input: '_dist_metrics.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: metrics_cython_tree,
)
_dist_metrics = py.extension_module(
'_dist_metrics',
cython_gen.process(_dist_metrics_pyx),
dependencies: [np_dep],
subdir: 'sklearn/metrics',
install: true
)
py.extension_module(
'_pairwise_fast',
[cython_gen.process('_pairwise_fast.pyx'), metrics_cython_tree],
dependencies: [openmp_dep],
subdir: 'sklearn/metrics',
install: true
)
subdir('_pairwise_distances_reduction')
subdir('cluster')
# Metrics is cimported from other subpackages so this is needed for the cimport
# to work
metrics_cython_tree = [
fs.copyfile('__init__.py')
]
# Some metrics code cimports code from utils, we may as well copy all the necessary files
metrics_cython_tree += utils_cython_tree
_dist_metrics_pxd = custom_target(
'_dist_metrics_pxd',
output: '_dist_metrics.pxd',
input: '_dist_metrics.pxd.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# Need to install the generated pxd because it is needed in other subpackages
# Cython code, e.g. sklearn.cluster
install_dir: sklearn_dir / 'metrics',
install: true,
)
metrics_cython_tree += [_dist_metrics_pxd]
_dist_metrics_pyx = custom_target(
'_dist_metrics_pyx',
output: '_dist_metrics.pyx',
input: '_dist_metrics.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: metrics_cython_tree,
)
_dist_metrics = py.extension_module(
'_dist_metrics',
cython_gen.process(_dist_metrics_pyx),
dependencies: [np_dep],
subdir: 'sklearn/metrics',
install: true
)
py.extension_module(
'_pairwise_fast',
[cython_gen.process('_pairwise_fast.pyx'), metrics_cython_tree],
dependencies: [openmp_dep],
subdir: 'sklearn/metrics',
install: true
)
subdir('_pairwise_distances_reduction')
subdir('cluster')
@@ -1,284 +1,284 @@
{{py:
# Generated file: _ball_tree.pyx
implementation_specific_values = [
# The values are arranged as follows:
#
# name_suffix, INPUT_DTYPE_t, INPUT_DTYPE
#
('64', 'float64_t', 'np.float64'),
('32', 'float32_t', 'np.float32')
]
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
}}
__all__ = ['BallTree', 'BallTree64', 'BallTree32']
{{for name_suffix, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
DOC_DICT{{name_suffix}} = {
'BinaryTree': 'BallTree{{name_suffix}}',
'binary_tree': 'ball_tree{{name_suffix}}',
}
VALID_METRICS{{name_suffix}} = [
'BrayCurtisDistance{{name_suffix}}',
'CanberraDistance{{name_suffix}}',
'ChebyshevDistance{{name_suffix}}',
'DiceDistance{{name_suffix}}',
'EuclideanDistance{{name_suffix}}',
'HammingDistance{{name_suffix}}',
'HaversineDistance{{name_suffix}}',
'JaccardDistance{{name_suffix}}',
'MahalanobisDistance{{name_suffix}}',
'ManhattanDistance{{name_suffix}}',
'MinkowskiDistance{{name_suffix}}',
'PyFuncDistance{{name_suffix}}',
'RogersTanimotoDistance{{name_suffix}}',
'RussellRaoDistance{{name_suffix}}',
'SEuclideanDistance{{name_suffix}}',
'SokalMichenerDistance{{name_suffix}}',
'SokalSneathDistance{{name_suffix}}',
'WMinkowskiDistance{{name_suffix}}',
]
{{endfor}}
include "_binary_tree.pxi"
{{for name_suffix, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
# Inherit BallTree{{name_suffix}} from BinaryTree{{name_suffix}}
cdef class BallTree{{name_suffix}}(BinaryTree{{name_suffix}}):
__doc__ = CLASS_DOC.format(**DOC_DICT{{name_suffix}})
pass
{{endfor}}
#----------------------------------------------------------------------
# The functions below specialized the Binary Tree as a Ball Tree
#
# Note that these functions use the concept of "reduced distance".
# The reduced distance, defined for some metrics, is a quantity which
# is more efficient to compute than the distance, but preserves the
# relative rankings of the true distance. For example, the reduced
# distance for the Euclidean metric is the squared-euclidean distance.
# For some metrics, the reduced distance is simply the distance.
{{for name_suffix, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
cdef int allocate_data{{name_suffix}}(
BinaryTree{{name_suffix}} tree,
intp_t n_nodes,
intp_t n_features,
) except -1:
"""Allocate arrays needed for the KD Tree"""
tree.node_bounds = np.zeros((1, n_nodes, n_features), dtype={{INPUT_DTYPE}})
return 0
cdef int init_node{{name_suffix}}(
BinaryTree{{name_suffix}} tree,
NodeData_t[::1] node_data,
intp_t i_node,
intp_t idx_start,
intp_t idx_end,
) except -1:
"""Initialize the node for the dataset stored in tree.data"""
cdef intp_t n_features = tree.data.shape[1]
cdef intp_t n_points = idx_end - idx_start
cdef intp_t i, j
cdef float64_t radius
cdef const {{INPUT_DTYPE_t}} *this_pt
cdef intp_t* idx_array = &tree.idx_array[0]
cdef const {{INPUT_DTYPE_t}}* data = &tree.data[0, 0]
cdef {{INPUT_DTYPE_t}}* centroid = &tree.node_bounds[0, i_node, 0]
cdef bint with_sample_weight = tree.sample_weight is not None
cdef const {{INPUT_DTYPE_t}}* sample_weight
cdef float64_t sum_weight_node
if with_sample_weight:
sample_weight = &tree.sample_weight[0]
# determine Node centroid
for j in range(n_features):
centroid[j] = 0
if with_sample_weight:
sum_weight_node = 0
for i in range(idx_start, idx_end):
sum_weight_node += sample_weight[idx_array[i]]
this_pt = data + n_features * idx_array[i]
for j from 0 <= j < n_features:
centroid[j] += this_pt[j] * sample_weight[idx_array[i]]
for j in range(n_features):
centroid[j] /= sum_weight_node
else:
for i in range(idx_start, idx_end):
this_pt = data + n_features * idx_array[i]
for j from 0 <= j < n_features:
centroid[j] += this_pt[j]
for j in range(n_features):
centroid[j] /= n_points
# determine Node radius
radius = 0
for i in range(idx_start, idx_end):
radius = fmax(radius,
tree.rdist(centroid,
data + n_features * idx_array[i],
n_features))
node_data[i_node].radius = tree.dist_metric._rdist_to_dist(radius)
node_data[i_node].idx_start = idx_start
node_data[i_node].idx_end = idx_end
return 0
cdef inline float64_t min_dist{{name_suffix}}(
BinaryTree{{name_suffix}} tree,
intp_t i_node,
const {{INPUT_DTYPE_t}}* pt,
) except -1 nogil:
"""Compute the minimum distance between a point and a node"""
cdef float64_t dist_pt = tree.dist(pt, &tree.node_bounds[0, i_node, 0],
tree.data.shape[1])
return fmax(0, dist_pt - tree.node_data[i_node].radius)
cdef inline float64_t max_dist{{name_suffix}}(
BinaryTree{{name_suffix}} tree,
intp_t i_node,
const {{INPUT_DTYPE_t}}* pt,
) except -1:
"""Compute the maximum distance between a point and a node"""
cdef float64_t dist_pt = tree.dist(pt, &tree.node_bounds[0, i_node, 0],
tree.data.shape[1])
return dist_pt + tree.node_data[i_node].radius
cdef inline int min_max_dist{{name_suffix}}(
BinaryTree{{name_suffix}} tree,
intp_t i_node,
const {{INPUT_DTYPE_t}}* pt,
float64_t* min_dist,
float64_t* max_dist,
) except -1 nogil:
"""Compute the minimum and maximum distance between a point and a node"""
cdef float64_t dist_pt = tree.dist(pt, &tree.node_bounds[0, i_node, 0],
tree.data.shape[1])
cdef float64_t rad = tree.node_data[i_node].radius
min_dist[0] = fmax(0, dist_pt - rad)
max_dist[0] = dist_pt + rad
return 0
cdef inline float64_t min_rdist{{name_suffix}}(
BinaryTree{{name_suffix}} tree,
intp_t i_node,
const {{INPUT_DTYPE_t}}* pt,
) except -1 nogil:
"""Compute the minimum reduced-distance between a point and a node"""
if tree.euclidean:
return euclidean_dist_to_rdist{{name_suffix}}(
min_dist{{name_suffix}}(tree, i_node, pt)
)
else:
return tree.dist_metric._dist_to_rdist(
min_dist{{name_suffix}}(tree, i_node, pt)
)
cdef inline float64_t max_rdist{{name_suffix}}(
BinaryTree{{name_suffix}} tree,
intp_t i_node,
const {{INPUT_DTYPE_t}}* pt,
) except -1:
"""Compute the maximum reduced-distance between a point and a node"""
if tree.euclidean:
return euclidean_dist_to_rdist{{name_suffix}}(
max_dist{{name_suffix}}(tree, i_node, pt)
)
else:
return tree.dist_metric._dist_to_rdist(
max_dist{{name_suffix}}(tree, i_node, pt)
)
cdef inline float64_t min_dist_dual{{name_suffix}}(
BinaryTree{{name_suffix}} tree1,
intp_t i_node1,
BinaryTree{{name_suffix}} tree2,
intp_t i_node2,
) except -1:
"""compute the minimum distance between two nodes"""
cdef float64_t dist_pt = tree1.dist(&tree2.node_bounds[0, i_node2, 0],
&tree1.node_bounds[0, i_node1, 0],
tree1.data.shape[1])
return fmax(0, (dist_pt - tree1.node_data[i_node1].radius
- tree2.node_data[i_node2].radius))
cdef inline float64_t max_dist_dual{{name_suffix}}(
BinaryTree{{name_suffix}} tree1,
intp_t i_node1,
BinaryTree{{name_suffix}} tree2,
intp_t i_node2,
) except -1:
"""compute the maximum distance between two nodes"""
cdef float64_t dist_pt = tree1.dist(&tree2.node_bounds[0, i_node2, 0],
&tree1.node_bounds[0, i_node1, 0],
tree1.data.shape[1])
return (dist_pt + tree1.node_data[i_node1].radius
+ tree2.node_data[i_node2].radius)
cdef inline float64_t min_rdist_dual{{name_suffix}}(
BinaryTree{{name_suffix}} tree1,
intp_t i_node1,
BinaryTree{{name_suffix}} tree2,
intp_t i_node2,
) except -1:
"""compute the minimum reduced distance between two nodes"""
if tree1.euclidean:
return euclidean_dist_to_rdist{{name_suffix}}(
min_dist_dual{{name_suffix}}(tree1, i_node1, tree2, i_node2)
)
else:
return tree1.dist_metric._dist_to_rdist(
min_dist_dual{{name_suffix}}(tree1, i_node1, tree2, i_node2)
)
cdef inline float64_t max_rdist_dual{{name_suffix}}(
BinaryTree{{name_suffix}} tree1,
intp_t i_node1,
BinaryTree{{name_suffix}} tree2,
intp_t i_node2,
) except -1:
"""compute the maximum reduced distance between two nodes"""
if tree1.euclidean:
return euclidean_dist_to_rdist{{name_suffix}}(
max_dist_dual{{name_suffix}}(tree1, i_node1, tree2, i_node2)
)
else:
return tree1.dist_metric._dist_to_rdist(
max_dist_dual{{name_suffix}}(tree1, i_node1, tree2, i_node2)
)
{{endfor}}
class BallTree(BallTree64):
__doc__ = CLASS_DOC.format(BinaryTree="BallTree")
pass
{{py:
# Generated file: _ball_tree.pyx
implementation_specific_values = [
# The values are arranged as follows:
#
# name_suffix, INPUT_DTYPE_t, INPUT_DTYPE
#
('64', 'float64_t', 'np.float64'),
('32', 'float32_t', 'np.float32')
]
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
}}
__all__ = ['BallTree', 'BallTree64', 'BallTree32']
{{for name_suffix, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
DOC_DICT{{name_suffix}} = {
'BinaryTree': 'BallTree{{name_suffix}}',
'binary_tree': 'ball_tree{{name_suffix}}',
}
VALID_METRICS{{name_suffix}} = [
'BrayCurtisDistance{{name_suffix}}',
'CanberraDistance{{name_suffix}}',
'ChebyshevDistance{{name_suffix}}',
'DiceDistance{{name_suffix}}',
'EuclideanDistance{{name_suffix}}',
'HammingDistance{{name_suffix}}',
'HaversineDistance{{name_suffix}}',
'JaccardDistance{{name_suffix}}',
'MahalanobisDistance{{name_suffix}}',
'ManhattanDistance{{name_suffix}}',
'MinkowskiDistance{{name_suffix}}',
'PyFuncDistance{{name_suffix}}',
'RogersTanimotoDistance{{name_suffix}}',
'RussellRaoDistance{{name_suffix}}',
'SEuclideanDistance{{name_suffix}}',
'SokalMichenerDistance{{name_suffix}}',
'SokalSneathDistance{{name_suffix}}',
'WMinkowskiDistance{{name_suffix}}',
]
{{endfor}}
include "_binary_tree.pxi"
{{for name_suffix, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
# Inherit BallTree{{name_suffix}} from BinaryTree{{name_suffix}}
cdef class BallTree{{name_suffix}}(BinaryTree{{name_suffix}}):
__doc__ = CLASS_DOC.format(**DOC_DICT{{name_suffix}})
pass
{{endfor}}
#----------------------------------------------------------------------
# The functions below specialized the Binary Tree as a Ball Tree
#
# Note that these functions use the concept of "reduced distance".
# The reduced distance, defined for some metrics, is a quantity which
# is more efficient to compute than the distance, but preserves the
# relative rankings of the true distance. For example, the reduced
# distance for the Euclidean metric is the squared-euclidean distance.
# For some metrics, the reduced distance is simply the distance.
{{for name_suffix, INPUT_DTYPE_t, INPUT_DTYPE in implementation_specific_values}}
cdef int allocate_data{{name_suffix}}(
BinaryTree{{name_suffix}} tree,
intp_t n_nodes,
intp_t n_features,
) except -1:
"""Allocate arrays needed for the KD Tree"""
tree.node_bounds = np.zeros((1, n_nodes, n_features), dtype={{INPUT_DTYPE}})
return 0
cdef int init_node{{name_suffix}}(
BinaryTree{{name_suffix}} tree,
NodeData_t[::1] node_data,
intp_t i_node,
intp_t idx_start,
intp_t idx_end,
) except -1:
"""Initialize the node for the dataset stored in tree.data"""
cdef intp_t n_features = tree.data.shape[1]
cdef intp_t n_points = idx_end - idx_start
cdef intp_t i, j
cdef float64_t radius
cdef const {{INPUT_DTYPE_t}} *this_pt
cdef intp_t* idx_array = &tree.idx_array[0]
cdef const {{INPUT_DTYPE_t}}* data = &tree.data[0, 0]
cdef {{INPUT_DTYPE_t}}* centroid = &tree.node_bounds[0, i_node, 0]
cdef bint with_sample_weight = tree.sample_weight is not None
cdef const {{INPUT_DTYPE_t}}* sample_weight
cdef float64_t sum_weight_node
if with_sample_weight:
sample_weight = &tree.sample_weight[0]
# determine Node centroid
for j in range(n_features):
centroid[j] = 0
if with_sample_weight:
sum_weight_node = 0
for i in range(idx_start, idx_end):
sum_weight_node += sample_weight[idx_array[i]]
this_pt = data + n_features * idx_array[i]
for j from 0 <= j < n_features:
centroid[j] += this_pt[j] * sample_weight[idx_array[i]]
for j in range(n_features):
centroid[j] /= sum_weight_node
else:
for i in range(idx_start, idx_end):
this_pt = data + n_features * idx_array[i]
for j from 0 <= j < n_features:
centroid[j] += this_pt[j]
for j in range(n_features):
centroid[j] /= n_points
# determine Node radius
radius = 0
for i in range(idx_start, idx_end):
radius = fmax(radius,
tree.rdist(centroid,
data + n_features * idx_array[i],
n_features))
node_data[i_node].radius = tree.dist_metric._rdist_to_dist(radius)
node_data[i_node].idx_start = idx_start
node_data[i_node].idx_end = idx_end
return 0
cdef inline float64_t min_dist{{name_suffix}}(
BinaryTree{{name_suffix}} tree,
intp_t i_node,
const {{INPUT_DTYPE_t}}* pt,
) except -1 nogil:
"""Compute the minimum distance between a point and a node"""
cdef float64_t dist_pt = tree.dist(pt, &tree.node_bounds[0, i_node, 0],
tree.data.shape[1])
return fmax(0, dist_pt - tree.node_data[i_node].radius)
cdef inline float64_t max_dist{{name_suffix}}(
BinaryTree{{name_suffix}} tree,
intp_t i_node,
const {{INPUT_DTYPE_t}}* pt,
) except -1:
"""Compute the maximum distance between a point and a node"""
cdef float64_t dist_pt = tree.dist(pt, &tree.node_bounds[0, i_node, 0],
tree.data.shape[1])
return dist_pt + tree.node_data[i_node].radius
cdef inline int min_max_dist{{name_suffix}}(
BinaryTree{{name_suffix}} tree,
intp_t i_node,
const {{INPUT_DTYPE_t}}* pt,
float64_t* min_dist,
float64_t* max_dist,
) except -1 nogil:
"""Compute the minimum and maximum distance between a point and a node"""
cdef float64_t dist_pt = tree.dist(pt, &tree.node_bounds[0, i_node, 0],
tree.data.shape[1])
cdef float64_t rad = tree.node_data[i_node].radius
min_dist[0] = fmax(0, dist_pt - rad)
max_dist[0] = dist_pt + rad
return 0
cdef inline float64_t min_rdist{{name_suffix}}(
BinaryTree{{name_suffix}} tree,
intp_t i_node,
const {{INPUT_DTYPE_t}}* pt,
) except -1 nogil:
"""Compute the minimum reduced-distance between a point and a node"""
if tree.euclidean:
return euclidean_dist_to_rdist{{name_suffix}}(
min_dist{{name_suffix}}(tree, i_node, pt)
)
else:
return tree.dist_metric._dist_to_rdist(
min_dist{{name_suffix}}(tree, i_node, pt)
)
cdef inline float64_t max_rdist{{name_suffix}}(
BinaryTree{{name_suffix}} tree,
intp_t i_node,
const {{INPUT_DTYPE_t}}* pt,
) except -1:
"""Compute the maximum reduced-distance between a point and a node"""
if tree.euclidean:
return euclidean_dist_to_rdist{{name_suffix}}(
max_dist{{name_suffix}}(tree, i_node, pt)
)
else:
return tree.dist_metric._dist_to_rdist(
max_dist{{name_suffix}}(tree, i_node, pt)
)
cdef inline float64_t min_dist_dual{{name_suffix}}(
BinaryTree{{name_suffix}} tree1,
intp_t i_node1,
BinaryTree{{name_suffix}} tree2,
intp_t i_node2,
) except -1:
"""compute the minimum distance between two nodes"""
cdef float64_t dist_pt = tree1.dist(&tree2.node_bounds[0, i_node2, 0],
&tree1.node_bounds[0, i_node1, 0],
tree1.data.shape[1])
return fmax(0, (dist_pt - tree1.node_data[i_node1].radius
- tree2.node_data[i_node2].radius))
cdef inline float64_t max_dist_dual{{name_suffix}}(
BinaryTree{{name_suffix}} tree1,
intp_t i_node1,
BinaryTree{{name_suffix}} tree2,
intp_t i_node2,
) except -1:
"""compute the maximum distance between two nodes"""
cdef float64_t dist_pt = tree1.dist(&tree2.node_bounds[0, i_node2, 0],
&tree1.node_bounds[0, i_node1, 0],
tree1.data.shape[1])
return (dist_pt + tree1.node_data[i_node1].radius
+ tree2.node_data[i_node2].radius)
cdef inline float64_t min_rdist_dual{{name_suffix}}(
BinaryTree{{name_suffix}} tree1,
intp_t i_node1,
BinaryTree{{name_suffix}} tree2,
intp_t i_node2,
) except -1:
"""compute the minimum reduced distance between two nodes"""
if tree1.euclidean:
return euclidean_dist_to_rdist{{name_suffix}}(
min_dist_dual{{name_suffix}}(tree1, i_node1, tree2, i_node2)
)
else:
return tree1.dist_metric._dist_to_rdist(
min_dist_dual{{name_suffix}}(tree1, i_node1, tree2, i_node2)
)
cdef inline float64_t max_rdist_dual{{name_suffix}}(
BinaryTree{{name_suffix}} tree1,
intp_t i_node1,
BinaryTree{{name_suffix}} tree2,
intp_t i_node2,
) except -1:
"""compute the maximum reduced distance between two nodes"""
if tree1.euclidean:
return euclidean_dist_to_rdist{{name_suffix}}(
max_dist_dual{{name_suffix}}(tree1, i_node1, tree2, i_node2)
)
else:
return tree1.dist_metric._dist_to_rdist(
max_dist_dual{{name_suffix}}(tree1, i_node1, tree2, i_node2)
)
{{endfor}}
class BallTree(BallTree64):
__doc__ = CLASS_DOC.format(BinaryTree="BallTree")
pass

Some files were not shown because too many files have changed in this diff Show More