From 15699ceae36364b199433864557d7ace5f81e9c1 Mon Sep 17 00:00:00 2001 From: Adam Li Date: Mon, 12 Jun 2023 13:11:34 -0400 Subject: [PATCH 01/17] Merge other branch Signed-off-by: Adam Li --- docs/api.rst | 37 +++++++++++++++++++++++++++++++ docs/whats_new/v0.1.rst | 2 ++ sktree/_lib/sklearn_fork | 2 +- sktree/meson.build | 3 ++- sktree/tree/_oblique_splitter.pyx | 4 ---- sktree/tree/_sklearn_splitter.pyx | 1 + 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index e150660d5..2917de68b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -65,3 +65,40 @@ The trees that comprise those forests are also available as standalone classes. tree.UnsupervisedDecisionTree tree.UnsupervisedObliqueDecisionTree + + +Distance Metrics +---------------- +Trees inherently produce a "distance-like" metric. We provide an API for +extracting pairwise distances from the trees that include a correction +that turns the "tree-distance" into a proper distance metric. + +.. currentmodule:: sktree.ensemble +.. autosummary:: + :toctree: generated/ + + pairwise_forest_distance + +Experimental Functionality +-------------------------- +We also include experimental functionality that is works in progress. + +.. currentmodule:: sktree.experimental +.. autosummary:: + :toctree: generated/ + + mutual_info_ksg + +We also include functions that help simulate and evaluate mutual information (MI) +and conditional mutual information (CMI) estimators. Specifically, functions that +help simulate multivariate gaussian data and compute the analytical solutions +for the entropy, MI and CMI of the Gaussian distributions. + +.. currentmodule:: sktree.experimental +.. autosummary:: + :toctree: generated/ + + mi_gaussian + cmi_gaussian + entropy_gaussian + simulate_multivariate_gaussian \ No newline at end of file diff --git a/docs/whats_new/v0.1.rst b/docs/whats_new/v0.1.rst index 78b7bf5b2..d060abbb9 100644 --- a/docs/whats_new/v0.1.rst +++ b/docs/whats_new/v0.1.rst @@ -36,6 +36,8 @@ Changelog - |Feature| A general-kernel MORF is now implemented where users can pass in a kernel library, by `Adam Li`_ (:pr:`70`) - |Feature| Implementation of ObliqueDecisionTreeRegressor, PatchObliqueDecisionTreeRegressor, ObliqueRandomForestRegressor, PatchObliqueRandomForestRegressor, by `SUKI-O`_ (:pr:`72`) - |Feature| Implementation of HonestTreeClassifier, HonestForestClassifier, by `Sambit Panda`_, `Adam Li`_, `Ronan Perry`_ and `Haoyin Xu`_ (:pr:`57`) +- |Feature| Implementation of (conditional) mutual information estimation via unsupervised tree models, by `Adam Li`_ (:pr:`47`) + Code and Documentation Contributors ----------------------------------- diff --git a/sktree/_lib/sklearn_fork b/sktree/_lib/sklearn_fork index 45320b4d3..6b4d0e7f3 160000 --- a/sktree/_lib/sklearn_fork +++ b/sktree/_lib/sklearn_fork @@ -1 +1 @@ -Subproject commit 45320b4d3ef05b4ccbe81e8c13676b1c755d1973 +Subproject commit 6b4d0e7f37dbd0af2bb8404b921db53cbb2cc78f diff --git a/sktree/meson.build b/sktree/meson.build index 8fd818a5e..fc7df580e 100644 --- a/sktree/meson.build +++ b/sktree/meson.build @@ -48,7 +48,7 @@ cc = meson.get_compiler('c') # py3.extension_module('_name', # 'source_fname', # numpy_nodepr_api) -numpy_nodepr_api = '-DNPY_NO_DEPRECATED_API=NPY_1_9_API_VERSION' +numpy_nodepr_api = '-DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION' cython_c_args += numpy_nodepr_api python_sources = [ @@ -81,4 +81,5 @@ cython_cpp_args = cython_c_args subdir('_lib') subdir('tree') subdir('ensemble') +subdir('experimental') subdir('tests') \ No newline at end of file diff --git a/sktree/tree/_oblique_splitter.pyx b/sktree/tree/_oblique_splitter.pyx index d5e6dcb3b..1a2c43be8 100644 --- a/sktree/tree/_oblique_splitter.pyx +++ b/sktree/tree/_oblique_splitter.pyx @@ -6,10 +6,6 @@ import numpy as np -cimport numpy as cnp - -cnp.import_array() - from cython.operator cimport dereference as deref from libcpp.vector cimport vector from sklearn.tree._utils cimport rand_int diff --git a/sktree/tree/_sklearn_splitter.pyx b/sktree/tree/_sklearn_splitter.pyx index a43008328..529abde12 100644 --- a/sktree/tree/_sklearn_splitter.pyx +++ b/sktree/tree/_sklearn_splitter.pyx @@ -1,5 +1,6 @@ # cython: language_level=3 # cython: boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True +# distutils: define_macros=NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION from libc.stdlib cimport qsort from libc.string cimport memcpy From 3e63af5c1f8bcaff4b0a9e235aaf65de07b00e51 Mon Sep 17 00:00:00 2001 From: Adam Li Date: Mon, 12 Jun 2023 13:12:42 -0400 Subject: [PATCH 02/17] Add experiemntal subdir Signed-off-by: Adam Li --- sktree/experimental/__init__.py | 10 + sktree/experimental/distributions.py | 0 sktree/experimental/meson.build | 10 + sktree/experimental/mutual_info.py | 443 ++++++++++++++++++ sktree/experimental/simulate.py | 196 ++++++++ sktree/experimental/tests/test_mutual_info.py | 52 ++ sktree/experimental/tests/test_simulate.py | 40 ++ 7 files changed, 751 insertions(+) create mode 100644 sktree/experimental/__init__.py create mode 100644 sktree/experimental/distributions.py create mode 100644 sktree/experimental/meson.build create mode 100644 sktree/experimental/mutual_info.py create mode 100644 sktree/experimental/simulate.py create mode 100644 sktree/experimental/tests/test_mutual_info.py create mode 100644 sktree/experimental/tests/test_simulate.py diff --git a/sktree/experimental/__init__.py b/sktree/experimental/__init__.py new file mode 100644 index 000000000..95c9c07a6 --- /dev/null +++ b/sktree/experimental/__init__.py @@ -0,0 +1,10 @@ +from .mutual_info import ( + entropy_gaussian, + entropy_weibull, + mi_gaussian, + mi_gamma, + cmi_gaussian, + mutual_info_ksg, + mi_from_entropy, + cmi_from_entropy, +) diff --git a/sktree/experimental/distributions.py b/sktree/experimental/distributions.py new file mode 100644 index 000000000..e69de29bb diff --git a/sktree/experimental/meson.build b/sktree/experimental/meson.build new file mode 100644 index 000000000..badbb1e18 --- /dev/null +++ b/sktree/experimental/meson.build @@ -0,0 +1,10 @@ +python_sources = [ + '__init__.py', + 'mutual_info.py', +] + +py3.install_sources( + python_sources, + pure: false, + subdir: 'sktree/experimental' +) \ No newline at end of file diff --git a/sktree/experimental/mutual_info.py b/sktree/experimental/mutual_info.py new file mode 100644 index 000000000..839d1b528 --- /dev/null +++ b/sktree/experimental/mutual_info.py @@ -0,0 +1,443 @@ +from typing import Optional + +import numpy as np +import scipy.linalg +import scipy.special +import scipy.stats +from numpy.typing import ArrayLike +from sklearn.neighbors import NearestNeighbors +from sklearn.preprocessing import StandardScaler + +from sktree.ensemble import UnsupervisedObliqueRandomForest +from sktree.tree import compute_forest_similarity_matrix + + +def entropy_gaussian(cov): + """Compute entropy of a multivariate Gaussian. + + Computes the analytical solution due to :footcite:`Darbellay1999Entropy`. + + Parameters + ---------- + cov : array-like of shape (d,d) + The covariance matrix of the distribution. + + Returns + ------- + true_mi : float + The true analytical mutual information of the generated multivariate Gaussian distribution. + + Notes + ----- + The analytical solution for the true mutual information, ``I(X; Y)`` is given by:: + + I(X; Y) = H(X) + H(Y) - H(X, Y) = -\\frac{1}{2} log(det(C)) + + References + ---------- + .. footbibliography:: + """ + d = cov.shape[0] + + true_entropy = 0.5 * d * (1 + np.log(2 * np.pi)) + 0.5 * np.log(np.linalg.det(cov)) + return true_entropy + + +def mi_gaussian(cov): + """Compute mutual information of a multivariate Gaussian. + + Parameters + ---------- + cov : array-like of shape (d,d) + The covariance matrix of the distribution. + + Returns + ------- + true_mi : float + The true analytical entropy of the generated multivariate Gaussian distribution. + + Notes + ----- + Analytical solution for entropy, :math:`H(X)` of a multivariate Gaussian is given by:: + + H(X) = \\frac{d}{2} (1 + log(2\\pi)) + \\frac{1}{2} log(det(C)) + """ + # computes the true MI + true_mi = -0.5 * np.log(np.linalg.det(cov)) + return true_mi + + +def cmi_gaussian(cov, x_index, y_index, z_index): + """Computes the analytical CMI for a multivariate Gaussian distribution. + + Parameters + ---------- + cov : array-like of shape (d,d) + The covariance matrix of the distribution. + x_index : list or int + List of indices in ``cov`` that are for the X variable. + y_index : list or int + List of indices in ``cov`` that are for the Y variable. + z_index : list or int + List of indices in ``cov`` that are for the Z variable. + + Returns + ------- + true_mi : float + The true analytical mutual information of the generated multivariate Gaussian distribution. + + Notes + ----- + Analytical solution for conditional mutual information, :math:`I(X;Y|Z)` of a + multivariate Gaussian is given by:: + + I(X;Y | Z) = H(X, Z) + H(Y, Z) - H(Z) - H(X, Y, Z) + + where we plug in the analytical solutions for entropy as shown in :func:`entropy_gaussian`. + """ + x_index = np.atleast_1d(x_index) + y_index = np.atleast_1d(y_index) + z_index = np.atleast_1d(z_index) + + xz_index = np.concatenate((x_index, z_index)).squeeze() + yz_index = np.concatenate((y_index, z_index)).squeeze() + + cov_xz = cov[xz_index, xz_index] + cov_yz = cov[yz_index, yz_index] + cov_z = cov[z_index, z_index] + + cmi = ( + entropy_gaussian(cov_xz) + + entropy_gaussian(cov_yz) + - entropy_gaussian(cov_z) + - entropy_gaussian(cov) + ) + return cmi + + +def entropy_weibull(alpha, k): + """Analytical solution for entropy of Weibull distribution. + + https://en.wikipedia.org/wiki/Weibull_distribution + """ + return np.euler_gamma * (1.0 - 1.0 / alpha) - np.log(alpha * np.power(k, 1.0 / alpha)) + 1 + + +def mi_gamma(theta): + """Analytical solution for""" + return scipy.special.digamma(theta + 1) - np.log(theta) + + +def mi_from_entropy(hx, hy, hxy): + """Analytic formula for MI given plug-in estimates of entropies.""" + return hx + hy - hxy + + +def cmi_from_entropy(hxz, hyz, hz, hxyz): + """Analytic formula for CMI given plug-in estimates of entropies.""" + return hxz + hyz - hz - hxyz + + +def mutual_info_ksg( + X, + Y, + Z=None, + k: float = 0.2, + metric="forest", + algorithm="kd_tree", + n_jobs: int = -1, + transform: str = "rank", + random_seed: int = None, +): + """Compute the generalized (conditional) mutual information KSG estimate. + + Parameters + ---------- + X : ArrayLike of shape (n_samples, n_features_x) + The X covariate space. + Y : ArrayLike of shape (n_samples, n_features_y) + The Y covariate space. + Z : ArrayLike of shape (n_samples, n_features_z), optional + The Z covariate space, by default None. If None, then the MI is computed. + If Z is defined, then the CMI is computed. + k : float, optional + The number of neighbors to use in defining the radius, by default 0.2. + metric : str + Any distance metric accepted by :class:`sklearn.neighbors.NearestNeighbors`. + If 'forest' (default), then uses an :class:`UnsupervisedObliqueRandomForest` to compute + geodesic distances. + algorithm : str, optional + Method to use, by default 'knn'. Can be ('ball_tree', 'kd_tree', 'brute'). + n_jobs : int, optional + Number of parallel jobs, by default -1. + transform : one of {'rank', 'standardize', 'uniform'} + Preprocessing, by default "rank". + random_seed : int, optional + Random seed, by default None. + + Returns + ------- + val : float + The estimated MI, or CMI value. + + Notes + ----- + Given a dataset with ``n`` samples, the KSG estimator proceeds by: + + 1. For fixed k, get the distance to the kth nearest-nbr in XYZ subspace, call it 'r' + 2. Get the number of NN in XZ subspace within radius 'r' + 3. Get the number of NN in YZ subspace within radius 'r' + 4. Get the number of NN in Z subspace within radius 'r' + 5. Apply analytic solution for KSG estimate + + For MI :footcite:`Kraskov_2004`, the analytical solution is:: + + \\psi(k) - E[(\\psi(n_x) + \\psi(n_y))] + \\psi(n) + + For CMI :footcite:`Frenzel2007`m the analytical solution is:: + + \\psi(k) - E[(\\psi(n_{xz}) + \\psi(n_{yz}) - \\psi(n_{z}))] + + where :math:`\\psi` is the DiGamma function, and each expectation term + is estimated by taking the sample average. + + Note that the :math:`n_i` terms denote the number of neighbors within + radius 'r' in the subspace of 'i', where 'i' could be for example the + X, Y, XZ, etc. subspaces. This term does not include the sample itself. + """ + rng = np.random.default_rng(random_seed) + + n_samples, n_features_x = X.shape + _, n_features_y = Y.shape + data = np.hstack((X, Y)) + + if Z is not None: + _, n_features_z = Z.shape + data = np.hstack((data, Z)) + else: + n_features_z = 0 + n_features = n_features_x + n_features_y + n_features_z + + # add minor noise to make sure there are no ties + random_noise = rng.random((n_samples, n_features_x)) + data += 1e-5 * random_noise @ np.std(data, axis=0).reshape(n_features, 1) + + if transform == "standardize": + # standardize with standard scaling + data = data.astype(np.float64) + scaler = StandardScaler() + data = scaler.fit_transform(data) + elif transform == "uniform": + data = _trafo2uniform(data) + elif transform == "rank": + # rank transform each column + data = scipy.stats.rankdata(data, axis=0) + + if k < 1: + knn_here = max(1, int(k * n_samples)) + else: + knn_here = max(1, int(k)) + + if Z is not None: + val = _cmi_ksg(data, X, Y, Z, metric, algorithm, knn_here, n_jobs) + else: + val = _mi_ksg(data, X, Y, metric, algorithm, knn_here, n_jobs) + return val + + +def _mi_ksg(data, X, Y, metric, algorithm, knn_here, n_jobs): + """Compute KSG estimate of MI.""" + n_samples = X.shape[0] + + # estimate distance to the kth NN in XYZ subspace for each sample + neigh = _compute_nn(data, algorithm=algorithm, metric=metric, k=knn_here, n_jobs=n_jobs) + + # get the radius we want to use per sample as the distance to the kth neighbor + # in the joint distribution space + dists, _ = neigh.kneighbors() + radius_per_sample = dists[:, -1] + + # compute on the subspace of X + num_nn_x = _compute_radius_nbrs( + X, + radius_per_sample, + knn_here, + algorithm=algorithm, + metric=metric, + n_jobs=n_jobs, + ) + + # compute on the subspace of Y + num_nn_y = _compute_radius_nbrs( + Y, + radius_per_sample, + knn_here, + algorithm=algorithm, + metric=metric, + n_jobs=n_jobs, + ) + + # compute the final MI value + # \\psi(k) - E[(\\psi(n_x) + \\psi(n_y))] + \\psi(n) + hxy = scipy.special.digamma(knn_here) + hx = scipy.special.digamma(num_nn_x) + hy = scipy.special.digamma(num_nn_y) + hn = scipy.special.digamma(n_samples) + val = hxy - (hx + hy).mean() + hn + return val + + +def _cmi_ksg(data, X, Y, Z, metric, algorithm, knn_here, n_jobs): + """Compute KSG estimate of CMI.""" + # estimate distance to the kth NN in XYZ subspace for each sample + neigh = _compute_nn(data, algorithm=algorithm, metric=metric, k=knn_here, n_jobs=n_jobs) + + # get the radius we want to use per sample as the distance to the kth neighbor + # in the joint distribution space + dists, _ = neigh.kneighbors() + radius_per_sample = dists[:, -1] + + # compute on the subspace of XZ + xz_data = np.hstack((X, Z)) + num_nn_xz = _compute_radius_nbrs( + xz_data, + radius_per_sample, + knn_here, + algorithm=algorithm, + metric=metric, + n_jobs=n_jobs, + ) + + # compute on the subspace of YZ + yz_data = np.hstack((Y, Z)) + num_nn_yz = _compute_radius_nbrs( + yz_data, + radius_per_sample, + knn_here, + algorithm=algorithm, + metric=metric, + n_jobs=n_jobs, + ) + + # compute on the subspace of XZ + num_nn_z = _compute_radius_nbrs( + Z, + radius_per_sample, + knn_here, + algorithm=algorithm, + metric=metric, + n_jobs=n_jobs, + ) + + # compute the final CMI value + hxyz = scipy.special.digamma(knn_here) + hxz = scipy.special.digamma(num_nn_xz) + hyz = scipy.special.digamma(num_nn_yz) + hz = scipy.special.digamma(num_nn_z) + val = hxyz - (hxz + hyz - hz).mean() + return val + + +def _compute_radius_nbrs( + data, + radius_per_sample, + k, + algorithm: str = "kd_tree", + metric="l2", + n_jobs: Optional[int] = None, +): + neigh = _compute_nn(data, algorithm=algorithm, metric=metric, k=k, n_jobs=n_jobs) + + n_samples = radius_per_sample.shape[0] + + num_nn_data = np.zeros((n_samples,)) + for idx in range(n_samples): + num_nn = neigh.radius_neighbors(radius=radius_per_sample[idx]) + num_nn_data[idx] = num_nn + return num_nn_data + + +def _compute_nn( + X: ArrayLike, algorithm: str = "kd_tree", metric="l2", k: int = 1, n_jobs: Optional[int] = None +) -> ArrayLike: + """Compute kNN in subspace. + + Parameters + ---------- + X : ArrayLike of shape (n_samples, n_features) + The covariate space. + algorithm : str, optional + Method to use, by default 'knn'. Can be ('ball_tree', 'kd_tree', 'brute'). + metric : str + Any distance metric accepted by :class:`sklearn.neighbors.NearestNeighbors`. + If 'forest', then uses an :class:`UnsupervisedObliqueRandomForest` to compute + geodesic distances. + k : int, optional + The number of k-nearest neighbors to query, by default 1. + n_jobs : int, + The number of CPUs to use for joblib. By default, None. + + Returns + ------- + neigh : instance of sklearn.neighbors.NearestNeighbor + A fitted instance of the nearest-neighbor algorithm on ``X`` input. + + Notes + ----- + Can query for the following, using the ``neigh.kneighbors(X)`` function, which would + return: + + dists : ArrayLike of shape (n_samples, k) + The distance array of every sample with its k-nearest neighbors. The columns + are ordered from closest to furthest neighbors. + indices : ArrayLike of shape (n_samples, k) + The sample indices of the k-nearest-neighbors for each sample. These + contain the row indices of ``X`` for each sample. The columns + are ordered from closest to furthest neighbors. + """ + if metric == "forest": + est = UnsupervisedObliqueRandomForest() + dists = compute_forest_similarity_matrix(est, X, n_jobs=n_jobs) + + # we have a precomputed distance matrix, so we can use the NearestNeighbor + # implementation of sklearn + metric = "precomputed" + else: + dists = X + + # compute the nearest neighbors in the space using specified NN algorithm + # then get the K nearest nbrs and their distances + neigh = NearestNeighbors(n_neighbors=k, algorithm=algorithm, metric=metric, n_jobs=n_jobs).fit( + dists + ) + return neigh + + +def _trafo2uniform(X): + """Transforms input array to uniform marginals. + + Assumes x.shape = (dim, T) + + Parameters + ---------- + X : arraylike + The input data with (n_samples,) rows and (n_features,) columns. + + Returns + ------- + u : array-like + array with uniform marginals. + """ + + def trafo(xi): + xisorted = np.sort(xi) + yi = np.linspace(1.0 / len(xi), 1, len(xi)) + return np.interp(xi, xisorted, yi) + + _, n_features = X.shape + + # apply a uniform transformation for each feature + for idx in range(n_features): + marginalized_feature = trafo(X[:, idx].to_numpy().squeeze()) + X[:, idx] = marginalized_feature + return X diff --git a/sktree/experimental/simulate.py b/sktree/experimental/simulate.py new file mode 100644 index 000000000..fd8de55c8 --- /dev/null +++ b/sktree/experimental/simulate.py @@ -0,0 +1,196 @@ +import numpy as np +import scipy.linalg +import scipy.special +import scipy.stats + + +def simulate_helix( + radius_a=0, + radius_b=1, + obs_noise_func=None, + nature_noise_func=None, + n_samples=1000, + random_seed=None, +): + """Simulate data from a helix. + + Parameters + ---------- + radius_a : int, optional + The value of the smallest radius, by default 0.0. + radius_b : int, optional + The value of the largest radius, by default 1.0 + obs_noise_func : scipy.stats.distribution, optional + By default None, which defaults to a Uniform distribution from + (-0.005, 0.005). If passed in, then must be a callable that when + called returns a random number denoting the noise. + nature_noise_func : callable, optional + By defauult None, which will add no noise. The nature noise func + is just an independent noise term added to ``P`` before it is + passed to the generation of the X, Y, and Z terms. + n_samples : int, optional + Number of samples to generate, by default 1000. + random_seed : int, optional + The random seed. + + Returns + ------- + P : array-like of shape (n_samples,) + The sampled P. + X : array-like of shape (n_samples,) + The X dimension. + Y : array-like of shape (n_samples,) + The X dimension. + Z : array-like of shape (n_samples,) + The X dimension. + + Notes + ----- + Data is generated as follows: We first sample a radius that + defines the helix, :math:`R \\approx Unif(radius_a, radius_b)`. + Afterwards, we generate one sample as follows:: + + P = 5\\pi + 3\\pi R + X = (P + \\epsilon_1) cos(P + \\epsilon_1) / 8\\pi + N_1 + Y = (P + \\epsilon_2) sin(P + \\epsilon_2) / 8\\pi + N_2 + Z = (P + \\epsilon_3) / 8\\pi + N_3 + + where :math:`N_1,N_2,N_3` are noise variables that are independently + sampled for each sample point. And + :math:`\\epsilon_1, \\epsilon_2, \\epsilon_3` are "nature noise" terms + which are off by default. This process is repeated ``n_samples`` times. + + Note, that this forms the graphical model:: + + R \\rightarrow P + + P \\rightarrow X + P \\rightarrow Y + P \\rightarrow Z + + such that P is a confounder among X, Y and Z. This implies that X, Y and Z + are conditionally dependent on P, whereas + """ + rng = np.random.default_rng(random_seed) + + Radii = np.zeros((n_samples,)) + P_arr = np.zeros((n_samples,)) + X_arr = np.zeros((n_samples,)) + Y_arr = np.zeros((n_samples,)) + Z_arr = np.zeros((n_samples,)) + + if obs_noise_func is None: + obs_noise_func = lambda: rng.uniform(-0.005, 0.005) + if nature_noise_func is None: + nature_noise_func = lambda: 0.0 + + for idx in range(n_samples): + Radii[idx] = rng.uniform(radius_a, radius_b) + P_arr[idx] = 5 * np.pi + 3 * np.pi * Radii[idx] + X_arr[idx] = (P_arr[idx] + nature_noise_func) * np.cos(P_arr[idx] + nature_noise_func) / ( + 8 * np.pi + ) + obs_noise_func() + Y_arr[idx] = (P_arr[idx] + nature_noise_func) * np.sin(P_arr[idx] + nature_noise_func) / ( + 8 * np.pi + ) + obs_noise_func() + Z_arr[idx] = (P_arr[idx] + nature_noise_func) / (8 * np.pi) + obs_noise_func() + + return P_arr, X_arr, Y_arr, Z_arr + + +def simulate_sphere(radius=1, noise_func=None, n_samples=1000, random_seed=None): + """Simulate samples generated on a sphere. + + Parameters + ---------- + radius : int, optional + The radius of the sphere, by default 1. + noise_func : callable, optional + The noise function to call to add to samples, by default None, + which defaults to sampling from the uniform distribution [-0.005, 0.005]. + n_samples : int, optional + Number of samples to generate, by default 1000. + random_seed : int, optional + Random seed, by default None. + + Returns + ------- + latitude : float + Latitude. + longitude : float + Longitude. + Y1 : array-like of shape (n_samples,) + The X coordinate. + Y2 : array-like of shape (n_samples,) + The Y coordinate. + Y3 : array-like of shape (n_samples,) + The Z coordinate. + """ + rng = np.random.default_rng(random_seed) + if noise_func is None: + noise_func = lambda: rng.uniform(-0.005, 0.005) + + latitude = np.zeros((n_samples,)) + longitude = np.zeros((n_samples,)) + Y1 = np.zeros((n_samples,)) + Y2 = np.zeros((n_samples,)) + Y3 = np.zeros((n_samples,)) + + for idx in range(n_samples): + # sample latitude and longitude + latitude[idx] = rng.uniform(0, 1) + longitude[idx] = rng.uniform(0, 1) + + Y1[idx] = np.cos(latitude[idx]) * np.cos(longitude[idx]) * radius + noise_func() + Y2[idx] = np.cos(latitude[idx]) * np.sin(longitude[idx]) * radius + noise_func() + Y3[idx] = np.sin(longitude[idx]) * radius + noise_func() + + return latitude, longitude, Y1, Y2, Y3 + + +def simulate_multivariate_gaussian(mean=None, cov=None, d=2, n_samples=1000, seed=1234): + """Multivariate gaussian simulation for testing entropy and MI estimators. + + Simulates samples from a "known" multivariate gaussian distribution + and then passes those samples, along with the true analytical MI/CMI. + + Parameters + ---------- + mean : array-like of shape (d,) + The optional mean array. If None (default), a random standard normal vector is drawn. + cov : array-like of shape (d,d) + The covariance array. If None (default), a random standard normal 2D array is drawn. + It is then converted to a PD matrix. + d : int + The dimensionality of the multivariate gaussian. By default 2. + n_samples : int + The number of samples to generate. By default 1000. + seed : int + The random seed to feed to :func:`numpy.random.default_rng`. + + Returns + ------- + data : array-like of shape (n_samples, d) + The generated data from the distribution. + mean : array-like of shape (d,) + The mean vector of the distribution. + cov : array-like of shape (d,d) + The covariance matrix of the distribution. + """ + rng = np.random.default_rng(seed) + + if mean is None: + mean = rng.normal(size=(d,)) + if cov is None: + # generate random covariance matrix and enure it is symmetric and positive-definite + cov = rng.normal(size=(d, d)) + cov = 0.5 * (cov + cov.T) + else: + if not np.all(np.linalg.eigvals(cov) > 0): + raise RuntimeError("Passed in covariance matrix should be positive definite") + if not scipy.linalg.issymmetric(cov): + raise RuntimeError("Passed in covariance matrix should be symmetric") + + data = rng.multivariate_normal(mean=mean, cov=cov, size=(n_samples)) + + return data, mean, cov diff --git a/sktree/experimental/tests/test_mutual_info.py b/sktree/experimental/tests/test_mutual_info.py new file mode 100644 index 000000000..a1c35dd30 --- /dev/null +++ b/sktree/experimental/tests/test_mutual_info.py @@ -0,0 +1,52 @@ +import numpy as np + + +def nonlinear_gaussian_with_additive_noise(): + """Nonlinear no-noise function with additive Gaussian noise. + + See: https://github.com/BiuBiuBiLL/NPEET_LNC/issues/4 + """ + # first simulate multivariate Gaussian without noise + + # then add the noise + + # compute MI by computing the H(Y|X) and H(X) + # H(Y|X) = log(noise_std) + # H(X) = kNN K-L estimate with large # of samples + pass + + +def main(): + d1 = [1, 1, 0] + d2 = [1, 0, 1] + d3 = [0, 1, 1] + mat = [d1, d2, d3] + tmat = np.transpose(mat) + diag = [[3, 0, 0], [0, 1, 0], [0, 0, 1]] + mean = np.array([0, 0, 0]) + cov = np.dot(tmat, np.dot(diag, mat)) + print("covariance matrix") + print(cov) + print(tmat) + + +def test_mi(): + d1 = [1, 1, 0] + d2 = [1, 0, 1] + d3 = [0, 1, 1] + mat = [d1, d2, d3] + tmat = np.transpose(mat) + diag = [[3, 0, 0], [0, 1, 0], [0, 0, 1]] + mean = np.array([0, 0, 0]) + cov = np.dot(tmat, np.dot(diag, mat)) + print("covariance matrix") + print(cov) + trueent = -0.5 * (3 + log(8.0 * pi * pi * pi * det(cov))) + trueent += -0.5 * (1 + log(2.0 * pi * cov[2][2])) # z sub + trueent += 0.5 * ( + 2 + log(4.0 * pi * pi * det([[cov[0][0], cov[0][2]], [cov[2][0], cov[2][2]]])) + ) # xz sub + trueent += 0.5 * ( + 2 + log(4.0 * pi * pi * det([[cov[1][1], cov[1][2]], [cov[2][1], cov[2][2]]])) + ) # yz sub + print("true CMI(x:y|x)", trueent / log(2)) diff --git a/sktree/experimental/tests/test_simulate.py b/sktree/experimental/tests/test_simulate.py new file mode 100644 index 000000000..15a140b69 --- /dev/null +++ b/sktree/experimental/tests/test_simulate.py @@ -0,0 +1,40 @@ +import pytest + +from sktree.experimental.simulate import ( + simulate_helix, + simulate_multivariate_gaussian, + simulate_sphere, +) + + +# Test simulate_helix function +def test_simulate_helix(): + P, X, Y, Z = simulate_helix(n_samples=1000) + assert len(P) == 1000 + assert len(X) == 1000 + assert len(Y) == 1000 + assert len(Z) == 1000 + + # Add more specific tests if necessary + + +# Test simulate_sphere function +def test_simulate_sphere(): + latitude, longitude, Y1, Y2, Y3 = simulate_sphere(n_samples=1000) + assert len(latitude) == 1000 + assert len(longitude) == 1000 + assert len(Y1) == 1000 + assert len(Y2) == 1000 + assert len(Y3) == 1000 + + # Add more specific tests if necessary + + +# Test simulate_multivariate_gaussian function +def test_simulate_multivariate_gaussian(): + data, mean, cov = simulate_multivariate_gaussian(d=2, n_samples=1000) + assert data.shape == (1000, 2) + assert mean.shape == (2,) + assert cov.shape == (2, 2) + + # Add more specific tests if necessary From b688edc831a08dfab61088cf4089f7bdfcde09f2 Mon Sep 17 00:00:00 2001 From: Adam Li Date: Thu, 15 Jun 2023 12:08:25 -0400 Subject: [PATCH 03/17] WIP to add marginalization capabilities Signed-off-by: Adam Li --- .spin/cmds.py | 3 +- README.md | 4 + experiments/Untitled.ipynb | 541 ++++++++++++++++++ experiments/plotting_cmi_analysis.ipynb | 530 +++++++++++++++++ pyproject.toml | 2 +- sktree/__init__.py | 3 +- sktree/experimental/__init__.py | 9 +- sktree/experimental/meson.build | 5 +- sktree/experimental/mutual_info.py | 50 +- sktree/experimental/simulate.py | 71 ++- sktree/experimental/tests/__init__.py | 0 sktree/experimental/tests/meson.build | 11 + sktree/experimental/tests/test_mutual_info.py | 18 +- sktree/experimental/tests/test_simulate.py | 2 - sktree/tree/__init__.py | 2 + sktree/tree/_honest_tree.py | 14 +- sktree/tree/_marginal.pxd | 19 + sktree/tree/_marginal.pyx | 191 +++++++ sktree/tree/_marginalize.py | 189 ++++++ sktree/tree/_neighbors.py | 139 +++++ sktree/tree/_utils.pxd | 2 + sktree/tree/_utils.pyx | 19 + sktree/tree/manifold/_morf_splitter.pxd | 1 - sktree/tree/meson.build | 4 +- sktree/tree/tests/meson.build | 3 +- sktree/tree/tests/test_marginal.py | 60 ++ 26 files changed, 1823 insertions(+), 69 deletions(-) create mode 100644 experiments/Untitled.ipynb create mode 100644 experiments/plotting_cmi_analysis.ipynb create mode 100644 sktree/experimental/tests/__init__.py create mode 100644 sktree/experimental/tests/meson.build create mode 100644 sktree/tree/_marginal.pxd create mode 100644 sktree/tree/_marginal.pyx create mode 100644 sktree/tree/_marginalize.py create mode 100644 sktree/tree/tests/test_marginal.py diff --git a/.spin/cmds.py b/.spin/cmds.py index 9a2b0e6de..8427b5b78 100644 --- a/.spin/cmds.py +++ b/.spin/cmds.py @@ -80,7 +80,7 @@ def setup_submodule(forcesubmodule=False): "submodule", "update", "--init", - "--force", + # "--force", ] ) @@ -111,6 +111,7 @@ def setup_submodule(forcesubmodule=False): commit_fpath, ], ) + print(commit_fpath) with open(commit_fpath, "w") as f: f.write(current_hash) diff --git a/README.md b/README.md index 8b9ed72c6..9d426eda6 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,10 @@ You can also do the same thing using Meson/Ninja itself. Run the following to bu python -c "from sktree import tree" python -c "import sklearn; print(sklearn.__version__);" +Alternatively, you can use editable installs + + pip install --no-build-isolation --editable . + References ========== [1]: [`Li, Adam, et al. "Manifold Oblique Random Forests: Towards Closing the Gap on Convolutional Deep Networks." arXiv preprint arXiv:1909.11799 (2019)`](https://arxiv.org/abs/1909.11799) \ No newline at end of file diff --git a/experiments/Untitled.ipynb b/experiments/Untitled.ipynb new file mode 100644 index 000000000..890b026ba --- /dev/null +++ b/experiments/Untitled.ipynb @@ -0,0 +1,541 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "98d65440-6674-4ce3-9c32-373ba84050c6", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "60e996a0-6bba-4cd2-893b-e0b9b9eda14f", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_stata('/Users/adam2392/Downloads/ICPSR_04291/DS0001/04291-0001-Data.dta')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "905584e4-93b7-4aaa-9b4b-646960588544", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
COLL_IDSERIALSTUDY_IDA1A2A3A4A4AA5A6...F30FRNDAGEGROUPAGELT21AGE2123AGEGT23SELFRATESELFEVERSTWGT_01WEIGHT01VOL30
0NaNNaN118.0female (0)freshman (1)No, did not transfer (1)NaNNo (0)single sex res/dorm (1)...No (0)Age < 21 (1)Yes (1)No (0)No (0)NaNNaN0.6646300.8109780.0
1NaNNaN224.0female (0)senior (4)yes current year (2)same state (2)No (0)other university housing(3)...No (0)Age > 23 (3)No (0)No (0)Yes (1)No (0)No (0)0.6544430.5298471.5
2NaNNaN325.0female (0)senior (4)yes before this year (3)same state (2)No (0)off campus house/apt (5)...No (0)Age > 23 (3)No (0)No (0)Yes (1)No (0)No (0)0.5986750.4598084.0
3NaNNaN419.0female (0)freshman (1)No, did not transfer (1)NaNYes (1)single sex res/dorm (1)...Yes (1)Age < 21 (1)Yes (1)No (0)No (0)No (0)No (0)0.4303060.3942361.5
4NaNNaN519.0male (1)freshman (1)No, did not transfer (1)NaNYes (1)coed res/dorm (2)...No (0)Age < 21 (1)Yes (1)No (0)No (0)No (0)No (0)1.1930081.27556460.0
\n", + "

5 rows × 483 columns

\n", + "
" + ], + "text/plain": [ + " COLL_ID SERIAL STUDY_ID A1 A2 A3 \\\n", + "0 NaN NaN 1 18.0 female (0) freshman (1) \n", + "1 NaN NaN 2 24.0 female (0) senior (4) \n", + "2 NaN NaN 3 25.0 female (0) senior (4) \n", + "3 NaN NaN 4 19.0 female (0) freshman (1) \n", + "4 NaN NaN 5 19.0 male (1) freshman (1) \n", + "\n", + " A4 A4A A5 \\\n", + "0 No, did not transfer (1) NaN No (0) \n", + "1 yes current year (2) same state (2) No (0) \n", + "2 yes before this year (3) same state (2) No (0) \n", + "3 No, did not transfer (1) NaN Yes (1) \n", + "4 No, did not transfer (1) NaN Yes (1) \n", + "\n", + " A6 ... F30FRND AGEGROUP AGELT21 AGE2123 \\\n", + "0 single sex res/dorm (1) ... No (0) Age < 21 (1) Yes (1) No (0) \n", + "1 other university housing(3) ... No (0) Age > 23 (3) No (0) No (0) \n", + "2 off campus house/apt (5) ... No (0) Age > 23 (3) No (0) No (0) \n", + "3 single sex res/dorm (1) ... Yes (1) Age < 21 (1) Yes (1) No (0) \n", + "4 coed res/dorm (2) ... No (0) Age < 21 (1) Yes (1) No (0) \n", + "\n", + " AGEGT23 SELFRATE SELFEVER STWGT_01 WEIGHT01 VOL30 \n", + "0 No (0) NaN NaN 0.664630 0.810978 0.0 \n", + "1 Yes (1) No (0) No (0) 0.654443 0.529847 1.5 \n", + "2 Yes (1) No (0) No (0) 0.598675 0.459808 4.0 \n", + "3 No (0) No (0) No (0) 0.430306 0.394236 1.5 \n", + "4 No (0) No (0) No (0) 1.193008 1.275564 60.0 \n", + "\n", + "[5 rows x 483 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(df.head())" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "70255bdc-695f-4c79-8c2d-8acffaa63572", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
COLL_IDSERIALSTUDY_IDA1C6_FEEC21_FEEF7F8MONTHDAYYEARCOMMENTSNUMPROBSURVDATEFULLYEARSTWGT_01WEIGHT01VOL30
count0.00.010904.00000010898.000000290.000000311.00000010820.00000010829.00000010891.00000010889.00000010904.010807.0000008578.00000010888.00000010904.010904.00000010904.00000010787.000000
meanNaNNaN5452.50000020.8238215.9346215.4474282.5610913.7776343.76604514.1576821.00.0279450.18467013774.7192322001.00.9521140.95386621.053861
stdNaNNaN3147.8580022.0446014.3531883.5141112.1221292.2186230.8706998.3210530.00.1839280.205743903.5393990.00.4952400.49252938.982783
minNaNNaN1.00000017.0000001.0000001.0000000.0000000.0000001.0000001.0000001.00.0000000.0000003001.0000002001.00.0411180.0359190.000000
25%NaNNaN2726.75000019.0000004.0000004.0000001.0000002.0000003.0000007.0000001.00.0000000.00000013014.0000002001.00.6904400.6946830.000000
50%NaNNaN5452.50000020.0000005.0000005.0000002.0000004.0000004.00000013.0000001.00.0000000.08000014011.0000002001.00.8635450.8765366.000000
75%NaNNaN8178.25000022.0000005.0000005.0000004.0000005.0000004.00000020.0000001.00.0000000.33000014023.0000002001.01.0997491.13905722.500000
maxNaNNaN10904.00000025.00000035.00000040.0000007.0000007.0000008.00000031.0000001.09.0000001.00000018028.0000002001.07.8511458.626275360.000000
\n", + "
" + ], + "text/plain": [ + " COLL_ID SERIAL STUDY_ID A1 C6_FEE C21_FEE \\\n", + "count 0.0 0.0 10904.000000 10898.000000 290.000000 311.000000 \n", + "mean NaN NaN 5452.500000 20.823821 5.934621 5.447428 \n", + "std NaN NaN 3147.858002 2.044601 4.353188 3.514111 \n", + "min NaN NaN 1.000000 17.000000 1.000000 1.000000 \n", + "25% NaN NaN 2726.750000 19.000000 4.000000 4.000000 \n", + "50% NaN NaN 5452.500000 20.000000 5.000000 5.000000 \n", + "75% NaN NaN 8178.250000 22.000000 5.000000 5.000000 \n", + "max NaN NaN 10904.000000 25.000000 35.000000 40.000000 \n", + "\n", + " F7 F8 MONTH DAY YEAR \\\n", + "count 10820.000000 10829.000000 10891.000000 10889.000000 10904.0 \n", + "mean 2.561091 3.777634 3.766045 14.157682 1.0 \n", + "std 2.122129 2.218623 0.870699 8.321053 0.0 \n", + "min 0.000000 0.000000 1.000000 1.000000 1.0 \n", + "25% 1.000000 2.000000 3.000000 7.000000 1.0 \n", + "50% 2.000000 4.000000 4.000000 13.000000 1.0 \n", + "75% 4.000000 5.000000 4.000000 20.000000 1.0 \n", + "max 7.000000 7.000000 8.000000 31.000000 1.0 \n", + "\n", + " COMMENTS NUMPROB SURVDATE FULLYEAR STWGT_01 \\\n", + "count 10807.000000 8578.000000 10888.000000 10904.0 10904.000000 \n", + "mean 0.027945 0.184670 13774.719232 2001.0 0.952114 \n", + "std 0.183928 0.205743 903.539399 0.0 0.495240 \n", + "min 0.000000 0.000000 3001.000000 2001.0 0.041118 \n", + "25% 0.000000 0.000000 13014.000000 2001.0 0.690440 \n", + "50% 0.000000 0.080000 14011.000000 2001.0 0.863545 \n", + "75% 0.000000 0.330000 14023.000000 2001.0 1.099749 \n", + "max 9.000000 1.000000 18028.000000 2001.0 7.851145 \n", + "\n", + " WEIGHT01 VOL30 \n", + "count 10904.000000 10787.000000 \n", + "mean 0.953866 21.053861 \n", + "std 0.492529 38.982783 \n", + "min 0.035919 0.000000 \n", + "25% 0.694683 0.000000 \n", + "50% 0.876536 6.000000 \n", + "75% 1.139057 22.500000 \n", + "max 8.626275 360.000000 " + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b3cd1c39-1244-4855-b173-240d3a03b2ac", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sktree", + "language": "python", + "name": "sktree" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/experiments/plotting_cmi_analysis.ipynb b/experiments/plotting_cmi_analysis.ipynb new file mode 100644 index 000000000..0cf705ed1 --- /dev/null +++ b/experiments/plotting_cmi_analysis.ipynb @@ -0,0 +1,530 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b0125b7c-d6d3-46a7-9e86-c3f7e2f6cda3", + "metadata": {}, + "source": [ + "# Analysis of (Conditional) Mutual Information Estimators Using Forests - Sample Efficiency\n", + "\n", + "In this simulation notebook, we will evaluate the sample efficiency of using forests to estimate\n", + "(conditional) mutual information. We will replicate the findings of https://arxiv.org/pdf/2110.13883.pdf. \n", + "\n", + "The data we will simulate comes from the following distributions for mutual information:\n", + "\n", + "- Helix: X is dependent on Y on a helix\n", + "- Sphere: X is dependent on Y\n", + "- Uniform: X is dependent on Y\n", + "- Gaussian: X is dependent on Y\n", + "- independent: X is completely independent of Y\n", + "\n", + "The data we will simulate comes from the following distributions for conditional mutual information:\n", + "\n", + "- Uniform: X is conditionally dependent on Y\n", + "- Gaussian: X is conditionally dependent on Y\n", + "\n", + "For each distribution, we will add a varying number of independent dimensions to the data (i.e. sampled from Gaussian distribution)." + ] + }, + { + "cell_type": "code", + "execution_count": 142, + "id": "2e7b6950-44a0-40a9-bf2a-b6eca06725c1", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import scipy\n", + "import scipy.spatial\n", + "\n", + "from sklearn.neighbors import NearestNeighbors\n", + "import sktree\n", + "from sktree.experimental.simulate import (simulate_helix, simulate_multivariate_gaussian, simulate_sphere)\n", + "from sktree.experimental.mutual_info import (\n", + " entropy_gaussian, entropy_weibull,\n", + " cmi_from_entropy,\n", + " mi_from_entropy,\n", + " mutual_info_ksg,\n", + " mi_gaussian, cmi_gaussian,\n", + " mi_gamma, \n", + ")\n", + "from sktree.tree import compute_forest_similarity_matrix\n", + "from sktree import UnsupervisedRandomForest, UnsupervisedObliqueRandomForest, ObliqueRandomForestClassifier\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "bf387bc2-f1d1-4b7a-9dab-e9c8fb992b2a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "markdown", + "id": "8ca87428-e8c8-46df-829f-116261c54f86", + "metadata": {}, + "source": [ + "## Define Hyperparameters of the Simulation" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e5089afb-c359-423d-92b1-641fca6315b2", + "metadata": {}, + "outputs": [], + "source": [ + "seed =12345\n", + "rng = np.random.default_rng(seed)" + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "id": "4fcde0a4-7413-4f09-bbd8-fe62fcd62c2d", + "metadata": {}, + "outputs": [], + "source": [ + "n_jobs = -1\n", + "\n", + "# hyperparameters of the simulation\n", + "n_samples = 500\n", + "n_noise_dims = 10\n", + "alpha = 0.01\n", + "\n", + "# dimensionality of mvg\n", + "d = 3\n", + "\n", + "# for sphere\n", + "radius = 1.0\n", + "\n", + "# for helix\n", + "radius_a = 0.0\n", + "radius_b = 2.0\n", + "\n", + "# manifold parameters\n", + "radii_func = lambda: rng.uniform(0, 1)" + ] + }, + { + "cell_type": "markdown", + "id": "dc222a29-5a31-486e-b2e3-6e276a61f81f", + "metadata": {}, + "source": [ + "## Demonstrate a single simulation\n", + "\n", + "Now, to demonstrate what the data would look like fromm a single parameterized simulation, we want to show the entire workflow from data generation to analysis and output value." + ] + }, + { + "cell_type": "code", + "execution_count": 139, + "id": "3cc73c4e-78b6-48b3-83b3-091d365d8ada", + "metadata": {}, + "outputs": [], + "source": [ + "# generate helix data\n", + "helix_data = simulate_helix(radius_a=radius_a, radius_b=radius_b, alpha=alpha/2, n_samples=n_samples, return_mi_lb=True, random_seed=seed)\n", + "P, X, Y, Z, helix_lb = helix_data\n", + "\n", + "# generate sphere data\n", + "sphere_data = simulate_sphere(radius=radius, alpha=alpha, n_samples=n_samples, return_mi_lb=True, random_seed=seed)\n", + "lat, lon, Y1, Y2, Y3, lb = sphere_data\n", + "\n", + "# simulate multivariate Gaussian\n", + "mvg_data = simulate_multivariate_gaussian(d=d, n_samples=n_samples, seed=seed)\n", + "data, mean, cov = mvg_data" + ] + }, + { + "cell_type": "code", + "execution_count": 124, + "id": "aa1a67bb-55fa-4983-b2d1-a23102945b7c", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# helix data\n", + "fig = plt.figure()\n", + "ax1 = fig.add_subplot(111, projection='3d')\n", + "\n", + "ax1.plot( X, Y, Z, '*', c='b', label='Helix')\n", + "ax1.legend(loc='upper left');\n", + "ax1.axis('equal')\n", + "# ax1.set(\n", + "# xlim=[-1, 1],\n", + "# ylim=[-1, 1],\n", + "# zlim=[0.25, 1],\n", + "# )\n", + "fig.tight_layout()\n", + "elev = 20\n", + "azim = 50\n", + "roll = 0\n", + "ax1.view_init(elev, azim, roll)\n", + "\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "a841ffc8-e821-4c0a-8df0-9b55b1be3aba", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZ8AAAGOCAYAAABIaA6qAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/P9b71AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOx9d3wUdf7+M7MtvWx6J4TeOySIeoqid37PLujxUyxYOc9y1rOdvZzKWVHsSlNR787zsKB0RIUkhFQSQgrpW1I2ybaZ3x/DZ3Zmd3Z3tiQEnef14hWy2Z2ZnZ39PPN+v5/386ZYlmWhQIECBQoUDCPoE30AChQoUKDgtweFfBQoUKBAwbBDIR8FChQoUDDsUMhHgQIFChQMOxTyUaBAgQIFww6FfBQoUKBAwbBDIR8FChQoUDDsUMhHgQIFChQMOxTyUaBAgQIFww6FfBQoUKBAwbBDIR8FChQoUDDsUMhHgQIFChQMOxTyUaBAgQIFww6FfBQoUKBAwbBDHc6NOZ1O2O32cG5SgUxoNBqoVKoTfRgKFChQIAthIR+WZdHW1gaz2RyOzSkIEgkJCUhPTwdFUSf6UBQoUKDAJ8JCPoR4UlNTERUVpSx+wwyWZdHf34+Ojg4AQEZGxgk+IgUKFCjwjZDJx+l08sSTlJQUjmNSEAQiIyMBAB0dHUhNTVVScAoUKBjRCFlwQGo8UVFRIR+MgtBAPgOl7qZAgYKRjrCp3ZRU24mH8hkoUKDgZIEitVagQIECBcMOhXwUKFCgQMGwQyGfEHD06FFQFIWSkpITfSgKFChQcFJhRJHPL78AZ5zB/RwOdHZ24qabbkJubi50Oh3S09OxZMkS7N69e3gOQIECBQp+owirw0Go+OAD4IcfgA8/BObMGfr9XXzxxbDZbHj//fcxevRotLe3Y+vWrTAYDEO/cx+w2WzQarUn9BgUKFCgYChxwiOfhgZg/37gwAFg0ybusY0bud/37+f+PhQwm83YuXMnnnnmGfzud79DXl4e5s2bh/vuuw9//OMfAXDqsddffx3nnnsuIiMjMXr0aHz66ace2zpy5Ah+97vfISoqCtOnT8fevXtFf9+1axcWLVqEyMhI5OTk4NZbb4XFYuH/PmrUKDz22GO48sorERcXh+uvv17W6xQoUKDgpAUbIgYGBtiKigp2YGAgqNcDrn8UJf5J/g0F7HY7GxMTw952223s4OCgl2MDm5SUxK5du5atrq5mH3jgAValUrEVFRUsy7JsfX09C4CdMGEC++WXX7LV1dXsJZdcwubl5bF2u51lWZatra1lo6Oj2RdffJGtqalhd+/ezc6cOZNdsWIFv5+8vDw2Li6O/cc//sHW1tby//y9zh2hfhYKFChQMFw44eTz0Ucsq1aLyYb8U6u5vw8VPv30UzYxMZGNiIhgi4qK2Pvuu48tLS3l/w6AvfHGG0WvmT9/PnvTTTexLOsin7feeov/e3l5OQuAraysZFmWZa+99lr2+uuvF21j586dLE3T/DnLy8tjL7jgAtFz5LzOHQr5KFCg4GTBCU+7/elPwL590n/bt4/7+1Dh4osvRktLC/7973/jnHPOwbZt2zBr1iy89957/HMKCwtFryksLERlZaXosWnTpvH/J75qxGettLQU7733HmJiYvh/S5YsAcMwqK+v5183x63IJfd1ChQoUHAyYkQJDmgaYBjXz+FAREQEzjrrLJx11ll48MEHcd111+Hhhx/GihUrZG9Do9Hw/ycuA8zxN9DX14cbbrgBt956q8frcnNz+f9HR0eL/ib3dQoUKFBwMmJEkE9qKpCeDuTkANdeC7z9NtDUxD0+3Jg0aRK++OIL/vcff/wRV155pej3mTNnyt7erFmzUFFRgTFjxgR0HMG+ToECBQpOBowI8snOBo4eBbRagKKA668HbDZApxu6fRoMBlx66aW45pprMG3aNMTGxuKXX37Bs88+i/PPP59/3ieffII5c+bglFNOwbp16/DTTz/h7bfflr2fe+65BwsWLMCqVatw3XXXITo6GhUVFfj222/xyiuvhP11ChQoUHAyYESQDyAmGooaWuIBgJiYGMyfPx8vvvgi6urqYLfbkZOTg5UrV+L+++/nn/f3v/8dGzduxM0334yMjAxs2LABkyZNkr2fadOmYfv27fjb3/6GRYsWgWVZFBQUYOnSpUPyOgUKFCg4GUCxLMuGsoHBwUHU19cjPz8fERER4TquEQGKovD555/jggsuONGHIgu/5s9CgQIFvy6ccLWbAgUKFCj47UEhHwUKFChQMOwYMTWfkYgQM5IKJMCyLBiGgdVqhUql4v8pg/AUKPhtQSEfBcMGlmVht9vhdDoxODgIgKur0TQNtVoNtVqtkJECBb8RhI18mOHqClXgFSP5M2AY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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# sphere data\n", + "fig = plt.figure()\n", + "ax1 = fig.add_subplot(111, projection='3d')\n", + "\n", + "ax1.plot( Y1, Y2, Y3, '*', c='b', label='Sphere')\n", + "ax1.legend(loc='upper left');\n", + "ax1.axis('equal')\n", + "\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 140, + "id": "383a17a7-6c4f-49fc-aa4d-322f820ac18e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0.31087872 0.01905156 -0.19413813]\n", + " [ 0.01905156 1.21114085 2.27200637]\n", + " [-0.19413813 2.27200637 5.13099624]]\n", + "[6.17646933 0.35752001 0.11902648]\n" + ] + } + ], + "source": [ + "print(cov)\n", + "print(np.linalg.eigvals(cov))" + ] + }, + { + "cell_type": "code", + "execution_count": 141, + "id": "570cf521-f62f-4e62-98e4-0aea8e4f72db", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# mvg data\n", + "fig = plt.figure()\n", + "ax1 = fig.add_subplot(111, projection='3d')\n", + "\n", + "ax1.plot(data[:, 0], data[:, 1], data[:, 2], '*', c='b', label='MVG')\n", + "ax1.legend(loc='upper left');\n", + "ax1.axis('equal')\n", + "elev = 20\n", + "azim = 50\n", + "roll = 0\n", + "ax1.view_init(elev, azim, roll)\n", + "\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "e642837c-d6b0-40c0-94b8-136127796ee1", + "metadata": {}, + "outputs": [], + "source": [ + "# fit an unsupervised forest\n", + "unsup_rf = Un" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "155e06b8-be96-421c-9bbe-ea8333670c8f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(30, 2)\n", + "(30, 2) (30, 2)\n" + ] + } + ], + "source": [ + "print(X.shape)\n", + "print(indices.shape, dists.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "ed563ca6-fb7c-4ed0-a6c8-e12cebfad1e1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[0. 1.]\n", + " [0. 1.]\n", + " [0. 1.]\n", + " [0. 1.]\n", + " [0. 1.]]\n" + ] + } + ], + "source": [ + "print(dists[:5, :])" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "8d454e2c-3cee-4dcf-b386-cd613e6f8b32", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[0. 0.25 0.5 ]\n", + " [0.25 0. 0.75]\n", + " [0.5 0.75 0. ]]\n", + "[[0. 0.25 0.5 ]\n", + " [0. 0. 0.75]\n", + " [0. 0. 0. ]]\n" + ] + } + ], + "source": [ + "X = np.arange(9).reshape((3, -1))\n", + "X = 0.5 * (X + X.T)\n", + "X = X / np.max(X)\n", + "X[np.diag_indices_from(X)] = 0.0\n", + "print(X)\n", + "\n", + "print(np.triu(X, 1))" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "e35dcdc7-f0d7-478e-a7f4-dc40f33af292", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(3, 3) (3, 3)\n", + "[[0 1 2]\n", + " [1 0 2]\n", + " [2 0 1]]\n", + "[[0. 0.25 0.5 ]\n", + " [0. 0.25 0.75]\n", + " [0. 0.5 0.75]]\n" + ] + } + ], + "source": [ + "\n", + "nbrs = NearestNeighbors(\n", + " n_neighbors=3, metric=\"precomputed\", n_jobs=n_jobs\n", + " ).fit(X)\n", + "\n", + "# then get the K nearest nbrs in the Z space\n", + "dists, indices = nbrs.kneighbors(X)\n", + "\n", + "print(dists.shape, indices.shape)\n", + "print(indices)\n", + "print(dists)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "b6769533-095a-46d3-ab25-5adf10503bcc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(array([1, 2, 2]), array([0, 0, 1]))\n", + "[3 6 7]\n" + ] + } + ], + "source": [ + "# get the triu indices\n", + "triu_idx = np.triu_indices_from(X, 1)\n", + "\n", + "print(triu_idx)\n", + "print(X[triu_idx])\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "6806acd9-bae0-4d08-9f34-a1026bcc9f31", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[0 1]\n", + " [0 2]\n", + " [1 2]]\n", + "[1 2 5]\n" + ] + } + ], + "source": [ + "# get the triu indices\n", + "triu_idx = np.triu_indices_from(X, 1)\n", + "\n", + "triu_ravel_argsort_idx = np.argsort(X[triu_idx], axis=None)\n", + "\n", + "triu_argsort_idx = np.vstack(triu_idx).T[triu_ravel_argsort_idx].squeeze()\n", + "\n", + "print(triu_argsort_idx)\n", + "print(X[triu_argsort_idx[:, 0], triu_argsort_idx[:, 1]])\n", + "# print(ravel_argsort_idx)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "1862eae0-ef26-4cfc-8448-c835053c9edb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(array([0, 0, 0]), array([0, 1, 2]))\n" + ] + } + ], + "source": [ + "print(np.unravel_index(ravel_argsort_idx, (3, 3)))" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "f321a9a1-1006-41d5-9eb0-5739929fa331", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0 1 2]\n", + "[[0 0]\n", + " [0 1]\n", + " [0 2]]\n" + ] + } + ], + "source": [ + "argsort_idx = np.unravel_index(ravel_argsort_idx, (3, 3))\n", + "\n", + "print(ravel_argsort_idx)\n", + "print(np.vstack(argsort_idx).T)" + ] + }, + { + "cell_type": "markdown", + "id": "67330326-469a-48de-9107-7d5cfdcfd4b7", + "metadata": {}, + "source": [ + "# Final Analysis Across All Possible Parametrizations\n", + "\n", + "Now, we want to analyze Unsup-Forest-KSG, Sup-Forest-KSG, Uncertainty-Forest, and KSG-estimator for MI. Moreover, we can implement all traditional RF and oblique RF." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a875b34-9a97-4bdf-ab45-14a1be6e60a5", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sktree", + "language": "python", + "name": "sktree" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyproject.toml b/pyproject.toml index f33ff3a29..b0021607b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -204,7 +204,7 @@ exclude_lines = ['pragma: no cover', 'if __name__ == .__main__.:'] precision = 2 [tool.bandit] -exclude_dirs = ["sktree/tests", "sktree/tree/tests", 'sktree/_build_utils/*', 'sktree/_lib/*'] +exclude_dirs = ["sktree/tests", "sktree/**/tests/*", 'sktree/_build_utils/*', 'sktree/_lib/*'] skips = ['B404', 'B603'] [tool.spin] diff --git a/sktree/__init__.py b/sktree/__init__.py index 51db668c4..d54b96834 100644 --- a/sktree/__init__.py +++ b/sktree/__init__.py @@ -36,7 +36,7 @@ # process, as it may not be compiled yet else: try: - from . import _lib, tree, ensemble + from . import _lib, tree, ensemble, experimental from .ensemble._unsupervised_forest import ( UnsupervisedRandomForest, UnsupervisedObliqueRandomForest, @@ -57,6 +57,7 @@ __all__ = [ "_lib", "tree", + "experimental", "ensemble", "ObliqueRandomForestClassifier", "ObliqueRandomForestRegressor", diff --git a/sktree/experimental/__init__.py b/sktree/experimental/__init__.py index 95c9c07a6..bf88ce488 100644 --- a/sktree/experimental/__init__.py +++ b/sktree/experimental/__init__.py @@ -1,10 +1,11 @@ +from . import mutual_info, simulate from .mutual_info import ( + cmi_from_entropy, + cmi_gaussian, entropy_gaussian, entropy_weibull, - mi_gaussian, + mi_from_entropy, mi_gamma, - cmi_gaussian, + mi_gaussian, mutual_info_ksg, - mi_from_entropy, - cmi_from_entropy, ) diff --git a/sktree/experimental/meson.build b/sktree/experimental/meson.build index badbb1e18..e92935a50 100644 --- a/sktree/experimental/meson.build +++ b/sktree/experimental/meson.build @@ -1,10 +1,13 @@ python_sources = [ '__init__.py', 'mutual_info.py', + 'simulate.py', ] py3.install_sources( python_sources, pure: false, subdir: 'sktree/experimental' -) \ No newline at end of file +) + +subdir('tests') \ No newline at end of file diff --git a/sktree/experimental/mutual_info.py b/sktree/experimental/mutual_info.py index 839d1b528..57eae54d7 100644 --- a/sktree/experimental/mutual_info.py +++ b/sktree/experimental/mutual_info.py @@ -207,19 +207,30 @@ def mutual_info_ksg( """ rng = np.random.default_rng(random_seed) - n_samples, n_features_x = X.shape - _, n_features_y = Y.shape data = np.hstack((X, Y)) - if Z is not None: - _, n_features_z = Z.shape data = np.hstack((data, Z)) + + data = _preprocess_data(data, transform, rng) + n_samples = data.shape[0] + + if k < 1: + knn_here = max(1, int(k * n_samples)) + else: + knn_here = max(1, int(k)) + + if Z is not None: + val = _cmi_ksg(data, X, Y, Z, metric, algorithm, knn_here, n_jobs) else: - n_features_z = 0 - n_features = n_features_x + n_features_y + n_features_z + val = _mi_ksg(data, X, Y, metric, algorithm, knn_here, n_jobs) + return val + + +def _preprocess_data(data, transform, rng): + n_samples, n_features = data.shape # add minor noise to make sure there are no ties - random_noise = rng.random((n_samples, n_features_x)) + random_noise = rng.random((n_samples, n_features)) data += 1e-5 * random_noise @ np.std(data, axis=0).reshape(n_features, 1) if transform == "standardize": @@ -232,17 +243,7 @@ def mutual_info_ksg( elif transform == "rank": # rank transform each column data = scipy.stats.rankdata(data, axis=0) - - if k < 1: - knn_here = max(1, int(k * n_samples)) - else: - knn_here = max(1, int(k)) - - if Z is not None: - val = _cmi_ksg(data, X, Y, Z, metric, algorithm, knn_here, n_jobs) - else: - val = _mi_ksg(data, X, Y, metric, algorithm, knn_here, n_jobs) - return val + return data def _mi_ksg(data, X, Y, metric, algorithm, knn_here, n_jobs): @@ -250,10 +251,9 @@ def _mi_ksg(data, X, Y, metric, algorithm, knn_here, n_jobs): n_samples = X.shape[0] # estimate distance to the kth NN in XYZ subspace for each sample + # - get the radius we want to use per sample as the distance to the kth neighbor + # in the joint distribution space neigh = _compute_nn(data, algorithm=algorithm, metric=metric, k=knn_here, n_jobs=n_jobs) - - # get the radius we want to use per sample as the distance to the kth neighbor - # in the joint distribution space dists, _ = neigh.kneighbors() radius_per_sample = dists[:, -1] @@ -352,14 +352,14 @@ def _compute_radius_nbrs( num_nn_data = np.zeros((n_samples,)) for idx in range(n_samples): - num_nn = neigh.radius_neighbors(radius=radius_per_sample[idx]) - num_nn_data[idx] = num_nn + nn = neigh.radius_neighbors(radius=radius_per_sample[idx], return_distance=False) + num_nn_data[idx] = len(nn) return num_nn_data def _compute_nn( X: ArrayLike, algorithm: str = "kd_tree", metric="l2", k: int = 1, n_jobs: Optional[int] = None -) -> ArrayLike: +) -> NearestNeighbors: """Compute kNN in subspace. Parameters @@ -397,7 +397,7 @@ def _compute_nn( """ if metric == "forest": est = UnsupervisedObliqueRandomForest() - dists = compute_forest_similarity_matrix(est, X, n_jobs=n_jobs) + dists = compute_forest_similarity_matrix(est, X) # we have a precomputed distance matrix, so we can use the NearestNeighbor # implementation of sklearn diff --git a/sktree/experimental/simulate.py b/sktree/experimental/simulate.py index fd8de55c8..01d3dc6ae 100644 --- a/sktree/experimental/simulate.py +++ b/sktree/experimental/simulate.py @@ -9,7 +9,9 @@ def simulate_helix( radius_b=1, obs_noise_func=None, nature_noise_func=None, + alpha=0.005, n_samples=1000, + return_mi_lb=False, random_seed=None, ): """Simulate data from a helix. @@ -28,8 +30,12 @@ def simulate_helix( By defauult None, which will add no noise. The nature noise func is just an independent noise term added to ``P`` before it is passed to the generation of the X, Y, and Z terms. + alpha : float, optional + The value of the noise, by default 0.005. n_samples : int, optional Number of samples to generate, by default 1000. + return_mi_lb : bool, optional + Whether to return the mutual information lower bound, by default False. random_seed : int, optional The random seed. @@ -43,6 +49,8 @@ def simulate_helix( The X dimension. Z : array-like of shape (n_samples,) The X dimension. + lb : float + The mutual information lower bound. Notes ----- @@ -80,25 +88,31 @@ def simulate_helix( Z_arr = np.zeros((n_samples,)) if obs_noise_func is None: - obs_noise_func = lambda: rng.uniform(-0.005, 0.005) + obs_noise_func = lambda: rng.uniform(-alpha, alpha) if nature_noise_func is None: nature_noise_func = lambda: 0.0 for idx in range(n_samples): Radii[idx] = rng.uniform(radius_a, radius_b) P_arr[idx] = 5 * np.pi + 3 * np.pi * Radii[idx] - X_arr[idx] = (P_arr[idx] + nature_noise_func) * np.cos(P_arr[idx] + nature_noise_func) / ( - 8 * np.pi - ) + obs_noise_func() - Y_arr[idx] = (P_arr[idx] + nature_noise_func) * np.sin(P_arr[idx] + nature_noise_func) / ( - 8 * np.pi - ) + obs_noise_func() - Z_arr[idx] = (P_arr[idx] + nature_noise_func) / (8 * np.pi) + obs_noise_func() + X_arr[idx] = (P_arr[idx] + nature_noise_func()) * np.cos( + P_arr[idx] + nature_noise_func() + ) / (8 * np.pi) + obs_noise_func() + Y_arr[idx] = (P_arr[idx] + nature_noise_func()) * np.sin( + P_arr[idx] + nature_noise_func() + ) / (8 * np.pi) + obs_noise_func() + Z_arr[idx] = (P_arr[idx] + nature_noise_func()) / (8 * np.pi) + obs_noise_func() + + if return_mi_lb: + lb = alpha / 2 - np.log(alpha) + return P_arr, X_arr, Y_arr, Z_arr, lb return P_arr, X_arr, Y_arr, Z_arr -def simulate_sphere(radius=1, noise_func=None, n_samples=1000, random_seed=None): +def simulate_sphere( + radius=1, noise_func=None, alpha=0.005, n_samples=1000, return_mi_lb=False, random_seed=None +): """Simulate samples generated on a sphere. Parameters @@ -107,9 +121,13 @@ def simulate_sphere(radius=1, noise_func=None, n_samples=1000, random_seed=None) The radius of the sphere, by default 1. noise_func : callable, optional The noise function to call to add to samples, by default None, - which defaults to sampling from the uniform distribution [-0.005, 0.005]. + which defaults to sampling from the uniform distribution [-alpha, alpha]. + alpha : float, optional + The value of the noise, by default 0.005. n_samples : int, optional Number of samples to generate, by default 1000. + return_mi_lb : bool, optional + Whether to return the mutual information lower bound, by default False. random_seed : int, optional Random seed, by default None. @@ -125,10 +143,12 @@ def simulate_sphere(radius=1, noise_func=None, n_samples=1000, random_seed=None) The Y coordinate. Y3 : array-like of shape (n_samples,) The Z coordinate. + lb : float + The mutual information lower bound. """ rng = np.random.default_rng(random_seed) if noise_func is None: - noise_func = lambda: rng.uniform(-0.005, 0.005) + noise_func = lambda: rng.uniform(-alpha, alpha) latitude = np.zeros((n_samples,)) longitude = np.zeros((n_samples,)) @@ -136,14 +156,19 @@ def simulate_sphere(radius=1, noise_func=None, n_samples=1000, random_seed=None) Y2 = np.zeros((n_samples,)) Y3 = np.zeros((n_samples,)) + # sample latitude and longitude for idx in range(n_samples): # sample latitude and longitude - latitude[idx] = rng.uniform(0, 1) - longitude[idx] = rng.uniform(0, 1) + latitude[idx] = rng.uniform(0, 2 * np.pi) + longitude[idx] = np.arccos(1 - 2 * rng.uniform(0, 1)) + + Y1[idx] = np.sin(longitude[idx]) * np.cos(latitude[idx]) * radius + noise_func() + Y2[idx] = np.sin(longitude[idx]) * np.sin(latitude[idx]) * radius + noise_func() + Y3[idx] = np.cos(longitude[idx]) * radius + noise_func() - Y1[idx] = np.cos(latitude[idx]) * np.cos(longitude[idx]) * radius + noise_func() - Y2[idx] = np.cos(latitude[idx]) * np.sin(longitude[idx]) * radius + noise_func() - Y3[idx] = np.sin(longitude[idx]) * radius + noise_func() + if return_mi_lb: + lb = alpha / 2 - np.log(alpha) + return latitude, longitude, Y1, Y2, Y3, lb return latitude, longitude, Y1, Y2, Y3 @@ -184,12 +209,14 @@ def simulate_multivariate_gaussian(mean=None, cov=None, d=2, n_samples=1000, see if cov is None: # generate random covariance matrix and enure it is symmetric and positive-definite cov = rng.normal(size=(d, d)) - cov = 0.5 * (cov + cov.T) - else: - if not np.all(np.linalg.eigvals(cov) > 0): - raise RuntimeError("Passed in covariance matrix should be positive definite") - if not scipy.linalg.issymmetric(cov): - raise RuntimeError("Passed in covariance matrix should be symmetric") + cov = 0.5 * (cov.dot(cov.T)) + if not np.all(np.linalg.eigvals(cov) > 0): + raise RuntimeError("Passed in covariance matrix should be positive definite") + if not scipy.linalg.issymmetric(cov): + raise RuntimeError(f"Passed in covariance matrix {cov} should be symmetric") + + if len(mean) != d or len(cov) != d: + raise RuntimeError(f"Dimensionality of mean and covariance matrix should match {d}") data = rng.multivariate_normal(mean=mean, cov=cov, size=(n_samples)) diff --git a/sktree/experimental/tests/__init__.py b/sktree/experimental/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sktree/experimental/tests/meson.build b/sktree/experimental/tests/meson.build new file mode 100644 index 000000000..ac5c5d40f --- /dev/null +++ b/sktree/experimental/tests/meson.build @@ -0,0 +1,11 @@ +python_sources = [ + '__init__.py', + 'test_mutual_info.py', + 'test_simulate.py', +] + +py3.install_sources( + python_sources, + pure: false, + subdir: 'sktree/experimental/tests' +) \ No newline at end of file diff --git a/sktree/experimental/tests/test_mutual_info.py b/sktree/experimental/tests/test_mutual_info.py index a1c35dd30..fd6233905 100644 --- a/sktree/experimental/tests/test_mutual_info.py +++ b/sktree/experimental/tests/test_mutual_info.py @@ -11,7 +11,7 @@ def nonlinear_gaussian_with_additive_noise(): # then add the noise # compute MI by computing the H(Y|X) and H(X) - # H(Y|X) = log(noise_std) + # H(Y|X) = np.log(noise_std) # H(X) = kNN K-L estimate with large # of samples pass @@ -41,12 +41,18 @@ def test_mi(): cov = np.dot(tmat, np.dot(diag, mat)) print("covariance matrix") print(cov) - trueent = -0.5 * (3 + log(8.0 * pi * pi * pi * det(cov))) - trueent += -0.5 * (1 + log(2.0 * pi * cov[2][2])) # z sub + trueent = -0.5 * (3 + np.log(8.0 * np.pi * np.pi * np.pi * np.linalg.det(cov))) + trueent += -0.5 * (1 + np.log(2.0 * np.pi * cov[2][2])) # z sub trueent += 0.5 * ( - 2 + log(4.0 * pi * pi * det([[cov[0][0], cov[0][2]], [cov[2][0], cov[2][2]]])) + 2 + + np.log( + 4.0 * np.pi * np.pi * np.linalg.det([[cov[0][0], cov[0][2]], [cov[2][0], cov[2][2]]]) + ) ) # xz sub trueent += 0.5 * ( - 2 + log(4.0 * pi * pi * det([[cov[1][1], cov[1][2]], [cov[2][1], cov[2][2]]])) + 2 + + np.log( + 4.0 * np.pi * np.pi * np.linalg.det([[cov[1][1], cov[1][2]], [cov[2][1], cov[2][2]]]) + ) ) # yz sub - print("true CMI(x:y|x)", trueent / log(2)) + print("true CMI(x:y|x)", trueent / np.log(2)) diff --git a/sktree/experimental/tests/test_simulate.py b/sktree/experimental/tests/test_simulate.py index 15a140b69..b613bd535 100644 --- a/sktree/experimental/tests/test_simulate.py +++ b/sktree/experimental/tests/test_simulate.py @@ -1,5 +1,3 @@ -import pytest - from sktree.experimental.simulate import ( simulate_helix, simulate_multivariate_gaussian, diff --git a/sktree/tree/__init__.py b/sktree/tree/__init__.py index f2779109e..1eecca5dd 100644 --- a/sktree/tree/__init__.py +++ b/sktree/tree/__init__.py @@ -7,8 +7,10 @@ UnsupervisedObliqueDecisionTree, ) from ._honest_tree import HonestTreeClassifier +from ._neighbors import compute_forest_similarity_matrix __all__ = [ + "compute_forest_similarity_matrix", "UnsupervisedDecisionTree", "UnsupervisedObliqueDecisionTree", "ObliqueDecisionTreeClassifier", diff --git a/sktree/tree/_honest_tree.py b/sktree/tree/_honest_tree.py index c3a327ea3..8f1fdc2b0 100644 --- a/sktree/tree/_honest_tree.py +++ b/sktree/tree/_honest_tree.py @@ -408,10 +408,18 @@ def fit(self, X, y, sample_weight=None, check_input=True): return self - def _set_leaf_nodes(self, X, y): - "Traverse the already built tree with X and set leaf nodes with y" + def _set_leaf_nodes(self, leaf_ids, y): + """Traverse the already built tree with X and set leaf nodes with y. + + tree_.value has shape (n_nodes, n_outputs, max_n_classes), where + n_nodes are the number of nodes in the tree (each node is either a split, + or leaf node), n_outputs is the number of outputs (1 for classification, + n for regression), and max_n_classes is the maximum number of classes + across all outputs. For classification with n_classes classes, the + classes are ordered by their index in the tree_.value array. + """ self.tree_.value[:, :, :] = 0 - for leaf_id, yval in zip(X, y[self.honest_indices_, 0]): + for leaf_id, yval in zip(leaf_ids, y[self.honest_indices_, 0]): self.tree_.value[leaf_id][0, yval] += 1 def _inherit_estimator_attributes(self): diff --git a/sktree/tree/_marginal.pxd b/sktree/tree/_marginal.pxd new file mode 100644 index 000000000..b2f504fa9 --- /dev/null +++ b/sktree/tree/_marginal.pxd @@ -0,0 +1,19 @@ +import numpy as np + +cimport numpy as cnp + +from .._lib.sklearn.tree._tree cimport DOUBLE_t # Type of y, sample_weight +from .._lib.sklearn.tree._tree cimport DTYPE_t # Type of X +from .._lib.sklearn.tree._tree cimport INT32_t # Signed 32 bit integer +from .._lib.sklearn.tree._tree cimport SIZE_t # Type for indices and counters +from .._lib.sklearn.tree._tree cimport UINT32_t # Unsigned 32 bit integer +from .._lib.sklearn.tree._tree cimport Node, Tree + + +cpdef apply_marginal_tree( + Tree tree, + const DTYPE_t[:, :] X, + const SIZE_t[:] marginal_indices, + unsigned char weighted, + object random_state +) diff --git a/sktree/tree/_marginal.pyx b/sktree/tree/_marginal.pyx new file mode 100644 index 000000000..10885b8ce --- /dev/null +++ b/sktree/tree/_marginal.pyx @@ -0,0 +1,191 @@ +# cython: language_level=3 +# cython: boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True + +from libc.math cimport isnan +from libcpp.unordered_set cimport unordered_set + +from sktree._lib.sklearn.tree._utils cimport RAND_R_MAX + +from ._utils cimport rand_weighted_binary + +from numpy import float32 as DTYPE + +TREE_LEAF = -1 +TREE_UNDEFINED = -2 +cdef SIZE_t _TREE_LEAF = TREE_LEAF +cdef SIZE_t _TREE_UNDEFINED = TREE_UNDEFINED + + +cpdef apply_marginal_tree( + Tree tree, + const DTYPE_t[:, :] X, + const SIZE_t[:] marginal_indices, + unsigned char weighted, + object random_state +): + """Apply a dataset to a marginalized tree. + + Parameters + ---------- + tree : Tree + The tree to apply. + X : ndarray of shape (n_samples, n_features) + The dataset to apply. + marginal_indices : ndarray of shape (n_marginals,) + The indices of the features to marginalize, which + are columns in ``X``. + weighted : unsigned char + Whether or not to use the weighted number of samples + in each node. + random_state : object + The random number state. + + Returns + ------- + out : ndarray of shape (n_samples, ) + The indices of the leaf that each sample falls into. + """ + cdef SIZE_t n_marginals = marginal_indices.shape[0] + + # sklearn_rand_r random number state + cdef UINT32_t rand_r_state = random_state.randint(0, RAND_R_MAX) + + # define a set of all marginal indices + cdef unordered_set[SIZE_t] marginal_indices_map + + # check all marginal indices are valid, and also convert to an unordered map + for i in range(n_marginals): + if marginal_indices[i] >= X.shape[1]: + raise ValueError( + "marginal_indices must be less than X.shape[1]" + ) + + marginal_indices_map.insert(marginal_indices[i]) + + # now we will apply the dataset to the tree + out = _apply_dense_marginal( + tree, + X, + marginal_indices_map, + weighted, + &rand_r_state + ) + return out + + +cdef void _resample_split_node( + Tree tree, + Node* node, + unordered_set[SIZE_t] marginal_indices_map, + const DTYPE_t[:, :] X, + const DOUBLE_t[:, ::1] y, + const DOUBLE_t[:] sample_weight, +) noexcept nogil: + pass + + +cdef inline cnp.ndarray _apply_dense_marginal( + Tree tree, + const DTYPE_t[:, :] X, + unordered_set[SIZE_t] marginal_indices_map, + unsigned char weighted, + UINT32_t* rand_r_state +): + """Finds the terminal region (=leaf node) for each sample in X. + + Applies dense dataset to the tree and returns the indices of + the leaf that each sample falls into. This marginalizes out + features that are not in the marginal indices. + + Parameters + ---------- + tree : Tree + The tree to apply. + X : const ndarray of shape (n_samples, n_features) + The data matrix. + weighted : unsigned char + Whether or not to use the weighted number of samples + in each node. + rand_r_state : UINT32_t + The random number state. + """ + + # Check input + if not isinstance(X, np.ndarray): + raise ValueError("X should be in np.ndarray format, got %s" % type(X)) + + if X.dtype != DTYPE: + raise ValueError("X.dtype should be np.float32, got %s" % X.dtype) + + # Extract input + cdef const DTYPE_t[:, :] X_ndarray = X + cdef SIZE_t n_samples = X.shape[0] + cdef DTYPE_t X_i_node_feature + + cdef DTYPE_t n_node_samples, n_right_samples, n_left_samples + cdef double p_left + cdef int is_left + + # Initialize output + cdef SIZE_t[:] out = np.zeros(n_samples, dtype=np.intp) + + # Initialize auxiliary data-structure + cdef Node* node = NULL + cdef SIZE_t i = 0 + + with nogil: + for i in range(n_samples): + node = tree.nodes + + # While node not a leaf + while node.left_child != _TREE_LEAF: + # XXX: this will only work for axis-aligned features + if is_element_present(marginal_indices_map, node.feature): + # if the feature is in the marginal indices, then we + # will flip a weighted coin to go down the left, or + # right child + if weighted: + n_node_samples = node.weighted_n_node_samples + n_left_samples = tree.nodes[node.left_child].weighted_n_node_samples + n_right_samples = tree.nodes[node.right_child].weighted_n_node_samples + else: + n_node_samples = node.n_node_samples + n_left_samples = tree.nodes[node.left_child].n_node_samples + n_right_samples = tree.nodes[node.right_child].n_node_samples + + # compute the probabilies for going left and right + p_left = (n_left_samples / n_node_samples) + + # randomly sample a direction + is_left = rand_weighted_binary(p_left, rand_r_state) + + if is_left: + node = &tree.nodes[node.left_child] + else: + node = &tree.nodes[node.right_child] + else: + X_i_node_feature = tree._compute_feature(X_ndarray, i, node) + # ... and node.right_child != _TREE_LEAF: + if isnan(X_i_node_feature): + if node.missing_go_to_left: + node = &tree.nodes[node.left_child] + else: + node = &tree.nodes[node.right_child] + elif X_i_node_feature <= node.threshold: + node = &tree.nodes[node.left_child] + else: + node = &tree.nodes[node.right_child] + + out[i] = (node - tree.nodes) # node offset + + return np.asarray(out) + + +cdef inline int is_element_present(unordered_set[SIZE_t]& my_set, SIZE_t element) noexcept nogil: + """Helper function to check presence of element in set.""" + cdef unordered_set[SIZE_t].iterator it = my_set.find(element) + + if it != my_set.end(): + return 1 + else: + return 0 diff --git a/sktree/tree/_marginalize.py b/sktree/tree/_marginalize.py new file mode 100644 index 000000000..a6291dfc3 --- /dev/null +++ b/sktree/tree/_marginalize.py @@ -0,0 +1,189 @@ +"""A set of mixin methods for marginalizing a random forest.""" +import numpy as np +from sklearn.ensemble._forest import BaseForest +from sklearn.utils.parallel import Parallel, delayed +from sklearn.utils.validation import check_is_fitted + +from sktree._lib.sklearn.tree import BaseDecisionTree +from sktree._lib.sklearn.tree._tree import DTYPE + +from ._marginal import apply_marginal_tree + + +def apply_marginal(est, X, S, weighted: bool = False, check_input=True): + """Apply a forest to X, while marginalizing over features. + + Parameters + ---------- + est : BaseForest or BaseDecisionTree + The tree/forest based estimator that was already fit on (X, y) data. + X : array-like of shape (n_samples, n_features) + Data that should match the data used to fit the forest. + S : array-like of shape (n_features), optional + An index vector of 1's and 0's indicating which features to + marginalize over. 1's indicate features to keep, 0's indicate + features to marginalize over. + weighted : bool, optional + Whether to weight the samples that are sent to the left and + right child nodes, by default False. + check_input : bool, optional + Whether to check the input data, by default True. + + Returns + ------- + X_leaves : array-like of shape (n_samples, n_estimators) + For each datapoint x in X and for each tree in the forest, return the + index of the leaf x ends up in. If it is a tree ``n_estimators=1``. + """ + check_is_fitted(est) + X = est._validate_X_predict(X, check_input) + + # check if this is a forest, or tree + if hasattr(est, "estimators_"): + _apply_marginal_func = _apply_marginal_forest + else: + _apply_marginal_func = _apply_marginal_tree + + X_leaves = _apply_marginal_func(est, X, S, weighted=weighted) + return X_leaves + + +def _apply_marginal_forest(est, X, S, weighted: bool = False): + """Apply forest to marginalized set of features.""" + check_is_fitted(est) + X = est._validate_X_predict(X) + + # if we trained a binning tree, then we should re-bin the data + # XXX: this is inefficient and should be improved to be in line with what + # the Histogram Gradient Boosting Tree does, where the binning thresholds + # are passed into the tree itself, thus allowing us to set the node feature + # value thresholds within the tree itself. + if est.max_bins is not None: + X = est._bin_data(X, is_training_data=False).astype(DTYPE) + + results = Parallel( + n_jobs=est.n_jobs, + verbose=est.verbose, + prefer="threads", + )(delayed(_apply_marginal_tree)(tree, X, S, weighted) for tree in est.estimators_) + return np.array(results).T + + +def _apply_marginal_tree(est: BaseDecisionTree, X, S, weighted: bool = False): + """Apply a tree to X, while marginalizing over features. + + Parameters + ---------- + X : array-like of shape (n_samples, n_features) + Data that should match the data used to fit the forest. + S : array-like of shape (n_marginal_features) + The feature indices to marginalize over (i.e. columns of X). + weighted : bool, optional + Whether to weight the samples that are sent to the left and + right child nodes, by default False. + + Returns + ------- + X_leaves : array-like of shape (n_samples,) + Index of the leaf that each sample in X ends up in after marginalizing + certain features. + """ + check_is_fitted(est) + X = est._validate_X_predict(X) + + X_leaves = apply_marginal_tree(est.tree_, X, S, weighted, random_state=est.random_state) + return X_leaves + + # n_repeats : int, optional + # Number of times to repeat the marginalization, by default 10. + # Each time, a feature that is encountered in the forest that + # is specified by S to be marginalized over, a random 50\% + # of samples are sent to the left child and the other 50\% + # are sent to the right child. Since this process is random + # and can affect the leaf nodes assignment, we repeat this + # and take the average of the leaf nodes assignment. + + +def compute_marginal(self: BaseForest, X, S, n_repeats=10): + """Compute marginal distribution of P(S = s) for each s in X. + + Parameters + ---------- + X : array-like of shape (n_samples, n_features) + Data that should match the data used to fit the forest. + S : array-like of shape (n_features), optional + An index vector of 1's and 0's indicating which features to + marginalize over. 1's indicate features we want to compute on, + 0's indicate features to marginalize over. + n_repeats : int, optional + Number of times to repeat the marginalization, by default 10. + Each time, a feature that is encountered in the forest that + is specified by S to be marginalized over, a random 50\% + of samples are sent to the left child and the other 50\% + are sent to the right child. Since this process is random + and can affect the leaf nodes assignment, we repeat this + and take the average of the leaf nodes assignment. + """ + # get the leaf nodes for each sample in X + # X_leaves = self.apply_marginal(X, S, n_repeats=n_repeats) + + # compute the marginal distribution of P(S = s) for each s in X + self.apply(X) + + # + + +def compute_conditional(self, X, S, y=None, n_repeats=10): + """Compute conditional P(Y | X, Z = z) for each X and Z. + + Parameters + ---------- + X : array-like of shape (n_samples, n_features_x) + Data that should match the data used to fit the forest. + S : array-like of shape (n_features), optional + An index vector of 1's and 0's indicating which features we + want to fix values for. + y : array-like of shape (n_samples, n_outputs), optional + Y data that used to fit the forest, by default None. + n_repeats : int, optional + Number of times to repeat the marginalization, by default 10. + Each time, a feature that is encountered in the forest that + is specified by S to be marginalized over, a random 50\% + of samples are sent to the left child and the other 50\% + are sent to the right child. Since this process is random + and can affect the leaf nodes assignment, we repeat this + and take the average of the leaf nodes assignment. + + Returns + ------- + proba_y_xz : array-like of shape (n_samples, n_outputs) + For each datapoint x in X and for each tree in the forest, return the + probability of y given x and specific instance of z. + + Questions for Mike: + 1. What is |l|? - is this the size of the leaf node? What does that mean? The number + of samples in l? + """ + # for every tree, and every leaf compute interval Z of data that reaches + # that leaf = I_{b,l, Z=z} + for tree in self.estimators_: + # tree node value (n_leaves, n_outputs, max_classes) + tree_leaves = tree.tree_.value + + for leaf in range(tree.tree_.n_leaves): + # get the size of the leaf + + # compute interval that reaches this leaf + + # now compute P(Y | x \in l) for every leaf + proba_y_x = tree_leaves[leaf, :, :] + + # get sample indices that reach this leaf in Z=z + sample_indices = [] + + # now estimate P(Y | Z=z) for each z in the Z sequence + for sample_idx in sample_indices: + # + pass + pass + pass diff --git a/sktree/tree/_neighbors.py b/sktree/tree/_neighbors.py index 5a2046a5e..eccd27e05 100644 --- a/sktree/tree/_neighbors.py +++ b/sktree/tree/_neighbors.py @@ -1,4 +1,8 @@ +import numbers + import numpy as np +from sklearn.neighbors import NearestNeighbors +from sklearn.utils.validation import check_is_fitted from sktree._lib.sklearn.ensemble._forest import BaseForest @@ -58,3 +62,138 @@ def compute_similarity_matrix(self, X): The similarity matrix among the samples. """ return compute_forest_similarity_matrix(self, X) + + def kneighbors(self, X=None, n_neighbors=None, return_distance=True, metric="l2"): + """Find the K-neighbors of a point. + + Returns indices of and distances to the neighbors of each point. + + Parameters + ---------- + X : {array-like, sparse matrix}, shape (n_queries, n_features), \ + or (n_queries, n_indexed) if metric == 'precomputed', default=None + Not used, present for API consistency by convention. + + n_neighbors : int, default=None + Number of neighbors required for each sample. The default is the + value passed to the constructor. + + return_distance : bool, default=True + Whether or not to return the distances. + + Returns + ------- + neigh_dist : ndarray of shape (n_queries, n_neighbors) + Array representing the lengths to points, only present if + return_distance=True. + + neigh_ind : ndarray of shape (n_queries, n_neighbors) + Indices of the nearest points in the population matrix. + """ + check_is_fitted(self) + + if n_neighbors is None: + n_neighbors = self.n_neighbors + elif n_neighbors <= 0: + raise ValueError("Expected n_neighbors > 0. Got %d" % n_neighbors) + elif not isinstance(n_neighbors, numbers.Integral): + raise TypeError( + "n_neighbors does not take %s value, enter integer value" % type(n_neighbors) + ) + + if X is not None: + raise RuntimeError("X cannot be passed in.") + + if not hasattr(self, "neigh_est_"): + # compute the nearest neighbors in the space using specified NN algorithm + # then get the K nearest nbrs and their distances + neigh_est = NearestNeighbors( + n_neighbors=n_neighbors, algorithm="precomputed", metric=metric, n_jobs=self.n_jobs + ) + + self.neigh_est_ = neigh_est + + if n_neighbors != self.neigh_est_.n_neighbors: + raise RuntimeError(f"n_neighbors must be the same as the one used in the fit method.") + if metric != self.neigh_est_.metric: + raise RuntimeError(f"metric must be the same as the one used in the fit method.") + + # get the fitted forest distance matrix + dists = compute_forest_similarity_matrix(self, X) + + # now fit the nearest neighbors algorithm to the forest-distance matrix + neigh_est.fit(dists) + return neigh_est.kneighbors(n_neighbors=n_neighbors, return_distance=return_distance) + + def radius_neighbors(self, X=None, radius=None, return_distance=True, sort_results=False): + """Find the neighbors within a given radius of a point or points. + + Return the indices and distances of each point from the dataset + lying in a ball with size ``radius`` around the points of the query + array. Points lying on the boundary are included in the results. + + The result points are *not* necessarily sorted by distance to their + query point. + + Parameters + ---------- + X : {array-like, sparse matrix} of (n_samples, n_features), default=None + The query point or points. + If not provided, neighbors of each indexed point are returned. + In this case, the query point is not considered its own neighbor. + + radius : float, default=None + Limiting distance of neighbors to return. The default is the value + passed to the constructor. + + return_distance : bool, default=True + Whether or not to return the distances. + + sort_results : bool, default=False + If True, the distances and indices will be sorted by increasing + distances before being returned. If False, the results may not + be sorted. If `return_distance=False`, setting `sort_results=True` + will result in an error. + + .. versionadded:: 0.22 + + Returns + ------- + neigh_dist : ndarray of shape (n_samples,) of arrays + Array representing the distances to each point, only present if + `return_distance=True`. The distance values are computed according + to the ``metric`` constructor parameter. + + neigh_ind : ndarray of shape (n_samples,) of arrays + An array of arrays of indices of the approximate nearest points + from the population matrix that lie within a ball of size + ``radius`` around the query points. + + Notes + ----- + Because the number of neighbors of each point is not necessarily + equal, the results for multiple query points cannot be fit in a + standard data array. + For efficiency, `radius_neighbors` returns arrays of objects, where + each object is a 1D array of indices or distances. + """ + dists, _ = self.neigh_est_.kneighbors() + radius_per_sample = dists[:, -1] + n_samples = radius_per_sample.shape[0] + + nn_ind_data = np.zeros((n_samples,)) + nn_dist_data = np.zeros((n_samples,)) + for idx in range(n_samples): + nn = self.neigh_est_.radius_neighbors( + X=X, radius=radius, return_distance=return_distance, sort_results=sort_results + ) + + if return_distance: + nn_ind_data[idx] = nn[0][idx] + nn_dist_data[idx] = nn[1][idx] + else: + nn_ind_data[idx] = nn + + if return_distance: + return nn_dist_data, nn_ind_data + return nn_ind_data diff --git a/sktree/tree/_utils.pxd b/sktree/tree/_utils.pxd index 5abb73a02..a004bc12a 100644 --- a/sktree/tree/_utils.pxd +++ b/sktree/tree/_utils.pxd @@ -12,6 +12,8 @@ from sktree._lib.sklearn.tree._tree cimport SIZE_t # Type for indices and count from sktree._lib.sklearn.tree._tree cimport UINT32_t # Unsigned 32 bit integer +cdef int rand_weighted_binary(double p0, UINT32_t* random_state) noexcept nogil + cpdef unravel_index( SIZE_t index, cnp.ndarray[SIZE_t, ndim=1] shape ) diff --git a/sktree/tree/_utils.pyx b/sktree/tree/_utils.pyx index e13ba86ce..62e80a6a3 100644 --- a/sktree/tree/_utils.pyx +++ b/sktree/tree/_utils.pyx @@ -11,6 +11,25 @@ cimport numpy as cnp cnp.import_array() +from sktree._lib.sklearn.tree._utils cimport rand_uniform + + +cdef inline int rand_weighted_binary(double p0, UINT32_t* random_state) noexcept nogil: + """Sample from integers 0 and 1 with different probabilities. + + Parameters + ---------- + p0 : double + The probability of sampling 0. + random_state : UINT32_t* + The random state. + """ + cdef double random_value = rand_uniform(0.0, 1.0, random_state) + + if random_value < p0: + return 0 + else: + return 1 cpdef unravel_index( SIZE_t index, diff --git a/sktree/tree/manifold/_morf_splitter.pxd b/sktree/tree/manifold/_morf_splitter.pxd index 06b469404..9574dbbea 100644 --- a/sktree/tree/manifold/_morf_splitter.pxd +++ b/sktree/tree/manifold/_morf_splitter.pxd @@ -12,7 +12,6 @@ import numpy as np cimport numpy as cnp from libcpp.vector cimport vector -from sklearn.utils._sorting cimport simultaneous_sort from ..._lib.sklearn.tree._splitter cimport SplitRecord from ..._lib.sklearn.tree._tree cimport DOUBLE_t # Type of y, sample_weight diff --git a/sktree/tree/meson.build b/sktree/tree/meson.build index 4aa0b0957..d94ad1f6c 100644 --- a/sktree/tree/meson.build +++ b/sktree/tree/meson.build @@ -3,6 +3,7 @@ extensions = [ '_oblique_splitter', '_oblique_tree', '_utils', + '_marginal', ] foreach ext: extensions @@ -20,7 +21,8 @@ python_sources = [ '__init__.py', '_classes.py', '_neighbors.py', - '_honest_tree.py' + '_honest_tree.py', + '_marginalize.py', ] py3.install_sources( diff --git a/sktree/tree/tests/meson.build b/sktree/tree/tests/meson.build index 6043999df..9807b5e42 100644 --- a/sktree/tree/tests/meson.build +++ b/sktree/tree/tests/meson.build @@ -2,7 +2,8 @@ python_sources = [ '__init__.py', 'test_tree.py', 'test_utils.py', - 'test_honest_tree.py' + 'test_honest_tree.py', + 'test_marginal.py' ] py3.install_sources( diff --git a/sktree/tree/tests/test_marginal.py b/sktree/tree/tests/test_marginal.py new file mode 100644 index 000000000..9ff937d80 --- /dev/null +++ b/sktree/tree/tests/test_marginal.py @@ -0,0 +1,60 @@ +import numpy as np +import pytest + +from sktree._lib.sklearn.ensemble._forest import RandomForestClassifier +from sktree._lib.sklearn.tree import ( + DecisionTreeClassifier, + DecisionTreeRegressor, + ExtraTreeClassifier, +) +from sktree.tree._marginalize import apply_marginal + +X_small = np.array( + [ + [0, 0, 4, 0, 0, 0, 1, -14, 0, -4, 0, 0, 0, 0], + [0, 0, 5, 3, 0, -4, 0, 0, 1, -5, 0.2, 0, 4, 1], + [-1, -1, 0, 0, -4.5, 0, 0, 2.1, 1, 0, 0, -4.5, 0, 1], + [-1, -1, 0, -1.2, 0, 0, 0, 0, 0, 0, 0.2, 0, 0, 1], + [-1, -1, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 1], + [-1, -2, 0, 4, -3, 10, 4, 0, -3.2, 0, 4, 3, -4, 1], + [2.11, 0, -6, -0.5, 0, 11, 0, 0, -3.2, 6, 0.5, 0, -3, 1], + [2.11, 0, -6, -0.5, 0, 11, 0, 0, -3.2, 6, 0, 0, -2, 1], + [2.11, 8, -6, -0.5, 0, 11, 0, 0, -3.2, 6, 0, 0, -2, 1], + [2.11, 8, -6, -0.5, 0, 11, 0, 0, -3.2, 6, 0.5, 0, -1, 0], + [2, 8, 5, 1, 0.5, -4, 10, 0, 1, -5, 3, 0, 2, 0], + [2, 0, 1, 1, 1, -1, 1, 0, 0, -2, 3, 0, 1, 0], + [2, 0, 1, 2, 3, -1, 10, 2, 0, -1, 1, 2, 2, 0], + [1, 1, 0, 2, 2, -1, 1, 2, 0, -5, 1, 2, 3, 0], + [3, 1, 0, 3, 0, -4, 10, 0, 1, -5, 3, 0, 3, 1], + [2.11, 8, -6, -0.5, 0, 1, 0, 0, -3.2, 6, 0.5, 0, -3, 1], + [2.11, 8, -6, -0.5, 0, 1, 0, 0, -3.2, 6, 1.5, 1, -1, -1], + [2.11, 8, -6, -0.5, 0, 10, 0, 0, -3.2, 6, 0.5, 0, -1, -1], + [2, 0, 5, 1, 0.5, -2, 10, 0, 1, -5, 3, 1, 0, -1], + [2, 0, 1, 1, 1, -2, 1, 0, 0, -2, 0, 0, 0, 1], + [2, 1, 1, 1, 2, -1, 10, 2, 0, -1, 0, 2, 1, 1], + [1, 1, 0, 0, 1, -3, 1, 2, 0, -5, 1, 2, 1, 1], + [3, 1, 0, 1, 0, -4, 1, 0, 1, -2, 0, 0, 1, 0], + ] +) + +y_small = [1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0] + + +def test_apply_marginal(): + # Create sample data + X = np.array([[1, 2], [3, 4], [5, 6]]) + S = np.array([1, 0]) # Example marginalization indices + + # Test with RandomForestClassifier (forest input) + est_forest = RandomForestClassifier() + est_forest.fit(X, [0, 1, 0]) # Example fitting + X_leaves_forest = apply_marginal(est_forest, X, S) + assert X_leaves_forest.shape == (3, est_forest.n_estimators) # Check the shape of the output + # Add more assertions based on your specific requirements for forest input + + # Test with ExtraTreeClassifier (tree input) + est_tree = ExtraTreeClassifier() + est_tree.fit(X, [0, 1, 0]) # Example fitting + X_leaves_tree = apply_marginal(est_tree, X, S) + assert X_leaves_tree.shape == (3,) # Check the shape of the output + # Add more assertions based on your specific requirements for tree input From 6ed89c5a1c5826d013880461b364d5b929023770 Mon Sep 17 00:00:00 2001 From: Adam Li Date: Thu, 15 Jun 2023 13:51:59 -0400 Subject: [PATCH 04/17] Update to most recent changes in sklearn fork Signed-off-by: Adam Li --- sktree/_lib/sklearn_fork | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sktree/_lib/sklearn_fork b/sktree/_lib/sklearn_fork index 6b4d0e7f3..545e2a298 160000 --- a/sktree/_lib/sklearn_fork +++ b/sktree/_lib/sklearn_fork @@ -1 +1 @@ -Subproject commit 6b4d0e7f37dbd0af2bb8404b921db53cbb2cc78f +Subproject commit 545e2a298ab403262e00a16f4d85ccde1c2a250b From 30bf7e755066d132b289f58a68f008831931a83f Mon Sep 17 00:00:00 2001 From: Adam Li Date: Thu, 15 Jun 2023 18:29:09 -0400 Subject: [PATCH 05/17] Tighten up the marginalization implementation Signed-off-by: Adam Li --- sktree/ensemble/_supervised_forest.py | 4 + sktree/ensemble/_unsupervised_forest.py | 2 + sktree/experimental/tests/test_mutual_info.py | 4 +- sktree/tree/_classes.py | 8 ++ sktree/tree/_marginal.pxd | 9 +- sktree/tree/_marginal.pyx | 99 +++++++++------ sktree/tree/_marginalize.py | 112 +++++++++++++---- sktree/tree/_neighbors.py | 9 +- sktree/tree/tests/test_marginal.py | 116 +++++++++++++++--- 9 files changed, 275 insertions(+), 88 deletions(-) diff --git a/sktree/ensemble/_supervised_forest.py b/sktree/ensemble/_supervised_forest.py index b1bf3d270..b7f02bec1 100644 --- a/sktree/ensemble/_supervised_forest.py +++ b/sktree/ensemble/_supervised_forest.py @@ -271,6 +271,7 @@ class labels (multi-output problem). [1] """ + tree_type = "oblique" _parameter_constraints: dict = { **ForestClassifier._parameter_constraints, **ObliqueDecisionTreeClassifier._parameter_constraints, @@ -578,6 +579,7 @@ class ObliqueRandomForestRegressor(SimMatrixMixin, ForestRegressor): [-5.86327109] """ + tree_type = "oblique" _parameter_constraints: dict = { **ForestRegressor._parameter_constraints, **ObliqueDecisionTreeRegressor._parameter_constraints, @@ -899,6 +901,7 @@ class labels (multi-output problem). .. footbibliography:: """ + tree_type = "oblique" _parameter_constraints: dict = { **ForestClassifier._parameter_constraints, **PatchObliqueDecisionTreeClassifier._parameter_constraints, @@ -1223,6 +1226,7 @@ class PatchObliqueRandomForestRegressor(SimMatrixMixin, ForestRegressor): [-5.82818509] """ + tree_type = "oblique" _parameter_constraints: dict = { **ForestRegressor._parameter_constraints, **PatchObliqueDecisionTreeRegressor._parameter_constraints, diff --git a/sktree/ensemble/_unsupervised_forest.py b/sktree/ensemble/_unsupervised_forest.py index ad68b1286..d07d174d7 100644 --- a/sktree/ensemble/_unsupervised_forest.py +++ b/sktree/ensemble/_unsupervised_forest.py @@ -778,6 +778,8 @@ class UnsupervisedObliqueRandomForest(ForestCluster): only when ``oob_score`` is True. """ + tree_type = "oblique" + def __init__( self, n_estimators=100, diff --git a/sktree/experimental/tests/test_mutual_info.py b/sktree/experimental/tests/test_mutual_info.py index fd6233905..cfae72df8 100644 --- a/sktree/experimental/tests/test_mutual_info.py +++ b/sktree/experimental/tests/test_mutual_info.py @@ -23,7 +23,7 @@ def main(): mat = [d1, d2, d3] tmat = np.transpose(mat) diag = [[3, 0, 0], [0, 1, 0], [0, 0, 1]] - mean = np.array([0, 0, 0]) + # mean = np.array([0, 0, 0]) cov = np.dot(tmat, np.dot(diag, mat)) print("covariance matrix") print(cov) @@ -37,7 +37,7 @@ def test_mi(): mat = [d1, d2, d3] tmat = np.transpose(mat) diag = [[3, 0, 0], [0, 1, 0], [0, 0, 1]] - mean = np.array([0, 0, 0]) + # mean = np.array([0, 0, 0]) cov = np.dot(tmat, np.dot(diag, mat)) print("covariance matrix") print(cov) diff --git a/sktree/tree/_classes.py b/sktree/tree/_classes.py index e6bd8bab3..0849c28e3 100644 --- a/sktree/tree/_classes.py +++ b/sktree/tree/_classes.py @@ -455,6 +455,8 @@ class UnsupervisedObliqueDecisionTree(UnsupervisedDecisionTree): Clustering function class keyword arguments. Passed to `clustering_func`. """ + tree_type = "oblique" + def __init__( self, *, @@ -774,6 +776,8 @@ class ObliqueDecisionTreeClassifier(SimMatrixMixin, DecisionTreeClassifier): 0.93..., 0.93..., 1. , 0.93..., 1. ]) """ + tree_type = "oblique" + _parameter_constraints = { **DecisionTreeClassifier._parameter_constraints, "feature_combinations": [ @@ -1132,6 +1136,8 @@ class ObliqueDecisionTreeRegressor(SimMatrixMixin, DecisionTreeRegressor): 0.32235221, 0.06945264, -1.1465216 , 0.34597007, -0.15308512]) """ + tree_type = "oblique" + _parameter_constraints = { **DecisionTreeRegressor._parameter_constraints, "feature_combinations": [ @@ -1509,6 +1515,7 @@ class PatchObliqueDecisionTreeClassifier(SimMatrixMixin, DecisionTreeClassifier) .. footbibliography:: """ + tree_type = "oblique" _parameter_constraints = { **DecisionTreeClassifier._parameter_constraints, "min_patch_dims": ["array-like", None], @@ -1987,6 +1994,7 @@ class PatchObliqueDecisionTreeRegressor(SimMatrixMixin, DecisionTreeRegressor): 0.41881754, 0.0588273 , -1.48722913, -0.07927208, -0.15600762]) """ + tree_type = "oblique" _parameter_constraints = { **DecisionTreeRegressor._parameter_constraints, "min_patch_dims": ["array-like", None], diff --git a/sktree/tree/_marginal.pxd b/sktree/tree/_marginal.pxd index b2f504fa9..1b65d60b0 100644 --- a/sktree/tree/_marginal.pxd +++ b/sktree/tree/_marginal.pxd @@ -7,13 +7,14 @@ from .._lib.sklearn.tree._tree cimport DTYPE_t # Type of X from .._lib.sklearn.tree._tree cimport INT32_t # Signed 32 bit integer from .._lib.sklearn.tree._tree cimport SIZE_t # Type for indices and counters from .._lib.sklearn.tree._tree cimport UINT32_t # Unsigned 32 bit integer -from .._lib.sklearn.tree._tree cimport Node, Tree +from .._lib.sklearn.tree._tree cimport BaseTree, Node cpdef apply_marginal_tree( - Tree tree, - const DTYPE_t[:, :] X, + BaseTree tree, + object X, const SIZE_t[:] marginal_indices, - unsigned char weighted, + int traversal_method, + unsigned char use_sample_weight, object random_state ) diff --git a/sktree/tree/_marginal.pyx b/sktree/tree/_marginal.pyx index 10885b8ce..f6aa6651e 100644 --- a/sktree/tree/_marginal.pyx +++ b/sktree/tree/_marginal.pyx @@ -1,10 +1,15 @@ # cython: language_level=3 # cython: boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True +import numpy as np + +cimport numpy as cnp + +cnp.import_array() from libc.math cimport isnan from libcpp.unordered_set cimport unordered_set -from sktree._lib.sklearn.tree._utils cimport RAND_R_MAX +from sktree._lib.sklearn.tree._utils cimport RAND_R_MAX, rand_uniform from ._utils cimport rand_weighted_binary @@ -17,10 +22,11 @@ cdef SIZE_t _TREE_UNDEFINED = TREE_UNDEFINED cpdef apply_marginal_tree( - Tree tree, - const DTYPE_t[:, :] X, + BaseTree tree, + object X, const SIZE_t[:] marginal_indices, - unsigned char weighted, + int traversal_method, + unsigned char use_sample_weight, object random_state ): """Apply a dataset to a marginalized tree. @@ -34,7 +40,10 @@ cpdef apply_marginal_tree( marginal_indices : ndarray of shape (n_marginals,) The indices of the features to marginalize, which are columns in ``X``. - weighted : unsigned char + traversal_method : int + The traversal method to use. 0 for 'random', 1 for + 'weighted'. + use_sample_weight : unsigned char Whether or not to use the weighted number of samples in each node. random_state : object @@ -42,9 +51,16 @@ cpdef apply_marginal_tree( Returns ------- - out : ndarray of shape (n_samples, ) + out : ndarray of shape (n_samples,) The indices of the leaf that each sample falls into. """ + # Check input + if not isinstance(X, np.ndarray): + raise ValueError("X should be in np.ndarray format, got %s" % type(X)) + + if X.dtype != DTYPE: + raise ValueError("X.dtype should be np.float32, got %s" % X.dtype) + cdef SIZE_t n_marginals = marginal_indices.shape[0] # sklearn_rand_r random number state @@ -67,14 +83,15 @@ cpdef apply_marginal_tree( tree, X, marginal_indices_map, - weighted, + traversal_method, + use_sample_weight, &rand_r_state ) return out cdef void _resample_split_node( - Tree tree, + BaseTree tree, Node* node, unordered_set[SIZE_t] marginal_indices_map, const DTYPE_t[:, :] X, @@ -85,10 +102,11 @@ cdef void _resample_split_node( cdef inline cnp.ndarray _apply_dense_marginal( - Tree tree, + BaseTree tree, const DTYPE_t[:, :] X, unordered_set[SIZE_t] marginal_indices_map, - unsigned char weighted, + int traversal_method, + unsigned char use_sample_weight, UINT32_t* rand_r_state ): """Finds the terminal region (=leaf node) for each sample in X. @@ -103,20 +121,18 @@ cdef inline cnp.ndarray _apply_dense_marginal( The tree to apply. X : const ndarray of shape (n_samples, n_features) The data matrix. - weighted : unsigned char + marginal_indices_map : unordered_set[SIZE_t] + The indices of the features to marginalize, which + are columns in ``X``. + traversal_method : int + The traversal method to use. 0 for 'random', 1 for + 'weighted'. + use_sample_weight : unsigned char Whether or not to use the weighted number of samples in each node. rand_r_state : UINT32_t The random number state. """ - - # Check input - if not isinstance(X, np.ndarray): - raise ValueError("X should be in np.ndarray format, got %s" % type(X)) - - if X.dtype != DTYPE: - raise ValueError("X.dtype should be np.float32, got %s" % X.dtype) - # Extract input cdef const DTYPE_t[:, :] X_ndarray = X cdef SIZE_t n_samples = X.shape[0] @@ -141,28 +157,37 @@ cdef inline cnp.ndarray _apply_dense_marginal( while node.left_child != _TREE_LEAF: # XXX: this will only work for axis-aligned features if is_element_present(marginal_indices_map, node.feature): - # if the feature is in the marginal indices, then we - # will flip a weighted coin to go down the left, or - # right child - if weighted: - n_node_samples = node.weighted_n_node_samples - n_left_samples = tree.nodes[node.left_child].weighted_n_node_samples - n_right_samples = tree.nodes[node.right_child].weighted_n_node_samples - else: - n_node_samples = node.n_node_samples - n_left_samples = tree.nodes[node.left_child].n_node_samples - n_right_samples = tree.nodes[node.right_child].n_node_samples + if traversal_method == 1: + # if the feature is in the marginal indices, then we + # will flip a weighted coin to go down the left, or + # right child + if use_sample_weight: + n_node_samples = node.weighted_n_node_samples + n_left_samples = tree.nodes[node.left_child].weighted_n_node_samples + n_right_samples = tree.nodes[node.right_child].weighted_n_node_samples + else: + n_node_samples = node.n_node_samples + n_left_samples = tree.nodes[node.left_child].n_node_samples + n_right_samples = tree.nodes[node.right_child].n_node_samples - # compute the probabilies for going left and right - p_left = (n_left_samples / n_node_samples) + # compute the probabilies for going left and right + p_left = (n_left_samples / n_node_samples) - # randomly sample a direction - is_left = rand_weighted_binary(p_left, rand_r_state) + # randomly sample a direction + is_left = rand_weighted_binary(p_left, rand_r_state) - if is_left: - node = &tree.nodes[node.left_child] + if is_left: + node = &tree.nodes[node.left_child] + else: + node = &tree.nodes[node.right_child] else: - node = &tree.nodes[node.right_child] + # traversal method is 0, so it is completely random + # and defined by a coin-flip + p_left = rand_uniform(0, 1, rand_r_state) + if p_left <= 0.5: + node = &tree.nodes[node.left_child] + else: + node = &tree.nodes[node.right_child] else: X_i_node_feature = tree._compute_feature(X_ndarray, i, node) # ... and node.right_child != _TREE_LEAF: diff --git a/sktree/tree/_marginalize.py b/sktree/tree/_marginalize.py index a6291dfc3..eee14f1b3 100644 --- a/sktree/tree/_marginalize.py +++ b/sktree/tree/_marginalize.py @@ -2,17 +2,28 @@ import numpy as np from sklearn.ensemble._forest import BaseForest from sklearn.utils.parallel import Parallel, delayed -from sklearn.utils.validation import check_is_fitted +from sklearn.utils.validation import check_is_fitted, check_random_state from sktree._lib.sklearn.tree import BaseDecisionTree from sktree._lib.sklearn.tree._tree import DTYPE from ._marginal import apply_marginal_tree +TRAVERSAL_METHOD_MAP = { + "random": 0, + "weighted": 1, +} -def apply_marginal(est, X, S, weighted: bool = False, check_input=True): + +def apply_marginal( + est, X, S, traversal_method="weighted", use_sample_weight: bool = False, check_input=True +): """Apply a forest to X, while marginalizing over features. + XXX: this should not work for ObliqueTrees. But we can add a traversal method + called 'resample', which applies a random conditional resampling of the data + to preserve X[S] | X[~S] conditional distribution. + Parameters ---------- est : BaseForest or BaseDecisionTree @@ -23,9 +34,19 @@ def apply_marginal(est, X, S, weighted: bool = False, check_input=True): An index vector of 1's and 0's indicating which features to marginalize over. 1's indicate features to keep, 0's indicate features to marginalize over. - weighted : bool, optional + traversal_method : str, {'random', 'weighted'} + The method to use for traversing the tree. If 'random', then + each time a feature is encountered that is specified by S to + be marginalized over, a fair coin is flipped and the sample + is sent to the left child if heads and the right child if tails. + If 'weighted', then each time a feature is encountered that is + specified by S to be marginalized over, the sample is sent to + the left child with probability equal to the fraction of samples + that went to the left child during training. By default 'weighted'. + use_sample_weight : bool, optional Whether to weight the samples that are sent to the left and - right child nodes, by default False. + right child nodes using ``weighted_node_samples``, by default False. + See :ref:`sklearn.plot_unveil_tree_structure` for more details. check_input : bool, optional Whether to check the input data, by default True. @@ -36,22 +57,45 @@ def apply_marginal(est, X, S, weighted: bool = False, check_input=True): index of the leaf x ends up in. If it is a tree ``n_estimators=1``. """ check_is_fitted(est) - X = est._validate_X_predict(X, check_input) + if hasattr(est, "tree_type") and est.tree_type == "oblique": + raise RuntimeError("This method only supports axis-aligned trees.") + + random_state = check_random_state(est.random_state) # check if this is a forest, or tree if hasattr(est, "estimators_"): _apply_marginal_func = _apply_marginal_forest else: _apply_marginal_func = _apply_marginal_tree - X_leaves = _apply_marginal_func(est, X, S, weighted=weighted) + # make sure S is an array + S = np.asarray(S).astype(np.intp) + + X_leaves = _apply_marginal_func( + est, + X, + S, + traversal_method=traversal_method, + use_sample_weight=use_sample_weight, + check_input=check_input, + random_state=random_state, + ) return X_leaves -def _apply_marginal_forest(est, X, S, weighted: bool = False): +def _apply_marginal_forest( + est, + X, + S, + traversal_method: str, + use_sample_weight: bool = False, + check_input=True, + random_state: np.random.RandomState = None, +): """Apply forest to marginalized set of features.""" check_is_fitted(est) - X = est._validate_X_predict(X) + if check_input: + X = est._validate_X_predict(X) # if we trained a binning tree, then we should re-bin the data # XXX: this is inefficient and should be improved to be in line with what @@ -61,15 +105,30 @@ def _apply_marginal_forest(est, X, S, weighted: bool = False): if est.max_bins is not None: X = est._bin_data(X, is_training_data=False).astype(DTYPE) - results = Parallel( - n_jobs=est.n_jobs, - verbose=est.verbose, - prefer="threads", - )(delayed(_apply_marginal_tree)(tree, X, S, weighted) for tree in est.estimators_) + results = Parallel(n_jobs=est.n_jobs, verbose=est.verbose, prefer="threads",)( + delayed(_apply_marginal_tree)( + tree, + X, + S, + traversal_method, + use_sample_weight, + check_input=False, + random_state=random_state, + ) + for tree in est.estimators_ + ) return np.array(results).T -def _apply_marginal_tree(est: BaseDecisionTree, X, S, weighted: bool = False): +def _apply_marginal_tree( + est: BaseDecisionTree, + X, + S, + traversal_method: str, + use_sample_weight: bool = False, + check_input=True, + random_state: np.random.RandomState = None, +): """Apply a tree to X, while marginalizing over features. Parameters @@ -78,9 +137,15 @@ def _apply_marginal_tree(est: BaseDecisionTree, X, S, weighted: bool = False): Data that should match the data used to fit the forest. S : array-like of shape (n_marginal_features) The feature indices to marginalize over (i.e. columns of X). - weighted : bool, optional + traversal_method : str, {'random', 'weighted'} + The method to use for traversing the tree. Maps to an integer + to allow easy comparison in Cython/C++. 'random' maps to 0, + 'weighted' maps to 1. + use_sample_weight : bool, optional Whether to weight the samples that are sent to the left and right child nodes, by default False. + check_input : bool, optional + Whether to check the input data, by default True. Returns ------- @@ -89,19 +154,14 @@ def _apply_marginal_tree(est: BaseDecisionTree, X, S, weighted: bool = False): certain features. """ check_is_fitted(est) - X = est._validate_X_predict(X) + X = est._validate_X_predict(X, check_input=check_input) - X_leaves = apply_marginal_tree(est.tree_, X, S, weighted, random_state=est.random_state) - return X_leaves + traversal_method = TRAVERSAL_METHOD_MAP[traversal_method] - # n_repeats : int, optional - # Number of times to repeat the marginalization, by default 10. - # Each time, a feature that is encountered in the forest that - # is specified by S to be marginalized over, a random 50\% - # of samples are sent to the left child and the other 50\% - # are sent to the right child. Since this process is random - # and can affect the leaf nodes assignment, we repeat this - # and take the average of the leaf nodes assignment. + X_leaves = apply_marginal_tree( + est.tree_, X, S, traversal_method, use_sample_weight, random_state=random_state + ) + return X_leaves def compute_marginal(self: BaseForest, X, S, n_repeats=10): diff --git a/sktree/tree/_neighbors.py b/sktree/tree/_neighbors.py index eccd27e05..53176b365 100644 --- a/sktree/tree/_neighbors.py +++ b/sktree/tree/_neighbors.py @@ -45,7 +45,10 @@ def compute_forest_similarity_matrix(forest, X): # ported from https://github.com/neurodata/hyppo/blob/main/hyppo/independence/_utils.py class SimMatrixMixin: - """Mixin class to calculate similarity and dissimilarity matrices.""" + """Mixin class to calculate similarity and dissimilarity matrices. + + This augments tree/forest models with the sklearn's nearest-neighbors API. + """ def compute_similarity_matrix(self, X): """ @@ -114,9 +117,9 @@ def kneighbors(self, X=None, n_neighbors=None, return_distance=True, metric="l2" self.neigh_est_ = neigh_est if n_neighbors != self.neigh_est_.n_neighbors: - raise RuntimeError(f"n_neighbors must be the same as the one used in the fit method.") + raise RuntimeError("n_neighbors must be the same as the one used in the fit method.") if metric != self.neigh_est_.metric: - raise RuntimeError(f"metric must be the same as the one used in the fit method.") + raise RuntimeError("metric must be the same as the one used in the fit method.") # get the fitted forest distance matrix dists = compute_forest_similarity_matrix(self, X) diff --git a/sktree/tree/tests/test_marginal.py b/sktree/tree/tests/test_marginal.py index 9ff937d80..21609a418 100644 --- a/sktree/tree/tests/test_marginal.py +++ b/sktree/tree/tests/test_marginal.py @@ -1,14 +1,88 @@ +from typing import Any, Dict + import numpy as np import pytest +from numpy.testing import assert_array_equal, assert_raises -from sktree._lib.sklearn.ensemble._forest import RandomForestClassifier +from sktree import ( + ObliqueRandomForestClassifier, + ObliqueRandomForestRegressor, + PatchObliqueRandomForestClassifier, + PatchObliqueRandomForestRegressor, + UnsupervisedObliqueRandomForest, + UnsupervisedRandomForest, +) +from sktree._lib.sklearn.ensemble._forest import ( + ExtraTreesClassifier, + ExtraTreesRegressor, + RandomForestClassifier, + RandomForestRegressor, + RandomTreesEmbedding, +) from sktree._lib.sklearn.tree import ( DecisionTreeClassifier, DecisionTreeRegressor, ExtraTreeClassifier, + ExtraTreeRegressor, ) from sktree.tree._marginalize import apply_marginal +CLF_TREES = { + "DecisionTreeClassifier": DecisionTreeClassifier, + "ExtraTreeClassifier": ExtraTreeClassifier, +} + +REG_TREES = { + "DecisionTreeRegressor": DecisionTreeRegressor, + "ExtraTreeRegressor": ExtraTreeRegressor, +} + + +FOREST_CLASSIFIERS = { + "ExtraTreesClassifier": ExtraTreesClassifier, + "RandomForestClassifier": RandomForestClassifier, +} + +FOREST_TRANSFORMERS = { + "RandomTreesEmbedding": RandomTreesEmbedding, +} + +FOREST_REGRESSORS = { + "ExtraTreesRegressor": ExtraTreesRegressor, + "RandomForestRegressor": RandomForestRegressor, +} + +FOREST_CLUSTERING = { + "UnsupervisedRandomForest": UnsupervisedRandomForest, +} + +OBLIQUE_FORESTS = { + "ObliqueRandomForestClassifier": ObliqueRandomForestClassifier, + "ObliqueRandomForestRegressor": ObliqueRandomForestRegressor, + "PatchObliqueRandomForestClassifier": PatchObliqueRandomForestClassifier, + "PatchObliqueRandomForestRegressor": PatchObliqueRandomForestRegressor, + "UnsupervisedObliqueRandomForest": UnsupervisedObliqueRandomForest, +} + +FOREST_ESTIMATORS: Dict[str, Any] = dict() +FOREST_ESTIMATORS.update(FOREST_CLASSIFIERS) +FOREST_ESTIMATORS.update(FOREST_REGRESSORS) +FOREST_ESTIMATORS.update(FOREST_TRANSFORMERS) +FOREST_ESTIMATORS.update(FOREST_CLUSTERING) + +FOREST_CLASSIFIERS_REGRESSORS: Dict[str, Any] = FOREST_CLASSIFIERS.copy() +FOREST_CLASSIFIERS_REGRESSORS.update(FOREST_REGRESSORS) + + +ALL_TREES: dict = dict() +ALL_TREES.update(CLF_TREES) +ALL_TREES.update(REG_TREES) + + +def assert_array_not_equal(x, y): + return assert_raises(AssertionError, assert_array_equal, x, y) + + X_small = np.array( [ [0, 0, 4, 0, 0, 0, 1, -14, 0, -4, 0, 0, 0, 0], @@ -40,21 +114,31 @@ y_small = [1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0] -def test_apply_marginal(): +@pytest.mark.parametrize("est_name", FOREST_ESTIMATORS) +def test_apply_marginal(est_name): # Create sample data - X = np.array([[1, 2], [3, 4], [5, 6]]) - S = np.array([1, 0]) # Example marginalization indices + S = np.array([1, 0, 5]) # Example marginalization indices + n_samples = len(X_small) # Test with RandomForestClassifier (forest input) - est_forest = RandomForestClassifier() - est_forest.fit(X, [0, 1, 0]) # Example fitting - X_leaves_forest = apply_marginal(est_forest, X, S) - assert X_leaves_forest.shape == (3, est_forest.n_estimators) # Check the shape of the output - # Add more assertions based on your specific requirements for forest input - - # Test with ExtraTreeClassifier (tree input) - est_tree = ExtraTreeClassifier() - est_tree.fit(X, [0, 1, 0]) # Example fitting - X_leaves_tree = apply_marginal(est_tree, X, S) - assert X_leaves_tree.shape == (3,) # Check the shape of the output - # Add more assertions based on your specific requirements for tree input + est_forest = FOREST_ESTIMATORS[est_name](n_estimators=5, random_state=0) + est_forest.fit(X_small, y_small) # Example fitting + X_leaves_forest = apply_marginal(est_forest, X_small, S) + assert X_leaves_forest.shape == ( + n_samples, + est_forest.n_estimators, + ) # Check the shape of the output + assert_array_not_equal(X_leaves_forest, est_forest.apply(X_small)) # Check the output + + # without marginalization, the tree should be exactly traversed the same. + assert_array_equal(apply_marginal(est_forest, X_small, []), est_forest.apply(X_small)) + + +@pytest.mark.parametrize("est_name", OBLIQUE_FORESTS) +def test_apply_marginal_error(est_name): + S = np.array([1, 0, 5]) # Example marginalization indices + est_forest = OBLIQUE_FORESTS[est_name](n_estimators=5, random_state=0) + est_forest.fit(X_small, y_small) # Example fitting + + with pytest.raises(RuntimeError, match="only supports axis-aligned trees"): + apply_marginal(est_forest, X_small, S) From 9ab9fa34c9d8bd1d7a9b9df36b40b98dad2eef97 Mon Sep 17 00:00:00 2001 From: Adam Li Date: Thu, 15 Jun 2023 20:36:24 -0400 Subject: [PATCH 06/17] Update fork Signed-off-by: Adam Li --- .gitignore | 2 ++ docs/conf.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 05941af96..032a69cc5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ coverage commit.txt sktree/_lib/sklearn/ +*.png + # Sphinx documentation docs/_build/ docs/generated/ diff --git a/docs/conf.py b/docs/conf.py index 122d2ec56..8415e3b15 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -204,6 +204,8 @@ "_type_", "MetadataRequest", "~utils.metadata_routing.MetadataRequest", + "quantiles", + "n_quantiles", } # validation From 318c0e825023b4517b3cc8737ec1667361fa39b7 Mon Sep 17 00:00:00 2001 From: Adam Li Date: Thu, 15 Jun 2023 20:37:34 -0400 Subject: [PATCH 07/17] Add force Signed-off-by: Adam Li --- .spin/cmds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.spin/cmds.py b/.spin/cmds.py index 8427b5b78..c1179be04 100644 --- a/.spin/cmds.py +++ b/.spin/cmds.py @@ -80,7 +80,7 @@ def setup_submodule(forcesubmodule=False): "submodule", "update", "--init", - # "--force", + "--force", ] ) From 7360056661f0df698793d764f1be8175d2887ac6 Mon Sep 17 00:00:00 2001 From: Adam Li Date: Fri, 16 Jun 2023 12:00:47 -0400 Subject: [PATCH 08/17] Working nn meta estimator Signed-off-by: Adam Li --- sktree/tests/test_neighbors.py | 1 + sktree/tree/_marginalize.py | 4 +- sktree/tree/_neighbors.py | 110 ++++++++++++++++++++-------- sktree/tree/tests/meson.build | 3 +- sktree/tree/tests/test_neighbors.py | 57 ++++++++++++++ 5 files changed, 140 insertions(+), 35 deletions(-) create mode 100644 sktree/tree/tests/test_neighbors.py diff --git a/sktree/tests/test_neighbors.py b/sktree/tests/test_neighbors.py index 97852cf2e..84fba22f5 100644 --- a/sktree/tests/test_neighbors.py +++ b/sktree/tests/test_neighbors.py @@ -31,5 +31,6 @@ def test_similarity_matrix(forest): clf.fit(X, y) sim_mat = clf.compute_similarity_matrix(X) + assert sim_mat.shape == (n_samples, n_samples) assert np.allclose(sim_mat, sim_mat.T) assert np.all((sim_mat.diagonal() == 1)) diff --git a/sktree/tree/_marginalize.py b/sktree/tree/_marginalize.py index eee14f1b3..173e0fa1c 100644 --- a/sktree/tree/_marginalize.py +++ b/sktree/tree/_marginalize.py @@ -228,7 +228,7 @@ def compute_conditional(self, X, S, y=None, n_repeats=10): # that leaf = I_{b,l, Z=z} for tree in self.estimators_: # tree node value (n_leaves, n_outputs, max_classes) - tree_leaves = tree.tree_.value + # tree_leaves = tree.tree_.value for leaf in range(tree.tree_.n_leaves): # get the size of the leaf @@ -236,7 +236,7 @@ def compute_conditional(self, X, S, y=None, n_repeats=10): # compute interval that reaches this leaf # now compute P(Y | x \in l) for every leaf - proba_y_x = tree_leaves[leaf, :, :] + # proba_y_x = tree_leaves[leaf, :, :] # get sample indices that reach this leaf in Z=z sample_indices = [] diff --git a/sktree/tree/_neighbors.py b/sktree/tree/_neighbors.py index 53176b365..1bee3f1b9 100644 --- a/sktree/tree/_neighbors.py +++ b/sktree/tree/_neighbors.py @@ -1,6 +1,7 @@ import numbers import numpy as np +from sklearn.base import BaseEstimator, MetaEstimatorMixin from sklearn.neighbors import NearestNeighbors from sklearn.utils.validation import check_is_fitted @@ -26,7 +27,7 @@ def compute_forest_similarity_matrix(forest, X): aff_matrix : array-like of shape (n_samples, n_samples) The estimated distance matrix. """ - if isinstance(forest, BaseForest): + if hasattr(forest, "estimator_"): # apply to the leaves X_leaves = forest.apply(X) @@ -37,12 +38,17 @@ def compute_forest_similarity_matrix(forest, X): n_est = 1 aff_matrix = sum(np.equal.outer(X_leaves[:, i], X_leaves[:, i]) for i in range(n_est)) - # normalize by the number of trees aff_matrix = np.divide(aff_matrix, n_est) return aff_matrix +def _compute_distance_matrix(aff_matrix): + """Private function to compute distance matrix after `compute_similarity_matrix`.""" + dists = 1.0 - aff_matrix + return dists + + # ported from https://github.com/neurodata/hyppo/blob/main/hyppo/independence/_utils.py class SimMatrixMixin: """Mixin class to calculate similarity and dissimilarity matrices. @@ -66,7 +72,58 @@ def compute_similarity_matrix(self, X): """ return compute_forest_similarity_matrix(self, X) - def kneighbors(self, X=None, n_neighbors=None, return_distance=True, metric="l2"): + +class NearestNeighborsMetaEstimator(BaseEstimator, MetaEstimatorMixin): + """Meta-estimator for nearest neighbors. + + Uses a decision-tree, or forest model to compute distances between samples + and then uses the sklearn's nearest-neighbors API to compute neighbors. + + Parameters + ---------- + estimator : BaseDecisionTree, BaseForest + The estimator to use for computing distances. + n_neighbors : int, optional + Number of neighbors to use by default for kneighbors queries, by default 5. + algorithm : str, optional + Algorithm used to compute the nearest-neighbors, by default 'auto'. + See :class:`sklearn.neighbors.NearestNeighbors` for details. + radius : float, optional + Range of parameter space to use by default for radius_neighbors queries, by default 1.0. + n_jobs : int, optional + The number of parallel jobs to run for neighbors, by default None. + """ + + def __init__(self, estimator, n_neighbors=5, radius=1.0, algorithm="auto", n_jobs=None): + self.estimator = estimator + self.n_neighbors = n_neighbors + self.algorithm = algorithm + self.radius = radius + self.n_jobs = n_jobs + + def fit(self, X, y=None): + # self._validate_params() + self.estimator_ = self.estimator + check_is_fitted(self.estimator_) + self._fit(X, self.n_neighbors) + return self + + def _fit(self, X, n_neighbors): + self.neigh_est_ = NearestNeighbors( + n_neighbors=n_neighbors, + algorithm=self.algorithm, + metric="precomputed", + n_jobs=self.n_jobs, + ) + + # compute the distance matrix + aff_matrix = compute_forest_similarity_matrix(self.estimator_, X) + dists = _compute_distance_matrix(aff_matrix) + + # fit the nearest-neighbors estimator + self.neigh_est_.fit(dists) + + def kneighbors(self, X=None, n_neighbors=None, return_distance=True): """Find the K-neighbors of a point. Returns indices of and distances to the neighbors of each point. @@ -105,28 +162,9 @@ def kneighbors(self, X=None, n_neighbors=None, return_distance=True, metric="l2" ) if X is not None: - raise RuntimeError("X cannot be passed in.") + self._fit(X, n_neighbors) - if not hasattr(self, "neigh_est_"): - # compute the nearest neighbors in the space using specified NN algorithm - # then get the K nearest nbrs and their distances - neigh_est = NearestNeighbors( - n_neighbors=n_neighbors, algorithm="precomputed", metric=metric, n_jobs=self.n_jobs - ) - - self.neigh_est_ = neigh_est - - if n_neighbors != self.neigh_est_.n_neighbors: - raise RuntimeError("n_neighbors must be the same as the one used in the fit method.") - if metric != self.neigh_est_.metric: - raise RuntimeError("metric must be the same as the one used in the fit method.") - - # get the fitted forest distance matrix - dists = compute_forest_similarity_matrix(self, X) - - # now fit the nearest neighbors algorithm to the forest-distance matrix - neigh_est.fit(dists) - return neigh_est.kneighbors(n_neighbors=n_neighbors, return_distance=return_distance) + return self.neigh_est_.kneighbors(n_neighbors=n_neighbors, return_distance=return_distance) def radius_neighbors(self, X=None, radius=None, return_distance=True, sort_results=False): """Find the neighbors within a given radius of a point or points. @@ -145,9 +183,10 @@ def radius_neighbors(self, X=None, radius=None, return_distance=True, sort_resul If not provided, neighbors of each indexed point are returned. In this case, the query point is not considered its own neighbor. - radius : float, default=None + radius : float, or array-like of shape (n_samples,) default=None Limiting distance of neighbors to return. The default is the value - passed to the constructor. + passed to the constructor. If an array-like of shape (n_samples), + then will query for each sample point with a different radius. return_distance : bool, default=True Whether or not to return the distances. @@ -180,15 +219,22 @@ def radius_neighbors(self, X=None, radius=None, return_distance=True, sort_resul For efficiency, `radius_neighbors` returns arrays of objects, where each object is a 1D array of indices or distances. """ - dists, _ = self.neigh_est_.kneighbors() - radius_per_sample = dists[:, -1] - n_samples = radius_per_sample.shape[0] + check_is_fitted(self) + + if X is not None: + n_samples = X.shape[0] + else: + n_samples = self.neigh_est_.n_samples_fit_ + + if isinstance(radius, numbers.Number): + radius = [radius] * n_samples - nn_ind_data = np.zeros((n_samples,)) - nn_dist_data = np.zeros((n_samples,)) + # now construct nearest neighbor indices and distances within radius + nn_ind_data = np.zeros((n_samples,), dtype=object) + nn_dist_data = np.zeros((n_samples,), dtype=object) for idx in range(n_samples): nn = self.neigh_est_.radius_neighbors( - X=X, radius=radius, return_distance=return_distance, sort_results=sort_results + X=X, radius=radius[idx], return_distance=return_distance, sort_results=sort_results ) if return_distance: diff --git a/sktree/tree/tests/meson.build b/sktree/tree/tests/meson.build index 9807b5e42..b3037aae0 100644 --- a/sktree/tree/tests/meson.build +++ b/sktree/tree/tests/meson.build @@ -3,7 +3,8 @@ python_sources = [ 'test_tree.py', 'test_utils.py', 'test_honest_tree.py', - 'test_marginal.py' + 'test_marginal.py', + 'test_neighbors.py', ] py3.install_sources( diff --git a/sktree/tree/tests/test_neighbors.py b/sktree/tree/tests/test_neighbors.py new file mode 100644 index 000000000..22b6f1991 --- /dev/null +++ b/sktree/tree/tests/test_neighbors.py @@ -0,0 +1,57 @@ +import numpy as np +import pytest +from sklearn.datasets import make_classification +from sklearn.ensemble import ExtraTreesClassifier, RandomForestClassifier +from sklearn.neighbors import NearestNeighbors +from sklearn.tree import ( + DecisionTreeClassifier, + DecisionTreeRegressor, + ExtraTreeClassifier, + ExtraTreeRegressor, +) + +from sktree.tree._neighbors import NearestNeighborsMetaEstimator + + +@pytest.fixture +def sample_data(): + # Generate sample data for testing + X, y = make_classification(n_samples=100, n_features=10, random_state=42) + return X, y + + +@pytest.mark.parametrize( + "estimator", + [ + DecisionTreeClassifier(random_state=0), + DecisionTreeRegressor(random_state=0), + ExtraTreeClassifier(random_state=0), + ExtraTreeRegressor(random_state=0), + RandomForestClassifier(random_state=0, n_estimators=10), + ExtraTreesClassifier(random_state=0, n_estimators=10), + ], +) +def test_nearest_neighbors_meta_estimator(sample_data, estimator): + X, y = sample_data + estimator.fit(X, y) + + meta_estimator = NearestNeighborsMetaEstimator(estimator) + + # Fit the meta-estimator + meta_estimator.fit(X, y) + + # Test the fitted estimator attribute + assert hasattr(meta_estimator, "estimator_") + + # Test the nearest neighbors estimator + assert isinstance(meta_estimator.neigh_est_, NearestNeighbors) + + # Test the kneighbors method + neigh_dist, neigh_ind = meta_estimator.kneighbors() + assert neigh_dist.shape == (X.shape[0], meta_estimator.n_neighbors) + assert neigh_ind.shape == (X.shape[0], meta_estimator.n_neighbors) + + # Test the radius_neighbors method + neigh_dist, neigh_ind = meta_estimator.radius_neighbors(radius=0.5) + assert neigh_dist.shape == (X.shape[0],) + assert neigh_ind.shape == (X.shape[0],) From 5c6b795573230707f93ed7ccd5bf4c3f1516732f Mon Sep 17 00:00:00 2001 From: Adam Li Date: Fri, 16 Jun 2023 17:10:30 -0400 Subject: [PATCH 09/17] Refactor neighbors API and introduce the meta neighbors estimators Signed-off-by: Adam Li --- docs/api.rst | 12 ++ sktree/__init__.py | 3 +- sktree/meson.build | 1 + sktree/neighbors.py | 191 ++++++++++++++++++++++++++++ sktree/tests/test_neighbors.py | 65 +++++++++- sktree/tree/_neighbors.py | 182 -------------------------- sktree/tree/meson.build | 1 - sktree/tree/tests/meson.build | 1 - sktree/tree/tests/test_neighbors.py | 57 --------- 9 files changed, 270 insertions(+), 243 deletions(-) create mode 100644 sktree/neighbors.py delete mode 100644 sktree/tree/tests/test_neighbors.py diff --git a/docs/api.rst b/docs/api.rst index 2917de68b..bcd4e269e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -79,6 +79,18 @@ that turns the "tree-distance" into a proper distance metric. pairwise_forest_distance +In addition to providing a distance metric based on leaves, tree-models +provide a natural way to compute neighbors based on the splits. We provide +an API for extracting the nearest neighbors from a tree-model. This provides +an API-like interface similar to :class:`~sklearn.neighbors.NearestNeighbors`. + +.. currentmodule:: sktree.neighbors +.. autosummary:: + :toctree: generated/ + + NearestNeighborsMetaEstimator + + Experimental Functionality -------------------------- We also include experimental functionality that is works in progress. diff --git a/sktree/__init__.py b/sktree/__init__.py index d54b96834..bf5d7b1cb 100644 --- a/sktree/__init__.py +++ b/sktree/__init__.py @@ -36,7 +36,7 @@ # process, as it may not be compiled yet else: try: - from . import _lib, tree, ensemble, experimental + from . import _lib, tree, ensemble, experimental, neighbors from .ensemble._unsupervised_forest import ( UnsupervisedRandomForest, UnsupervisedObliqueRandomForest, @@ -59,6 +59,7 @@ "tree", "experimental", "ensemble", + "neighbors", "ObliqueRandomForestClassifier", "ObliqueRandomForestRegressor", "PatchObliqueRandomForestClassifier", diff --git a/sktree/meson.build b/sktree/meson.build index fc7df580e..0d5518a73 100644 --- a/sktree/meson.build +++ b/sktree/meson.build @@ -53,6 +53,7 @@ cython_c_args += numpy_nodepr_api python_sources = [ '__init__.py', + 'neighbors.py', ] py3.install_sources( diff --git a/sktree/neighbors.py b/sktree/neighbors.py new file mode 100644 index 000000000..7becffb94 --- /dev/null +++ b/sktree/neighbors.py @@ -0,0 +1,191 @@ +import numbers +from copy import copy + +import numpy as np +from sklearn.base import BaseEstimator, MetaEstimatorMixin +from sklearn.exceptions import NotFittedError +from sklearn.neighbors import NearestNeighbors +from sklearn.utils.validation import check_is_fitted + +from sktree.tree._neighbors import _compute_distance_matrix, compute_forest_similarity_matrix + + +class NearestNeighborsMetaEstimator(BaseEstimator, MetaEstimatorMixin): + """Meta-estimator for nearest neighbors. + + Uses a decision-tree, or forest model to compute distances between samples + and then uses the sklearn's nearest-neighbors API to compute neighbors. + + Parameters + ---------- + estimator : BaseDecisionTree, BaseForest + The estimator to use for computing distances. + n_neighbors : int, optional + Number of neighbors to use by default for kneighbors queries, by default 5. + algorithm : str, optional + Algorithm used to compute the nearest-neighbors, by default 'auto'. + See :class:`sklearn.neighbors.NearestNeighbors` for details. + radius : float, optional + Range of parameter space to use by default for radius_neighbors queries, by default 1.0. + n_jobs : int, optional + The number of parallel jobs to run for neighbors, by default None. + """ + + def __init__(self, estimator, n_neighbors=5, radius=1.0, algorithm="auto", n_jobs=None): + self.estimator = estimator + self.n_neighbors = n_neighbors + self.algorithm = algorithm + self.radius = radius + self.n_jobs = n_jobs + + def fit(self, X, y=None): + """Fit the nearest neighbors estimator from the training dataset.""" + X, y = self._validate_data(X, y, accept_sparse="csc") + + self.estimator_ = copy(self.estimator) + try: + check_is_fitted(self.estimator_) + except NotFittedError: + self.estimator_.fit(X, y) + + self._fit(X, self.n_neighbors) + return self + + def _fit(self, X, n_neighbors): + self.neigh_est_ = NearestNeighbors( + n_neighbors=n_neighbors, + algorithm=self.algorithm, + metric="precomputed", + n_jobs=self.n_jobs, + ) + + # compute the distance matrix + aff_matrix = compute_forest_similarity_matrix(self.estimator_, X) + dists = _compute_distance_matrix(aff_matrix) + + # fit the nearest-neighbors estimator + self.neigh_est_.fit(dists) + + def kneighbors(self, X=None, n_neighbors=None, return_distance=True): + """Find the K-neighbors of a point. + + Returns indices of and distances to the neighbors of each point. + + Parameters + ---------- + X : {array-like, sparse matrix}, shape (n_queries, n_features), \ + or (n_queries, n_indexed) if metric == 'precomputed', default=None + Not used, present for API consistency by convention. + + n_neighbors : int, default=None + Number of neighbors required for each sample. The default is the + value passed to the constructor. + + return_distance : bool, default=True + Whether or not to return the distances. + + Returns + ------- + neigh_dist : ndarray of shape (n_queries, n_neighbors) + Array representing the lengths to points, only present if + return_distance=True. + + neigh_ind : ndarray of shape (n_queries, n_neighbors) + Indices of the nearest points in the population matrix. + """ + check_is_fitted(self) + + if n_neighbors is None: + n_neighbors = self.n_neighbors + elif n_neighbors <= 0: + raise ValueError("Expected n_neighbors > 0. Got %d" % n_neighbors) + elif not isinstance(n_neighbors, numbers.Integral): + raise TypeError( + "n_neighbors does not take %s value, enter integer value" % type(n_neighbors) + ) + + if X is not None: + self._fit(X, n_neighbors) + + return self.neigh_est_.kneighbors(n_neighbors=n_neighbors, return_distance=return_distance) + + def radius_neighbors(self, X=None, radius=None, return_distance=True, sort_results=False): + """Find the neighbors within a given radius of a point or points. + + Return the indices and distances of each point from the dataset + lying in a ball with size ``radius`` around the points of the query + array. Points lying on the boundary are included in the results. + + The result points are *not* necessarily sorted by distance to their + query point. + + Parameters + ---------- + X : {array-like, sparse matrix} of (n_samples, n_features), default=None + The query point or points. + If not provided, neighbors of each indexed point are returned. + In this case, the query point is not considered its own neighbor. + + radius : float, or array-like of shape (n_samples,) default=None + Limiting distance of neighbors to return. The default is the value + passed to the constructor. If an array-like of shape (n_samples), + then will query for each sample point with a different radius. + + return_distance : bool, default=True + Whether or not to return the distances. + + sort_results : bool, default=False + If True, the distances and indices will be sorted by increasing + distances before being returned. If False, the results may not + be sorted. If `return_distance=False`, setting `sort_results=True` + will result in an error. + + .. versionadded:: 0.22 + + Returns + ------- + neigh_dist : ndarray of shape (n_samples,) of arrays + Array representing the distances to each point, only present if + `return_distance=True`. The distance values are computed according + to the ``metric`` constructor parameter. + + neigh_ind : ndarray of shape (n_samples,) of arrays + An array of arrays of indices of the approximate nearest points + from the population matrix that lie within a ball of size + ``radius`` around the query points. + + Notes + ----- + Because the number of neighbors of each point is not necessarily + equal, the results for multiple query points cannot be fit in a + standard data array. + For efficiency, `radius_neighbors` returns arrays of objects, where + each object is a 1D array of indices or distances. + """ + check_is_fitted(self) + + if X is not None: + n_samples = X.shape[0] + else: + n_samples = self.neigh_est_.n_samples_fit_ + + if isinstance(radius, numbers.Number): + radius = [radius] * n_samples + + # now construct nearest neighbor indices and distances within radius + nn_ind_data = np.zeros((n_samples,), dtype=object) + nn_dist_data = np.zeros((n_samples,), dtype=object) + for idx in range(n_samples): + nn = self.neigh_est_.radius_neighbors( + X=X, radius=radius[idx], return_distance=return_distance, sort_results=sort_results + ) + + if return_distance: + nn_ind_data[idx] = nn[0][idx] + nn_dist_data[idx] = nn[1][idx] + else: + nn_ind_data[idx] = nn + + if return_distance: + return nn_dist_data, nn_ind_data + return nn_ind_data diff --git a/sktree/tests/test_neighbors.py b/sktree/tests/test_neighbors.py index 84fba22f5..d44b9ec14 100644 --- a/sktree/tests/test_neighbors.py +++ b/sktree/tests/test_neighbors.py @@ -1,6 +1,15 @@ import numpy as np import pytest -from sklearn.datasets import make_blobs +from sklearn.datasets import make_blobs, make_classification +from sklearn.ensemble import ExtraTreesClassifier, RandomForestClassifier +from sklearn.neighbors import NearestNeighbors +from sklearn.tree import ( + DecisionTreeClassifier, + DecisionTreeRegressor, + ExtraTreeClassifier, + ExtraTreeRegressor, +) +from sklearn.utils.estimator_checks import parametrize_with_checks from sktree.ensemble import ( ObliqueRandomForestClassifier, @@ -8,6 +17,7 @@ UnsupervisedObliqueRandomForest, UnsupervisedRandomForest, ) +from sktree.neighbors import NearestNeighborsMetaEstimator FORESTS = [ ObliqueRandomForestClassifier, @@ -34,3 +44,56 @@ def test_similarity_matrix(forest): assert sim_mat.shape == (n_samples, n_samples) assert np.allclose(sim_mat, sim_mat.T) assert np.all((sim_mat.diagonal() == 1)) + + +@pytest.fixture +def sample_data(): + # Generate sample data for testing + X, y = make_classification(n_samples=100, n_features=10, random_state=42) + return X, y + + +@pytest.mark.parametrize( + "estimator", + [ + DecisionTreeClassifier(random_state=0), + DecisionTreeRegressor(random_state=0), + ExtraTreeClassifier(random_state=0), + ExtraTreeRegressor(random_state=0), + RandomForestClassifier(random_state=0, n_estimators=10), + ExtraTreesClassifier(random_state=0, n_estimators=10), + ], +) +def test_nearest_neighbors_meta_estimator(sample_data, estimator): + X, y = sample_data + estimator.fit(X, y) + + meta_estimator = NearestNeighborsMetaEstimator(estimator) + + # Fit the meta-estimator + meta_estimator.fit(X, y) + + # Test the fitted estimator attribute + assert hasattr(meta_estimator, "estimator_") + + # Test the nearest neighbors estimator + assert isinstance(meta_estimator.neigh_est_, NearestNeighbors) + + # Test the kneighbors method + neigh_dist, neigh_ind = meta_estimator.kneighbors() + assert neigh_dist.shape == (X.shape[0], meta_estimator.n_neighbors) + assert neigh_ind.shape == (X.shape[0], meta_estimator.n_neighbors) + + # Test the radius_neighbors method + neigh_dist, neigh_ind = meta_estimator.radius_neighbors(radius=0.5) + assert neigh_dist.shape == (X.shape[0],) + assert neigh_ind.shape == (X.shape[0],) + + +@parametrize_with_checks( + [ + NearestNeighborsMetaEstimator(DecisionTreeClassifier(random_state=0)), + ] +) +def test_sklearn_compatible_transformer(estimator, check): + check(estimator) diff --git a/sktree/tree/_neighbors.py b/sktree/tree/_neighbors.py index 1bee3f1b9..17ba3e32f 100644 --- a/sktree/tree/_neighbors.py +++ b/sktree/tree/_neighbors.py @@ -1,11 +1,4 @@ -import numbers - import numpy as np -from sklearn.base import BaseEstimator, MetaEstimatorMixin -from sklearn.neighbors import NearestNeighbors -from sklearn.utils.validation import check_is_fitted - -from sktree._lib.sklearn.ensemble._forest import BaseForest def compute_forest_similarity_matrix(forest, X): @@ -71,178 +64,3 @@ def compute_similarity_matrix(self, X): The similarity matrix among the samples. """ return compute_forest_similarity_matrix(self, X) - - -class NearestNeighborsMetaEstimator(BaseEstimator, MetaEstimatorMixin): - """Meta-estimator for nearest neighbors. - - Uses a decision-tree, or forest model to compute distances between samples - and then uses the sklearn's nearest-neighbors API to compute neighbors. - - Parameters - ---------- - estimator : BaseDecisionTree, BaseForest - The estimator to use for computing distances. - n_neighbors : int, optional - Number of neighbors to use by default for kneighbors queries, by default 5. - algorithm : str, optional - Algorithm used to compute the nearest-neighbors, by default 'auto'. - See :class:`sklearn.neighbors.NearestNeighbors` for details. - radius : float, optional - Range of parameter space to use by default for radius_neighbors queries, by default 1.0. - n_jobs : int, optional - The number of parallel jobs to run for neighbors, by default None. - """ - - def __init__(self, estimator, n_neighbors=5, radius=1.0, algorithm="auto", n_jobs=None): - self.estimator = estimator - self.n_neighbors = n_neighbors - self.algorithm = algorithm - self.radius = radius - self.n_jobs = n_jobs - - def fit(self, X, y=None): - # self._validate_params() - self.estimator_ = self.estimator - check_is_fitted(self.estimator_) - self._fit(X, self.n_neighbors) - return self - - def _fit(self, X, n_neighbors): - self.neigh_est_ = NearestNeighbors( - n_neighbors=n_neighbors, - algorithm=self.algorithm, - metric="precomputed", - n_jobs=self.n_jobs, - ) - - # compute the distance matrix - aff_matrix = compute_forest_similarity_matrix(self.estimator_, X) - dists = _compute_distance_matrix(aff_matrix) - - # fit the nearest-neighbors estimator - self.neigh_est_.fit(dists) - - def kneighbors(self, X=None, n_neighbors=None, return_distance=True): - """Find the K-neighbors of a point. - - Returns indices of and distances to the neighbors of each point. - - Parameters - ---------- - X : {array-like, sparse matrix}, shape (n_queries, n_features), \ - or (n_queries, n_indexed) if metric == 'precomputed', default=None - Not used, present for API consistency by convention. - - n_neighbors : int, default=None - Number of neighbors required for each sample. The default is the - value passed to the constructor. - - return_distance : bool, default=True - Whether or not to return the distances. - - Returns - ------- - neigh_dist : ndarray of shape (n_queries, n_neighbors) - Array representing the lengths to points, only present if - return_distance=True. - - neigh_ind : ndarray of shape (n_queries, n_neighbors) - Indices of the nearest points in the population matrix. - """ - check_is_fitted(self) - - if n_neighbors is None: - n_neighbors = self.n_neighbors - elif n_neighbors <= 0: - raise ValueError("Expected n_neighbors > 0. Got %d" % n_neighbors) - elif not isinstance(n_neighbors, numbers.Integral): - raise TypeError( - "n_neighbors does not take %s value, enter integer value" % type(n_neighbors) - ) - - if X is not None: - self._fit(X, n_neighbors) - - return self.neigh_est_.kneighbors(n_neighbors=n_neighbors, return_distance=return_distance) - - def radius_neighbors(self, X=None, radius=None, return_distance=True, sort_results=False): - """Find the neighbors within a given radius of a point or points. - - Return the indices and distances of each point from the dataset - lying in a ball with size ``radius`` around the points of the query - array. Points lying on the boundary are included in the results. - - The result points are *not* necessarily sorted by distance to their - query point. - - Parameters - ---------- - X : {array-like, sparse matrix} of (n_samples, n_features), default=None - The query point or points. - If not provided, neighbors of each indexed point are returned. - In this case, the query point is not considered its own neighbor. - - radius : float, or array-like of shape (n_samples,) default=None - Limiting distance of neighbors to return. The default is the value - passed to the constructor. If an array-like of shape (n_samples), - then will query for each sample point with a different radius. - - return_distance : bool, default=True - Whether or not to return the distances. - - sort_results : bool, default=False - If True, the distances and indices will be sorted by increasing - distances before being returned. If False, the results may not - be sorted. If `return_distance=False`, setting `sort_results=True` - will result in an error. - - .. versionadded:: 0.22 - - Returns - ------- - neigh_dist : ndarray of shape (n_samples,) of arrays - Array representing the distances to each point, only present if - `return_distance=True`. The distance values are computed according - to the ``metric`` constructor parameter. - - neigh_ind : ndarray of shape (n_samples,) of arrays - An array of arrays of indices of the approximate nearest points - from the population matrix that lie within a ball of size - ``radius`` around the query points. - - Notes - ----- - Because the number of neighbors of each point is not necessarily - equal, the results for multiple query points cannot be fit in a - standard data array. - For efficiency, `radius_neighbors` returns arrays of objects, where - each object is a 1D array of indices or distances. - """ - check_is_fitted(self) - - if X is not None: - n_samples = X.shape[0] - else: - n_samples = self.neigh_est_.n_samples_fit_ - - if isinstance(radius, numbers.Number): - radius = [radius] * n_samples - - # now construct nearest neighbor indices and distances within radius - nn_ind_data = np.zeros((n_samples,), dtype=object) - nn_dist_data = np.zeros((n_samples,), dtype=object) - for idx in range(n_samples): - nn = self.neigh_est_.radius_neighbors( - X=X, radius=radius[idx], return_distance=return_distance, sort_results=sort_results - ) - - if return_distance: - nn_ind_data[idx] = nn[0][idx] - nn_dist_data[idx] = nn[1][idx] - else: - nn_ind_data[idx] = nn - - if return_distance: - return nn_dist_data, nn_ind_data - return nn_ind_data diff --git a/sktree/tree/meson.build b/sktree/tree/meson.build index d94ad1f6c..09ac76013 100644 --- a/sktree/tree/meson.build +++ b/sktree/tree/meson.build @@ -16,7 +16,6 @@ foreach ext: extensions ) endforeach -# TODO: comment in _classes.py when we have a working Cython unsupervised tree with a Python API python_sources = [ '__init__.py', '_classes.py', diff --git a/sktree/tree/tests/meson.build b/sktree/tree/tests/meson.build index b3037aae0..0d25326f3 100644 --- a/sktree/tree/tests/meson.build +++ b/sktree/tree/tests/meson.build @@ -4,7 +4,6 @@ python_sources = [ 'test_utils.py', 'test_honest_tree.py', 'test_marginal.py', - 'test_neighbors.py', ] py3.install_sources( diff --git a/sktree/tree/tests/test_neighbors.py b/sktree/tree/tests/test_neighbors.py deleted file mode 100644 index 22b6f1991..000000000 --- a/sktree/tree/tests/test_neighbors.py +++ /dev/null @@ -1,57 +0,0 @@ -import numpy as np -import pytest -from sklearn.datasets import make_classification -from sklearn.ensemble import ExtraTreesClassifier, RandomForestClassifier -from sklearn.neighbors import NearestNeighbors -from sklearn.tree import ( - DecisionTreeClassifier, - DecisionTreeRegressor, - ExtraTreeClassifier, - ExtraTreeRegressor, -) - -from sktree.tree._neighbors import NearestNeighborsMetaEstimator - - -@pytest.fixture -def sample_data(): - # Generate sample data for testing - X, y = make_classification(n_samples=100, n_features=10, random_state=42) - return X, y - - -@pytest.mark.parametrize( - "estimator", - [ - DecisionTreeClassifier(random_state=0), - DecisionTreeRegressor(random_state=0), - ExtraTreeClassifier(random_state=0), - ExtraTreeRegressor(random_state=0), - RandomForestClassifier(random_state=0, n_estimators=10), - ExtraTreesClassifier(random_state=0, n_estimators=10), - ], -) -def test_nearest_neighbors_meta_estimator(sample_data, estimator): - X, y = sample_data - estimator.fit(X, y) - - meta_estimator = NearestNeighborsMetaEstimator(estimator) - - # Fit the meta-estimator - meta_estimator.fit(X, y) - - # Test the fitted estimator attribute - assert hasattr(meta_estimator, "estimator_") - - # Test the nearest neighbors estimator - assert isinstance(meta_estimator.neigh_est_, NearestNeighbors) - - # Test the kneighbors method - neigh_dist, neigh_ind = meta_estimator.kneighbors() - assert neigh_dist.shape == (X.shape[0], meta_estimator.n_neighbors) - assert neigh_ind.shape == (X.shape[0], meta_estimator.n_neighbors) - - # Test the radius_neighbors method - neigh_dist, neigh_ind = meta_estimator.radius_neighbors(radius=0.5) - assert neigh_dist.shape == (X.shape[0],) - assert neigh_ind.shape == (X.shape[0],) From d341146c3d16e95946cd13b8e09711688a84483c Mon Sep 17 00:00:00 2001 From: Adam Li Date: Fri, 16 Jun 2023 17:49:19 -0400 Subject: [PATCH 10/17] Update submodule Signed-off-by: Adam Li --- docs/conf.py | 10 ++++++++++ docs/whats_new/v0.1.rst | 2 +- sktree/_lib/sklearn_fork | 2 +- sktree/tree/_marginalize.py | 4 ++-- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 8415e3b15..03fd1ee03 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -206,6 +206,15 @@ "~utils.metadata_routing.MetadataRequest", "quantiles", "n_quantiles", + "metric", + "n_queries", + "BaseForest", + "BaseDecisionTree", + "n_indexed", + "n_queries", + "n_features_x", + "n_features_y", + "n_features_z", } # validation @@ -356,6 +365,7 @@ def replace_sklearn_fork_with_sklearn(app, what, name, obj, options, lines): # Use regular expressions to replace 'sklearn_fork' with 'sklearn' content = re.sub(r"`pipeline.Pipeline", r"`~sklearn.pipeline.Pipeline", content) content = re.sub(r"`~utils.metadata_routing.MetadataRequest", r"``MetadataRequest``", content) + content = re.sub(r"`np.quantile", r"`~np.quantile`", content) # Convert the modified string back to a list of lines lines[:] = content.split("\n") diff --git a/docs/whats_new/v0.1.rst b/docs/whats_new/v0.1.rst index d060abbb9..42b0cc719 100644 --- a/docs/whats_new/v0.1.rst +++ b/docs/whats_new/v0.1.rst @@ -36,7 +36,7 @@ Changelog - |Feature| A general-kernel MORF is now implemented where users can pass in a kernel library, by `Adam Li`_ (:pr:`70`) - |Feature| Implementation of ObliqueDecisionTreeRegressor, PatchObliqueDecisionTreeRegressor, ObliqueRandomForestRegressor, PatchObliqueRandomForestRegressor, by `SUKI-O`_ (:pr:`72`) - |Feature| Implementation of HonestTreeClassifier, HonestForestClassifier, by `Sambit Panda`_, `Adam Li`_, `Ronan Perry`_ and `Haoyin Xu`_ (:pr:`57`) -- |Feature| Implementation of (conditional) mutual information estimation via unsupervised tree models, by `Adam Li`_ (:pr:`47`) +- |Feature| Implementation of (conditional) mutual information estimation via unsupervised tree models and added NearestNeighborsMetaEstimator by `Adam Li`_ (:pr:`83`) Code and Documentation Contributors diff --git a/sktree/_lib/sklearn_fork b/sktree/_lib/sklearn_fork index 545e2a298..3f5cb6597 160000 --- a/sktree/_lib/sklearn_fork +++ b/sktree/_lib/sklearn_fork @@ -1 +1 @@ -Subproject commit 545e2a298ab403262e00a16f4d85ccde1c2a250b +Subproject commit 3f5cb6597e36a08f651f8f0eb7324e9658a14bea diff --git a/sktree/tree/_marginalize.py b/sktree/tree/_marginalize.py index 173e0fa1c..5c399543f 100644 --- a/sktree/tree/_marginalize.py +++ b/sktree/tree/_marginalize.py @@ -156,10 +156,10 @@ def _apply_marginal_tree( check_is_fitted(est) X = est._validate_X_predict(X, check_input=check_input) - traversal_method = TRAVERSAL_METHOD_MAP[traversal_method] + traversal_method_int = TRAVERSAL_METHOD_MAP[traversal_method] X_leaves = apply_marginal_tree( - est.tree_, X, S, traversal_method, use_sample_weight, random_state=random_state + est.tree_, X, S, traversal_method_int, use_sample_weight, random_state=random_state ) return X_leaves From b493b49a5168bfb9d010f03a5f440c34e73785ac Mon Sep 17 00:00:00 2001 From: Adam Li Date: Fri, 16 Jun 2023 18:36:08 -0400 Subject: [PATCH 11/17] Fix Signed-off-by: Adam Li --- docs/api.rst | 17 ++++++++++++----- docs/conf.py | 5 ++++- docs/references.bib | 25 +++++++++++++++++++++++++ sktree/_lib/sklearn_fork | 2 +- sktree/experimental/simulate.py | 10 +++++----- sktree/neighbors.py | 22 +++++++++++++++++++--- sktree/tree/_neighbors.py | 2 +- 7 files changed, 67 insertions(+), 16 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index bcd4e269e..c3e30d58c 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -73,11 +73,11 @@ Trees inherently produce a "distance-like" metric. We provide an API for extracting pairwise distances from the trees that include a correction that turns the "tree-distance" into a proper distance metric. -.. currentmodule:: sktree.ensemble +.. currentmodule:: sktree.tree .. autosummary:: :toctree: generated/ - pairwise_forest_distance + compute_forest_similarity_matrix In addition to providing a distance metric based on leaves, tree-models provide a natural way to compute neighbors based on the splits. We provide @@ -106,11 +106,18 @@ and conditional mutual information (CMI) estimators. Specifically, functions tha help simulate multivariate gaussian data and compute the analytical solutions for the entropy, MI and CMI of the Gaussian distributions. -.. currentmodule:: sktree.experimental +.. currentmodule:: sktree.experimental.simulate +.. autosummary:: + :toctree: generated/ + + simulate_multivariate_gaussian + simulate_helix + simulate_sphere + +.. currentmodule:: sktree.experimental.mutual_info .. autosummary:: :toctree: generated/ mi_gaussian cmi_gaussian - entropy_gaussian - simulate_multivariate_gaussian \ No newline at end of file + entropy_gaussian \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 03fd1ee03..ae75c979d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -162,6 +162,7 @@ "PatchObliqueDecisionTreeClassifier": "sktree.tree.PatchObliqueDecisionTreeClassifier", "ObliqueDecisionTreeRegressor": "sktree.tree.ObliqueDecisionTreeRegressor", "PatchObliqueDecisionTreeRegressor": "sktree.tree.PatchObliqueDecisionTreeRegressor", + "UnsupervisedObliqueRandomForest": "sktree.ensemble.UnsupervisedObliqueRandomForest", "DecisionTreeClassifier": "sklearn.tree.DecisionTreeClassifier", "DecisionTreeRegressor": "sklearn.tree.DecisionTreeRegressor", "pipeline.Pipeline": "sklearn.pipeline.Pipeline", @@ -215,6 +216,7 @@ "n_features_x", "n_features_y", "n_features_z", + 'n_neighbors', 'one', } # validation @@ -365,7 +367,8 @@ def replace_sklearn_fork_with_sklearn(app, what, name, obj, options, lines): # Use regular expressions to replace 'sklearn_fork' with 'sklearn' content = re.sub(r"`pipeline.Pipeline", r"`~sklearn.pipeline.Pipeline", content) content = re.sub(r"`~utils.metadata_routing.MetadataRequest", r"``MetadataRequest``", content) - content = re.sub(r"`np.quantile", r"`~np.quantile`", content) + content = re.sub(r"`np.quantile", r"`numpy.quantile", content) + content = re.sub(r"`~np.quantile", r"`numpy.quantile", content) # Convert the modified string back to a list of lines lines[:] = content.split("\n") diff --git a/docs/references.bib b/docs/references.bib index 290e683e0..07c251309 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -54,4 +54,29 @@ @article{TomitaSPORF2020 number = {104}, pages = {1--39}, url = {http://jmlr.org/papers/v21/18-664.html} +} + +@article{Darbellay1999Entropy, + title={Estimation of the Information by an Adaptive Partitioning of the Observation Space}, + author={Georges A. Darbellay and Igor Vajda}, + journal={IEEE Trans. Inf. Theory}, + year={1999}, + volume={45}, + pages={1315-1321} +} + +@article{Kraskov_2004, + title = {Estimating mutual information}, + volume = {69}, + url = {https://link.aps.org/doi/10.1103/PhysRevE.69.066138}, + doi = {10.1103/PhysRevE.69.066138}, + number = {6}, + urldate = {2023-01-27}, + journal = {Physical Review E}, + author = {Kraskov, Alexander and Stögbauer, Harald and Grassberger, Peter}, + month = jun, + year = {2004}, + note = {Publisher: American Physical Society}, + pages = {066138}, + file = {APS Snapshot:/Users/adam2392/Zotero/storage/GRW23BYU/PhysRevE.69.html:text/html;Full Text PDF:/Users/adam2392/Zotero/storage/NJT9QCVA/Kraskov et al. - 2004 - Estimating mutual information.pdf:application/pdf} } \ No newline at end of file diff --git a/sktree/_lib/sklearn_fork b/sktree/_lib/sklearn_fork index 3f5cb6597..7401ddcb1 160000 --- a/sktree/_lib/sklearn_fork +++ b/sktree/_lib/sklearn_fork @@ -1 +1 @@ -Subproject commit 3f5cb6597e36a08f651f8f0eb7324e9658a14bea +Subproject commit 7401ddcb19a42132cf46e79a14b22a2bdfb8519c diff --git a/sktree/experimental/simulate.py b/sktree/experimental/simulate.py index 01d3dc6ae..33f59c9a5 100644 --- a/sktree/experimental/simulate.py +++ b/sktree/experimental/simulate.py @@ -181,9 +181,9 @@ def simulate_multivariate_gaussian(mean=None, cov=None, d=2, n_samples=1000, see Parameters ---------- - mean : array-like of shape (d,) + mean : array-like of shape (n_features,) The optional mean array. If None (default), a random standard normal vector is drawn. - cov : array-like of shape (d,d) + cov : array-like of shape (n_features, n_features) The covariance array. If None (default), a random standard normal 2D array is drawn. It is then converted to a PD matrix. d : int @@ -195,11 +195,11 @@ def simulate_multivariate_gaussian(mean=None, cov=None, d=2, n_samples=1000, see Returns ------- - data : array-like of shape (n_samples, d) + data : array-like of shape (n_samples, n_features) The generated data from the distribution. - mean : array-like of shape (d,) + mean : array-like of shape (n_features,) The mean vector of the distribution. - cov : array-like of shape (d,d) + cov : array-like of shape (n_features, n_features) The covariance matrix of the distribution. """ rng = np.random.default_rng(seed) diff --git a/sktree/neighbors.py b/sktree/neighbors.py index 7becffb94..4421c6d41 100644 --- a/sktree/neighbors.py +++ b/sktree/neighbors.py @@ -22,11 +22,11 @@ class NearestNeighborsMetaEstimator(BaseEstimator, MetaEstimatorMixin): The estimator to use for computing distances. n_neighbors : int, optional Number of neighbors to use by default for kneighbors queries, by default 5. + radius : float, optional + Range of parameter space to use by default for radius_neighbors queries, by default 1.0. algorithm : str, optional Algorithm used to compute the nearest-neighbors, by default 'auto'. See :class:`sklearn.neighbors.NearestNeighbors` for details. - radius : float, optional - Range of parameter space to use by default for radius_neighbors queries, by default 1.0. n_jobs : int, optional The number of parallel jobs to run for neighbors, by default None. """ @@ -39,7 +39,23 @@ def __init__(self, estimator, n_neighbors=5, radius=1.0, algorithm="auto", n_job self.n_jobs = n_jobs def fit(self, X, y=None): - """Fit the nearest neighbors estimator from the training dataset.""" + """Fit the nearest neighbors estimator from the training dataset. + + Parameters + ---------- + X : {array-like, sparse matrix} of shape (n_samples, n_features) + The training input samples. Internally, it will be converted to + ``dtype=np.float32`` and if a sparse matrix is provided + to a sparse ``csc_matrix``. + + y : array-like of shape (n_samples,) or (n_samples, n_outputs) + The target values, by default None. + + Returns + ------- + self : NearestNeighborsMetaEstimator + Fitted estimator. + """ X, y = self._validate_data(X, y, accept_sparse="csc") self.estimator_ = copy(self.estimator) diff --git a/sktree/tree/_neighbors.py b/sktree/tree/_neighbors.py index 17ba3e32f..93d8ff1a0 100644 --- a/sktree/tree/_neighbors.py +++ b/sktree/tree/_neighbors.py @@ -10,7 +10,7 @@ def compute_forest_similarity_matrix(forest, X): Parameters ---------- - forest : sklearn.ensemble._forest.BaseForest or sklearn.tree.BaseDecisionTree + forest : BaseForest or BaseDecisionTree The fitted forest. X : array-like of shape (n_samples, n_features) The input data. From d1b5abaa721a6bee9aa31d5071e1b43adb005004 Mon Sep 17 00:00:00 2001 From: Adam Li Date: Fri, 16 Jun 2023 18:41:28 -0400 Subject: [PATCH 12/17] Fix Signed-off-by: Adam Li --- sktree/_lib/sklearn_fork | 2 +- sktree/experimental/mutual_info.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sktree/_lib/sklearn_fork b/sktree/_lib/sklearn_fork index 7401ddcb1..13e29135b 160000 --- a/sktree/_lib/sklearn_fork +++ b/sktree/_lib/sklearn_fork @@ -1 +1 @@ -Subproject commit 7401ddcb19a42132cf46e79a14b22a2bdfb8519c +Subproject commit 13e29135bd0b640f3bf325ec40a22a879096b719 diff --git a/sktree/experimental/mutual_info.py b/sktree/experimental/mutual_info.py index 57eae54d7..118b2bebb 100644 --- a/sktree/experimental/mutual_info.py +++ b/sktree/experimental/mutual_info.py @@ -68,7 +68,7 @@ def mi_gaussian(cov): def cmi_gaussian(cov, x_index, y_index, z_index): - """Computes the analytical CMI for a multivariate Gaussian distribution. + """Compute the analytical CMI for a multivariate Gaussian distribution. Parameters ---------- From 8d57305ac6719c3483fec1269056f67e9b275e66 Mon Sep 17 00:00:00 2001 From: Adam Li Date: Sat, 17 Jun 2023 11:01:24 -0400 Subject: [PATCH 13/17] Update submodule Signed-off-by: Adam Li --- sktree/_lib/sklearn_fork | 2 +- sktree/experimental/mutual_info.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/sktree/_lib/sklearn_fork b/sktree/_lib/sklearn_fork index 13e29135b..fe3072f4e 160000 --- a/sktree/_lib/sklearn_fork +++ b/sktree/_lib/sklearn_fork @@ -1 +1 @@ -Subproject commit 13e29135bd0b640f3bf325ec40a22a879096b719 +Subproject commit fe3072f4ee28f49d590e7b437bf01bffd61ab917 diff --git a/sktree/experimental/mutual_info.py b/sktree/experimental/mutual_info.py index 118b2bebb..8c74610a1 100644 --- a/sktree/experimental/mutual_info.py +++ b/sktree/experimental/mutual_info.py @@ -164,8 +164,8 @@ def mutual_info_ksg( The number of neighbors to use in defining the radius, by default 0.2. metric : str Any distance metric accepted by :class:`sklearn.neighbors.NearestNeighbors`. - If 'forest' (default), then uses an :class:`UnsupervisedObliqueRandomForest` to compute - geodesic distances. + If 'forest' (default), then uses an :class:`UnsupervisedObliqueRandomForest` + to compute geodesic distances. algorithm : str, optional Method to use, by default 'knn'. Can be ('ball_tree', 'kd_tree', 'brute'). n_jobs : int, optional @@ -191,11 +191,13 @@ def mutual_info_ksg( 5. Apply analytic solution for KSG estimate For MI :footcite:`Kraskov_2004`, the analytical solution is:: + .. math:: \\psi(k) - E[(\\psi(n_x) + \\psi(n_y))] + \\psi(n) For CMI :footcite:`Frenzel2007`m the analytical solution is:: - + .. math:: + \\psi(k) - E[(\\psi(n_{xz}) + \\psi(n_{yz}) - \\psi(n_{z}))] where :math:`\\psi` is the DiGamma function, and each expectation term From e9838c589ec6177653605396dab94edbd7987105 Mon Sep 17 00:00:00 2001 From: Adam Li Date: Mon, 19 Jun 2023 21:34:22 -0400 Subject: [PATCH 14/17] Update fork version Signed-off-by: Adam Li --- sktree/_lib/sklearn_fork | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sktree/_lib/sklearn_fork b/sktree/_lib/sklearn_fork index fe3072f4e..2d4de9aff 160000 --- a/sktree/_lib/sklearn_fork +++ b/sktree/_lib/sklearn_fork @@ -1 +1 @@ -Subproject commit fe3072f4ee28f49d590e7b437bf01bffd61ab917 +Subproject commit 2d4de9aff7567bf796626aed4f27149f6ccf399c From debffb1400613f886f5cd549e521682392048c49 Mon Sep 17 00:00:00 2001 From: Adam Li Date: Mon, 19 Jun 2023 21:35:47 -0400 Subject: [PATCH 15/17] Update fork version Signed-off-by: Adam Li --- sktree/_lib/sklearn_fork | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sktree/_lib/sklearn_fork b/sktree/_lib/sklearn_fork index 2d4de9aff..1c1ec8cff 160000 --- a/sktree/_lib/sklearn_fork +++ b/sktree/_lib/sklearn_fork @@ -1 +1 @@ -Subproject commit 2d4de9aff7567bf796626aed4f27149f6ccf399c +Subproject commit 1c1ec8cff3a181b7a86a4df8a2aeb01fa7cdbe6a From 8b06c45d0d9d80fe385a033cfaadb51100bc705a Mon Sep 17 00:00:00 2001 From: Adam Li Date: Mon, 19 Jun 2023 21:54:47 -0400 Subject: [PATCH 16/17] Fixed docs Signed-off-by: Adam Li --- docs/conf.py | 3 ++- sktree/experimental/mutual_info.py | 20 +++++++++++--------- sktree/experimental/simulate.py | 2 +- sktree/neighbors.py | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index ae75c979d..d40c5344a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -216,7 +216,8 @@ "n_features_x", "n_features_y", "n_features_z", - 'n_neighbors', 'one', + "n_neighbors", + "one", } # validation diff --git a/sktree/experimental/mutual_info.py b/sktree/experimental/mutual_info.py index 8c74610a1..a1820e715 100644 --- a/sktree/experimental/mutual_info.py +++ b/sktree/experimental/mutual_info.py @@ -164,8 +164,8 @@ def mutual_info_ksg( The number of neighbors to use in defining the radius, by default 0.2. metric : str Any distance metric accepted by :class:`sklearn.neighbors.NearestNeighbors`. - If 'forest' (default), then uses an :class:`UnsupervisedObliqueRandomForest` - to compute geodesic distances. + If 'forest' (default), then uses an + :class:`sktree.UnsupervisedObliqueRandomForest` to compute geodesic distances. algorithm : str, optional Method to use, by default 'knn'. Can be ('ball_tree', 'kd_tree', 'brute'). n_jobs : int, optional @@ -190,14 +190,16 @@ def mutual_info_ksg( 4. Get the number of NN in Z subspace within radius 'r' 5. Apply analytic solution for KSG estimate - For MI :footcite:`Kraskov_2004`, the analytical solution is:: - .. math:: + For MI, the analytical solution is: + + .. math:: \\psi(k) - E[(\\psi(n_x) + \\psi(n_y))] + \\psi(n) - For CMI :footcite:`Frenzel2007`m the analytical solution is:: - .. math:: - + For CMI, the analytical solution is: + + .. math:: + \\psi(k) - E[(\\psi(n_{xz}) + \\psi(n_{yz}) - \\psi(n_{z}))] where :math:`\\psi` is the DiGamma function, and each expectation term @@ -372,8 +374,8 @@ def _compute_nn( Method to use, by default 'knn'. Can be ('ball_tree', 'kd_tree', 'brute'). metric : str Any distance metric accepted by :class:`sklearn.neighbors.NearestNeighbors`. - If 'forest', then uses an :class:`UnsupervisedObliqueRandomForest` to compute - geodesic distances. + If 'forest', then uses an :class:`sktree.UnsupervisedObliqueRandomForest` + to compute geodesic distances. k : int, optional The number of k-nearest neighbors to query, by default 1. n_jobs : int, diff --git a/sktree/experimental/simulate.py b/sktree/experimental/simulate.py index 33f59c9a5..17b56145f 100644 --- a/sktree/experimental/simulate.py +++ b/sktree/experimental/simulate.py @@ -22,7 +22,7 @@ def simulate_helix( The value of the smallest radius, by default 0.0. radius_b : int, optional The value of the largest radius, by default 1.0 - obs_noise_func : scipy.stats.distribution, optional + obs_noise_func : Callable, optional By default None, which defaults to a Uniform distribution from (-0.005, 0.005). If passed in, then must be a callable that when called returns a random number denoting the noise. diff --git a/sktree/neighbors.py b/sktree/neighbors.py index 4421c6d41..1d6e1ed84 100644 --- a/sktree/neighbors.py +++ b/sktree/neighbors.py @@ -53,7 +53,7 @@ def fit(self, X, y=None): Returns ------- - self : NearestNeighborsMetaEstimator + self : object Fitted estimator. """ X, y = self._validate_data(X, y, accept_sparse="csc") From 74639048c3315917f9cb0c59800cae856a879361 Mon Sep 17 00:00:00 2001 From: Adam Li Date: Tue, 20 Jun 2023 11:30:35 -0400 Subject: [PATCH 17/17] Fix circleci Signed-off-by: Adam Li --- docs/api.rst | 2 +- sktree/__init__.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index c3e30d58c..da68af4ab 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -84,7 +84,7 @@ provide a natural way to compute neighbors based on the splits. We provide an API for extracting the nearest neighbors from a tree-model. This provides an API-like interface similar to :class:`~sklearn.neighbors.NearestNeighbors`. -.. currentmodule:: sktree.neighbors +.. currentmodule:: sktree .. autosummary:: :toctree: generated/ diff --git a/sktree/__init__.py b/sktree/__init__.py index bf5d7b1cb..d5b18b805 100644 --- a/sktree/__init__.py +++ b/sktree/__init__.py @@ -36,7 +36,8 @@ # process, as it may not be compiled yet else: try: - from . import _lib, tree, ensemble, experimental, neighbors + from . import _lib, tree, ensemble, experimental + from .neighbors import NearestNeighborsMetaEstimator from .ensemble._unsupervised_forest import ( UnsupervisedRandomForest, UnsupervisedObliqueRandomForest, @@ -59,7 +60,7 @@ "tree", "experimental", "ensemble", - "neighbors", + "NearestNeighborsMetaEstimator", "ObliqueRandomForestClassifier", "ObliqueRandomForestRegressor", "PatchObliqueRandomForestClassifier",