release(camera-ai): publish 0.4.12 catalog portability fix
This commit is contained in:
@@ -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
|
||||
|
||||
+210
-210
@@ -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
|
||||
|
||||
+507
-507
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], ¢ers[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(
|
||||
¢ers_new[j, 0], ¢ers_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], ¢ers[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(
|
||||
¢ers_new[j, 0], ¢ers_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(¢ers_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, ¢ers_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(¢ers_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(¢ers_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, ¢ers_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(¢ers_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]
|
||||
|
||||
+218
-218
@@ -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')
|
||||
|
||||
+252
-252
@@ -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"))
|
||||
|
||||
+570
-570
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
|
||||
|
||||
|
+21
-21
@@ -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
|
||||
|
||||
|
+21
-21
@@ -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,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
|
||||
|
||||
|
+118
-118
@@ -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.
|
||||
|
||||
+46
-46
@@ -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.
|
||||
|
||||
+44
-44
@@ -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.
|
||||
|
||||
+40
-40
@@ -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`
|
||||
|
||||
+248
-248
@@ -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
|
||||
)
|
||||
|
||||
+9
-9
@@ -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
|
||||
|
||||
+3
-3
@@ -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 +1 @@
|
||||
-1 5:2.5 2:-5.2 15:1.5
|
||||
-1 5:2.5 2:-5.2 15:1.5
|
||||
|
||||
+5
-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
|
||||
|
||||
+110
-110
@@ -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
|
||||
)
|
||||
|
||||
+262
-262
@@ -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
|
||||
|
||||
+85
-85
@@ -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
|
||||
|
||||
+20
-20
@@ -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
|
||||
|
||||
+65
-65
@@ -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)
|
||||
|
||||
+59
-59
@@ -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]
|
||||
|
||||
+256
-256
@@ -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)
|
||||
|
||||
+43
-43
@@ -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
|
||||
|
||||
+44
-44
@@ -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()
|
||||
|
||||
+520
-520
File diff suppressed because it is too large
Load Diff
+20
-20
@@ -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
|
||||
|
||||
+1201
-1201
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
|
||||
|
||||
+21
-21
@@ -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
@@ -1 +1 @@
|
||||
Update this directory using maint_tools/vendor_array_api_compat.sh
|
||||
Update this directory using maint_tools/vendor_array_api_compat.sh
|
||||
|
||||
+21
-21
@@ -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
@@ -1 +1 @@
|
||||
Update this directory using maint_tools/vendor_array_api_extra.sh
|
||||
Update this directory using maint_tools/vendor_array_api_extra.sh
|
||||
|
||||
Vendored
+40
-40
@@ -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: ...
|
||||
|
||||
Vendored
+105
-105
@@ -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"]
|
||||
|
||||
+89
-89
@@ -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}}
|
||||
|
||||
+2811
-2811
File diff suppressed because it is too large
Load Diff
+31
-31
@@ -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}}
|
||||
|
||||
+512
-512
File diff suppressed because it is too large
Load Diff
+182
-182
@@ -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}}
|
||||
|
||||
+135
-135
@@ -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}}
|
||||
|
||||
+504
-504
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -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
|
||||
|
||||
+67
-67
@@ -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}}
|
||||
|
||||
+406
-406
@@ -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}}
|
||||
|
||||
+228
-228
@@ -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}}
|
||||
|
||||
+633
-633
File diff suppressed because it is too large
Load Diff
+90
-90
@@ -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}}
|
||||
|
||||
+514
-514
File diff suppressed because it is too large
Load Diff
+217
-217
@@ -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}}
|
||||
|
||||
+193
-193
@@ -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
|
||||
|
||||
+69
-69
@@ -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
Reference in New Issue
Block a user