diff --git a/.spin/cmds.py b/.spin/cmds.py index a71f863be..c8ed5bda8 100644 --- a/.spin/cmds.py +++ b/.spin/cmds.py @@ -105,6 +105,7 @@ def setup_submodule(forcesubmodule=False): print(commit_fpath) with open(commit_fpath, "w") as f: f.write(current_hash) + print(commit, current_hash) util.run( [ diff --git a/README.md b/README.md index ac815d9ec..5727f1290 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![Latest PyPI release](https://img.shields.io/pypi/v/scikit-tree.svg)](https://pypi.org/project/scikit-tree/) [![DOI](https://zenodo.org/badge/491260497.svg)](https://zenodo.org/doi/10.5281/zenodo.8412279) + scikit-tree =========== diff --git a/benchmarks_nonasv/bench_est_mi.py b/benchmarks_nonasv/bench_est_mi.py new file mode 100644 index 000000000..64d0c93dd --- /dev/null +++ b/benchmarks_nonasv/bench_est_mi.py @@ -0,0 +1,73 @@ +# Reimplementation of Figure 4 from Uncertainty Forests + +import copy +import pickle + +import matplotlib.pyplot as plt +import numpy as np +import seaborn as sns +from joblib import Parallel, delayed +from scipy.integrate import nquad +from scipy.stats import entropy, multivariate_normal +from sklearn.calibration import CalibratedClassifierCV +from sklearn.ensemble import RandomForestClassifier + +from sktree import HonestForestClassifier +from sktree.experimental.simulate import simulate_separate_gaussians +from sktree.tree import ObliqueDecisionTreeClassifier + + +def plot_setting(X, y, name, ax): + colors = ["#c51b7d", "#2166ac", "#d95f02"] + ax.scatter(X[:, 0], X[:, 1], color = np.array(colors)[y], marker = ".") + + ax.set_xlim(left = -5.05) + ax.set_xlim(right = 5.05) + ax.set_ylabel(name) + + +def plot_example_2D_gaussians(): + names = ['Spherical Gaussians', 'Elliptical Gaussians', 'Three Class Gaussians'] + fig, axes = plt.subplots(1, len(names), figsize = (18,4)) + + mean = 3 if name == 'Three Class Gaussians' else 1 + X, y = simulate_separate_gaussians(n_samples=n, n_dims=2, **setting['kwargs'], mu1 = mean) + n_samples = 2000 + n_dims = 2 + X, y, means, sigmas, pi = simulate_separate_gaussians( + n_dims=n_dims, n_samples=n_samples, n_classes=n_classes, seed=seed + ) + +if __name__ == '__main__': + n_jobs = -1 + n_estimators = 100 + feature_combinations = 2.0 + n_nbrs = 5 + seed = 12345 + + # hyperparameters of the simulation + n_samples = 1000 + n_noise_dims = 20 + alpha = 0.001 + n_classes = 2 + + # dimensionality of mvg + n_dims = 3 + + # simulate separated multivariate Gaussians + X, y, means, sigmas, pi = simulate_separate_gaussians( + n_dims=n_dims, n_samples=n_samples, n_classes=n_classes, seed=seed + ) + + print(X.shape, y.shape) + + # Plot data. + fig, axes = plt.subplots(1, len(settings), figsize = (18,4)) + for i, setting in enumerate(settings): + plot_setting(2000, setting, axes[i]) + + plt.show() + plt.clf() + + + \ No newline at end of file diff --git a/doc/api.rst b/doc/api.rst index 59238d77a..6b4feca93 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -181,3 +181,10 @@ for the entropy, MI and CMI of the Gaussian distributions. mi_gaussian cmi_gaussian entropy_gaussian + + +.. currentmodule:: sktree.experimental.monte_carlo +.. autosummary:: + :toctree: generated/ + + conditional_resample diff --git a/doc/references.bib b/doc/references.bib index 8671a338b..1953b153e 100644 --- a/doc/references.bib +++ b/doc/references.bib @@ -11,6 +11,15 @@ @article{breiman2001random publisher = {Springer} } +@inproceedings{marx2022estimating, + title = {Estimating Mutual Information via Geodesic k NN}, + author = {Marx, Alexander and Fischer, Jonas}, + booktitle = {Proceedings of the 2022 SIAM International Conference on Data Mining (SDM)}, + pages = {415--423}, + year = {2022}, + organization = {SIAM} +} + @article{coleman2022scalable, title = {Scalable and efficient hypothesis testing with random forests}, author = {Coleman, Tim and Peng, Wei and Mentch, Lucas}, diff --git a/doc/whats_new/v0.4.rst b/doc/whats_new/v0.4.rst index 6270ad13c..cb787df12 100644 --- a/doc/whats_new/v0.4.rst +++ b/doc/whats_new/v0.4.rst @@ -12,7 +12,7 @@ Version 0.4 Changelog --------- -- +- |Feature| Implement forest-based MI and CMI estimators, by `Adam Li`_ (:pr:`110`) Code and Documentation Contributors ----------------------------------- diff --git a/experiments/plotting_cmi_analysis_unsupervised.ipynb b/experiments/plotting_cmi_analysis_unsupervised.ipynb new file mode 100644 index 000000000..fa1c35648 --- /dev/null +++ b/experiments/plotting_cmi_analysis_unsupervised.ipynb @@ -0,0 +1,1194 @@ +{ + "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 (in)dependent on Y given Z\n", + "- Gaussian: X is conditionally (in)dependent on Y given Z\n", + "\n", + "For each distribution, we will add a varying number of independent dimensions to the data (i.e. sampled from Gaussian distribution).\n", + "\n", + "## Graphical models of the simulation\n", + "\n", + "For our purposes, we are motivated by the feature selection problem in cancer research, where cancer status (1-4 severity) is the outcome variable of interest and certain feature sets (X and W) are possibly high-dimensional sets that may have causal impact on each other. With prior knowledge, we know that cancer status cannot cause any of the features in the feature sets. We are also typically concerned with comparing sets of features conditioned on at most one other feature set. This allows us to enumerate all possible causal DAG settings that match this. We have the following 8 possible DAGs:\n", + "\n", + "1. W is completely independent: $(X \\rightarrow Y, W)$\n", + "2. W is an observed covariate: $(X \\rightarrow Y, X \\leftarrow W \\rightarrow Y)$\n", + "3. W is an observed confounder that explains the correlation between X and Y: $(X, X \\leftarrow W \\rightarrow Y)$\n", + "4. W affects the outcome only: $(X \\rightarrow Y, W \\rightarrow Y)$\n", + "5. W affects the treatment only: $(X \\rightarrow Y, W \\rightarrow X)$\n", + "6. W affects the outcome only but X is independent: $(X, W \\rightarrow Y)$\n", + "7. W affects the treatment only but X is independent: $(Y, W \\rightarrow X)$\n", + "8. All are d-separated: $(Y, W, X)$\n", + "\n", + "With these, we assume that there are no other relevant latent confounders that are not controlled for in our experimental design.\n", + "\n", + "# Estimation Methods for Y discrete\n", + "\n", + "1. Direct estimation of entropy using supervised forests:\n", + "\n", + "$$I(X; Y) = H(X) - H(X | Y) = H(Y) - H(Y | X)$$\n", + "\n", + "-> H(Y| X) = clf.predict_proba(X) after fitting forest to (X, Y) pairs\n", + "-> H(Y) = empirical entropy calculuation\n", + "\n", + "$$I(X; Y|Z) = H(Y | Z) - H(Y | X, Z)$$\n", + "\n", + "-> Train two forests and directly estimate using pairs of (Y, Z) and (Y, X \\cup Z)\n", + "\n", + "2. Indirect estimation of entropy using KSG with unsupervised forests\n", + "\n", + "The (conditional) mutual information KSG estimator just requires training a forest.%load_ext lab_black" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "705fb134-54c2-42fd-bbc3-1d4541ae8769", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext lab_black" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2e7b6950-44a0-40a9-bf2a-b6eca06725c1", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "# from dodiscover.ci import CMITest\n", + "import pandas as pd\n", + "import scipy\n", + "import scipy.spatial\n", + "import seaborn as sns\n", + "from sklearn.neighbors import NearestNeighbors\n", + "from tqdm import tqdm\n", + "\n", + "import sktree\n", + "from sktree import (\n", + " NearestNeighborsMetaEstimator,\n", + " ObliqueRandomForestClassifier,\n", + " ObliqueRandomForestRegressor,\n", + " UnsupervisedObliqueRandomForest,\n", + " UnsupervisedRandomForest,\n", + ")\n", + "from sktree.experimental import SupervisedInfoForest, mutual_info_ksg\n", + "from sktree.experimental.ksg import _compute_radius_nbrs, mutual_info_ksg_nn\n", + "from sktree.experimental.mutual_info import (\n", + " cmi_from_entropy,\n", + " cmi_gaussian,\n", + " entropy_gaussian,\n", + " entropy_weibull,\n", + " mi_from_entropy,\n", + " mi_gamma,\n", + " mi_gaussian,\n", + ")\n", + "from sktree.experimental.simulate import (\n", + " embed_high_dims,\n", + " simulate_helix,\n", + " simulate_multivariate_gaussian,\n", + " simulate_sphere,\n", + ")\n", + "from sktree.neighbors import forest_distance\n", + "from sktree.tree import compute_forest_similarity_matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "bf387bc2-f1d1-4b7a-9dab-e9c8fb992b2a", + "metadata": {}, + "outputs": [], + "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": 39, + "id": "4fcde0a4-7413-4f09-bbd8-fe62fcd62c2d", + "metadata": {}, + "outputs": [], + "source": [ + "n_jobs = -1\n", + "n_estimators = 100\n", + "feature_combinations = 2.0\n", + "n_nbrs = 5\n", + "\n", + "# hyperparameters of the simulation\n", + "n_samples = 5000\n", + "n_noise_dims = 20\n", + "alpha = 0.001\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": [ + "## Setup 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": 40, + "id": "3cc73c4e-78b6-48b3-83b3-091d365d8ada", + "metadata": {}, + "outputs": [], + "source": [ + "# generate helix data\n", + "helix_data = simulate_helix(\n", + " radius_a=radius_a,\n", + " radius_b=radius_b,\n", + " alpha=alpha / 2,\n", + " n_samples=n_samples,\n", + " return_mi_lb=True,\n", + " random_seed=seed,\n", + ")\n", + "P, X, Y, Z, helix_lb = helix_data\n", + "\n", + "# generate sphere data\n", + "sphere_data = simulate_sphere(\n", + " radius=radius, alpha=alpha, n_samples=n_samples, return_mi_lb=True, random_seed=seed\n", + ")\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": 41, + "id": "aa1a67bb-55fa-4983-b2d1-a23102945b7c", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "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()" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "a841ffc8-e821-4c0a-8df0-9b55b1be3aba", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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dHV10dzczPbt2732++EPf8jGjRu56KKLuOCCC9T8qv48++yz3HPPPciyTEFBAZMmTaKqqoqf//znrF27lk8//XREBZ0nTZrE5MmTfb432FhGimbuO8/outEmoemtqoPdbmfPnj1qMmx+fj7z588fNLpuqCrh4eC662L58kt9JOoK9IyELMNLL8XxyisxlJX1jRk3XqjKDBkMhgFBE9u2bWPOnDkcOHBAnRNqa2t5/vnnef7551m0aBG7du0acKxdu3Zx7733AvDHP/6R2267DUmSaGhoYOXKlezatYvbbruNtWvXBjzO73znO/zsZz8bzan6jSYzkSJEyt9o0ZP7rquri88//5zmZnc7gLlz51JcXDxkuLeW7rtdu6CuLuwfK4hwzGYDl1ySyMaN+o52DQZKZDCEvsyQ0Wjk7LPPZvv27Tz00EPq3FBYWKhuc+GFF/rc9/HHH8flcnHTTTdx++23q56nCRMm8M9//hODwcC6deuorKwM6TmMFs0spWD2PdKDKMmyTG1tLfv370eWZRITEykpKSE9PX3YfbVy3+3aBRdeKFpPRCehLwdVW2vkG99IpKoquhNsZfnrDr3hLP3lKYYfffQRkiSxefNmSktLB2xrMpnUtZ3bb799wPszZszgwgsv5IMPPuD1119n4cKFoR38KNA8+i4YaC1KDoeDvXv30tjYCEBeXh4LFizwO2FOC0tJqdYgBCma6C9EoRcml0viqqsSWLcueiPyPO/LcOZB9o++y87O5tvf/rbPbcvKyrBarcTHx3Paaaf53Oacc87hgw8+4Isvvgh4LFu3bmXfvn20tbWRkZHBKaecwo033uhlwQULTS2lYKGlKPX09FBWVkZfXx+SJDFr1iwKCwsDOj8tROnii0W1huhDGuT/Q0tVlTsi7733zGH7zHDiOa+Es4yZ0nUWho++O3ToEACTJ08e9GF42rRpABw8eDDgsXz88cde/37jjTf4r//6L37+85/z4IMPBny8oYiK1W2tirLW1dWxbds2+vr6SEhI4LTTTqOoqGjEIe3hEqVdu8AcnfOHTolOC+JrZG66ya71IEKG53pSOEVJSZ41Go3ExcUNuW17ezvAkJF1yntKeTN/mDFjBk899RRlZWV0dHTQ19fHF198wbXXXovdbudHP/oRzz//vN/H84eoct+Fa1J3Op1UVVWpJURycnJYuHDh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ZrOpPaHefvtttSeUJ9OmTVN7QpWWliJJEp999hkbN27k7LPPJiYmhkWLFpGenq7BGfhm+/btXHnllapF+Pjjj/PTn/5U41EJxjpClASCAHA4HHz00UesWbOGN99802dPqMLCQk4//XTWr1+P2WzmBz/4AQ899JCuBGn37t0sX75crT4u+gcJ9EJ0d1ATDMrWrVtZvnw5ubm5JCYmMnv2bB555BF6e3u1HpquiYmJ4aKLLuL3v/89dXV1fPTRR9x9990UFBSo2xw/fpw1a9aoDft2797Nnj17BuRJaUVlZSUrVqxQBemBBx7gscce03hUAoEbYSmNQZ599lnuueceZFmmoKCA3NxctXT/nDlz+PTTT0dcf22s4nK52L59O//v//0/XnvtNbVyd2ZmptpqIi8vT21aeM4552jSE2rv3r1cfvnltLW1AXD33XfzzDPPRGULekFkIkRpjLFr1y5OO+00ZFn2anLW0NDAypUr2bVrF1dddRVr167VeqgRxyeffMKyZctUa/Oss86iqamJ6urqAdtmZ2erPaHOP//8sPSEOnDgAMuWLaO5uRmA73//+/zud78TgiTQFUKUxhirVq3irbfeGrYddEVFBQsXLtRolJHJF198waWXXkpPTw+PPvooP/vZz5BlmT179vD666+zbt26QXtCXXHFFaxatYqLLrooJD2hqqurWbp0qboGduutt/LCCy+MuJ2FQBAqhCiNIUwmEzk5OVitVj799FOfXUMvueQSPvjgA7HwPUK2bdvGRx99xI9//GOf71dVVbFmzRrWrVtHRUXFgPdTU1PVnlCXXnppUHpCHT16lMsuu4y6ujrAnRj70ksvCUES6BIhSmOITz75hHPPPZf4+Hh6enqIjY0dsM1jjz3Go48+ysUXX8ymTZs0GOXY4fDhw2pPqC+//HLA+0lJSSxdulTtCTWSjqgnTpzgsssu4/jx4wB885vf5O9//7vf/ZQEgnAjHpXGEIcOHQJg8uTJPgUJ3Pk2AAcPHgzbuMYqM2bM4Mc//jE7d+7k2LFjPPXUUyxZskR9v6+vjzfeeINbbrmFwsJCvvGNb/DPf/5TjZobjvr6ei6//HJVkK666ipefvllIUgCXSNEaQzR3t4OMGRknfKeEjEmCA+FhYXcd999fP7559TV1fHb3/6Wc889V3WxWSwW3n33Xb773e9SWFjI6tWr+dvf/qZG0fWnsbGRK664gqNHjwKwYsUKXnnlFU0i/gSCQBCiNIZQapsNFekVHx8PoObYCMLPxIkT+eEPf8hHH31EQ0MDzz//PBdffLFq4djtdjZu3Mj3v/99pkyZwvLly/nzn/+sRtU1NzezfPlyDh8+DMBll13G66+/Pqh1LBDoCSFKYwglqqt/zxxPlCrRiYmJYRmTYGjGjRvH97//fTZt2kRTUxN/+tOfWLZsmSowTqeTrVu3cs899zBt2jQuuugizjzzTPbv3w/ARRddxLp169SHDYFA7whRGkMozeQUN54vlPe0ajwnGJzs7Gy++93v8q9//Yvm5mb+93//l5UrV6qC43K5+OKLLzh58iTgzpN6++23xQOGIKIQojSGmDlzJuCOyOrfeVXhyJEjXtsK9ElGRgY333wzb731Fi0tLfzjH//gyiuvVNegMjIy2LBhQ1BCygWCcCJEaQxRWlpKXFwcVquVHTt2+Nzmk08+AfCKAhPom9TUVK6//nrefPNNmpqaWLFiBS+//PKIQsgFAq0ReUpjjJUrV7J+/fphKzqUl5dTXFys0SgFAsFYRVhKY4xHHnkESZJ4+eWXeeGFF9TCoSdPnuT666/H5XKxatUqIUiCoNPZ2clrr73GAw88wPnnn09qaiqSJFFUVKT10AQ6QlhKY5BnnnmG++67D1mWmTRpEjk5OWqV8FmzZvHpp5+Sk5Oj9TAFUcabb77J6tWrB7xeWFjIsWPHwj8ggS4RmXRjkHvvvZcFCxbw1FNPsX37dpqbmyksLOSaa67hJz/5iViLEISExMREzj33XBYvXsypp55Ke3s7d911l9bDEugMYSkJBAJNeOedd1ixYoWwlAReiDUlgUAgEOgGIUoCgUAg0A1ClAQCgUCgG4QoCQQCgUA3CFESCAQCgW4QoiQQCIbk3nvvRZKkgP/OP/98rYcuiEBEnpJAIBiSlJQUsrOzA94vPT09BKMRRDsiT0kgGILGxkY2bdrEzp072blzJ+Xl5VgsFs477zw+/PBDrYcX0Yg8JYEvhKUkEAzBK6+8wr//+79rPQyBYMwgREkgGIK0tDQuvvhiFi9ezOLFiykrK+Pxxx/XelgCQdQiREkgGILvfOc7fOc731H/XV9fr+FoBILoR4iSQCAIG57V55Xux7W1tV6vX3/99Tz77LNhH5tAHwhREggEYaOtrW3Aay6Xy+v1np6ecA5JoDOEKAkEgrAhgn0FwyGSZwUCgUCgG4QoCQQCgUA3CFESCAQCgW4QoiQQCAQC3SBESSAQCAS6QYiSQCAQCHSDECWBQCAQ6AYhSgKBQCDQDaJ1hUAwBLW1tZSWlqr/tlgs9Pb2EhMT49Uv6MEHH+TBBx/UYogCQVQhKjoIBEPgdDp9lsZxOBxer/f19YVzWAJB1CIsJYFAIBDoBrGmJBAIBALdIERJIBAIBLpBiJJAIBAIdIMQJYFAIBDoBiFKAoFAINANQpQEAoFAoBuEKAkEAoFANwhREggEAoFuEKIkEAgEAt0gREkgEAgEukGIkkAgEAh0gxAlgUAgEOgGIUoCgUAg0A1ClAQCgUCgG/4/p/Qh7Qn9dnQAAAAASUVORK5CYII=", + "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()" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "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": 44, + "id": "570cf521-f62f-4e62-98e4-0aea8e4f72db", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "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()" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "438211c0-228c-453d-aaec-02813988177f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(5000, 3)\n" + ] + } + ], + "source": [ + "# now we will embed in high-dimensions\n", + "data, mean, cov = mvg_data\n", + "print(data.shape)\n", + "X = embed_high_dims(data, n_dims=50, random_state=seed)" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "ff5c92e5-65e6-4990-9287-708e4af32450", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(5000, 21) (5000, 3)\n" + ] + } + ], + "source": [ + "X = embed_high_dims(data[:, [0]], n_dims=n_noise_dims, random_state=seed)\n", + "Y = embed_high_dims(data[:, [1]], n_dims=n_noise_dims, random_state=seed)\n", + "Z = embed_high_dims(data[:, [2]], n_dims=n_noise_dims, random_state=seed)\n", + "highdim_data = np.hstack((X, Y, Z))\n", + "\n", + "print(X.shape, data.shape)" + ] + }, + { + "cell_type": "markdown", + "id": "2e7eb0d5-75f9-4934-af65-1985bb395b38", + "metadata": {}, + "source": [ + "### Unsupervised KSG approach" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "cc2209c6-3562-44e2-95d9-8343e24b5e28", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(5000, 21) (5000, 21) (5000, 42)\n" + ] + } + ], + "source": [ + "# define the forest-based estimator to determine distances\n", + "clf = UnsupervisedObliqueRandomForest(\n", + " n_estimators=n_estimators,\n", + " criterion='fasterbic',\n", + " feature_combinations=feature_combinations,\n", + " min_samples_split=int(np.sqrt(2 * n_samples)),\n", + " n_jobs=n_jobs,\n", + " random_state=seed,\n", + ")\n", + "\n", + "# meta-estimator for nearest-neighbor lookup\n", + "est = NearestNeighborsMetaEstimator(\n", + " estimator=clf,\n", + " n_neighbors=n_nbrs,\n", + " algorithm=\"auto\",\n", + " n_jobs=n_jobs,\n", + " force_fit=True,\n", + " verbose=True,\n", + ")\n", + "\n", + "data = np.hstack((X, Y))\n", + "print(X.shape, Y.shape, data.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "ff401a6b-bd8b-479c-a36c-e198e1dd450e", + "metadata": {}, + "outputs": [ + { + "ename": "KeyboardInterrupt", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[48], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mclf\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/ensemble/_unsupervised_forest.py:156\u001b[0m, in \u001b[0;36mForestCluster.fit\u001b[0;34m(self, X, y, sample_weight)\u001b[0m\n\u001b[1;32m 145\u001b[0m trees \u001b[38;5;241m=\u001b[39m [\n\u001b[1;32m 146\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_make_estimator(append\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m, random_state\u001b[38;5;241m=\u001b[39mrandom_state)\n\u001b[1;32m 147\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(n_more_estimators)\n\u001b[1;32m 148\u001b[0m ]\n\u001b[1;32m 150\u001b[0m \u001b[38;5;66;03m# Parallel loop: we prefer the threading backend as the Cython code\u001b[39;00m\n\u001b[1;32m 151\u001b[0m \u001b[38;5;66;03m# for fitting the trees is internally releasing the Python GIL\u001b[39;00m\n\u001b[1;32m 152\u001b[0m \u001b[38;5;66;03m# making threading more efficient than multiprocessing in\u001b[39;00m\n\u001b[1;32m 153\u001b[0m \u001b[38;5;66;03m# that case. However, for joblib 0.12+ we respect any\u001b[39;00m\n\u001b[1;32m 154\u001b[0m \u001b[38;5;66;03m# parallel_backend contexts set at a higher level,\u001b[39;00m\n\u001b[1;32m 155\u001b[0m \u001b[38;5;66;03m# since correctness does not rely on using threads.\u001b[39;00m\n\u001b[0;32m--> 156\u001b[0m trees \u001b[38;5;241m=\u001b[39m \u001b[43mParallel\u001b[49m\u001b[43m(\u001b[49m\u001b[43mn_jobs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mn_jobs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mverbose\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mverbose\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mprefer\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mthreads\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m)\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 157\u001b[0m \u001b[43m \u001b[49m\u001b[43mdelayed\u001b[49m\u001b[43m(\u001b[49m\u001b[43m_parallel_build_trees\u001b[49m\u001b[43m)\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 158\u001b[0m \u001b[43m \u001b[49m\u001b[43mt\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 159\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbootstrap\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 160\u001b[0m \u001b[43m \u001b[49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 161\u001b[0m \u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 162\u001b[0m \u001b[43m \u001b[49m\u001b[43msample_weight\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 163\u001b[0m \u001b[43m \u001b[49m\u001b[43mi\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 164\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mlen\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mtrees\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 165\u001b[0m \u001b[43m \u001b[49m\u001b[43mverbose\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mverbose\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 166\u001b[0m \u001b[43m \u001b[49m\u001b[43mclass_weight\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mclass_weight\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 167\u001b[0m \u001b[43m \u001b[49m\u001b[43mn_samples_bootstrap\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mn_samples_bootstrap\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 168\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 169\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mi\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mt\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43menumerate\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mtrees\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 170\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 172\u001b[0m \u001b[38;5;66;03m# Collect newly grown trees\u001b[39;00m\n\u001b[1;32m 173\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mestimators_\u001b[38;5;241m.\u001b[39mextend(trees)\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/sklearn/utils/parallel.py:65\u001b[0m, in \u001b[0;36mParallel.__call__\u001b[0;34m(self, iterable)\u001b[0m\n\u001b[1;32m 60\u001b[0m config \u001b[38;5;241m=\u001b[39m get_config()\n\u001b[1;32m 61\u001b[0m iterable_with_config \u001b[38;5;241m=\u001b[39m (\n\u001b[1;32m 62\u001b[0m (_with_config(delayed_func, config), args, kwargs)\n\u001b[1;32m 63\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m delayed_func, args, kwargs \u001b[38;5;129;01min\u001b[39;00m iterable\n\u001b[1;32m 64\u001b[0m )\n\u001b[0;32m---> 65\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[38;5;21;43m__call__\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43miterable_with_config\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/joblib/parallel.py:1952\u001b[0m, in \u001b[0;36mParallel.__call__\u001b[0;34m(self, iterable)\u001b[0m\n\u001b[1;32m 1946\u001b[0m \u001b[38;5;66;03m# The first item from the output is blank, but it makes the interpreter\u001b[39;00m\n\u001b[1;32m 1947\u001b[0m \u001b[38;5;66;03m# progress until it enters the Try/Except block of the generator and\u001b[39;00m\n\u001b[1;32m 1948\u001b[0m \u001b[38;5;66;03m# reach the first `yield` statement. This starts the aynchronous\u001b[39;00m\n\u001b[1;32m 1949\u001b[0m \u001b[38;5;66;03m# dispatch of the tasks to the workers.\u001b[39;00m\n\u001b[1;32m 1950\u001b[0m \u001b[38;5;28mnext\u001b[39m(output)\n\u001b[0;32m-> 1952\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m output \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mreturn_generator \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;43mlist\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43moutput\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/joblib/parallel.py:1595\u001b[0m, in \u001b[0;36mParallel._get_outputs\u001b[0;34m(self, iterator, pre_dispatch)\u001b[0m\n\u001b[1;32m 1592\u001b[0m \u001b[38;5;28;01myield\u001b[39;00m\n\u001b[1;32m 1594\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backend\u001b[38;5;241m.\u001b[39mretrieval_context():\n\u001b[0;32m-> 1595\u001b[0m \u001b[38;5;28;01myield from\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_retrieve()\n\u001b[1;32m 1597\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mGeneratorExit\u001b[39;00m:\n\u001b[1;32m 1598\u001b[0m \u001b[38;5;66;03m# The generator has been garbage collected before being fully\u001b[39;00m\n\u001b[1;32m 1599\u001b[0m \u001b[38;5;66;03m# consumed. This aborts the remaining tasks if possible and warn\u001b[39;00m\n\u001b[1;32m 1600\u001b[0m \u001b[38;5;66;03m# the user if necessary.\u001b[39;00m\n\u001b[1;32m 1601\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_exception \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/joblib/parallel.py:1707\u001b[0m, in \u001b[0;36mParallel._retrieve\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1702\u001b[0m \u001b[38;5;66;03m# If the next job is not ready for retrieval yet, we just wait for\u001b[39;00m\n\u001b[1;32m 1703\u001b[0m \u001b[38;5;66;03m# async callbacks to progress.\u001b[39;00m\n\u001b[1;32m 1704\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ((\u001b[38;5;28mlen\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_jobs) \u001b[38;5;241m==\u001b[39m \u001b[38;5;241m0\u001b[39m) \u001b[38;5;129;01mor\u001b[39;00m\n\u001b[1;32m 1705\u001b[0m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_jobs[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mget_status(\n\u001b[1;32m 1706\u001b[0m timeout\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtimeout) \u001b[38;5;241m==\u001b[39m TASK_PENDING)):\n\u001b[0;32m-> 1707\u001b[0m \u001b[43mtime\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msleep\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.01\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1708\u001b[0m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[1;32m 1710\u001b[0m \u001b[38;5;66;03m# We need to be careful: the job list can be filling up as\u001b[39;00m\n\u001b[1;32m 1711\u001b[0m \u001b[38;5;66;03m# we empty it and Python list are not thread-safe by\u001b[39;00m\n\u001b[1;32m 1712\u001b[0m \u001b[38;5;66;03m# default hence the use of the lock\u001b[39;00m\n", + "\u001b[0;31mKeyboardInterrupt\u001b[0m: " + ] + } + ], + "source": [ + "clf.fit(data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a7f17e6c-ed4a-4581-a76a-58a5a0972a73", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "aadc7bb0-40e5-4e5c-9e99-f810d570b19f", + "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" + ] + } + ], + "source": [ + "print(cov)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "d49a1ad6-55f3-42aa-b68c-26b009970ad7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(1000, 21) (1000, 21) (1000, 42)\n" + ] + } + ], + "source": [ + "print(X.shape, Y.shape, data.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "3bded813-31ea-44ba-bf77-973b3338f0fc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.8879202209746868\n", + "0.09749224201325823\n" + ] + } + ], + "source": [ + "# true MI and CMI\n", + "true_mi = mi_gaussian(cov, 1, 2)\n", + "true_cmi = cmi_gaussian(cov, 0, 1, 2)\n", + "print(true_mi)\n", + "print(true_cmi)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "c051b1e1-4262-49c0-8383-74565b192a6b", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Data shape: (10000, 22)\n", + "X shape: (10000, 11), Y shape: (10000, 11)\n", + "Preprocessing complete.\n", + "Using 2000 neighbors to define D-dimensional volume.\n", + "(10000, 22) [ 0 1 2 3 4 5 6 7 8 9 10] [11 12 13 14 15 16 17 18 19 20 21] []\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "KeyboardInterrupt\n", + "\n" + ] + } + ], + "source": [ + "geo_ksg_mi = mutual_info_ksg(X, Y, nn_estimator=est, n_jobs=n_jobs, k=0.2, verbose=True)\n", + "print(geo_ksg_mi)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "162653a5-d2f9-4e17-91fa-04be2c8515d7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "print(geo_ksg_mi)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "5a56b255-4f29-4ce7-a0ee-616063c10ad5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.3430269602006017\n" + ] + } + ], + "source": [ + "ksg_mi = mutual_info_ksg_nn(X, Y, n_jobs=n_jobs, k=0.2, verbose=False)\n", + "print(ksg_mi)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "21a59855-6d48-490e-b746-231871281d9e", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Data shape: (10000, 22)\n", + "X shape: (10000, 11), Y shape: (10000, 11)\n", + "Preprocessing complete.\n", + "Using 2000 neighbors to define D-dimensional volume.\n", + "(10000, 22) [ 0 1 2 3 4 5 6 7 8 9 10] [11 12 13 14 15 16 17 18 19 20 21] []\n" + ] + }, + { + "ename": "KeyboardInterrupt", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[32], line 4\u001b[0m\n\u001b[1;32m 1\u001b[0m nn_estimator \u001b[38;5;241m=\u001b[39m NearestNeighbors(\n\u001b[1;32m 2\u001b[0m n_neighbors\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m5\u001b[39m, algorithm\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mkd_tree\u001b[39m\u001b[38;5;124m\"\u001b[39m, p\u001b[38;5;241m=\u001b[39mnp\u001b[38;5;241m.\u001b[39minf, n_jobs\u001b[38;5;241m=\u001b[39mn_jobs\n\u001b[1;32m 3\u001b[0m )\n\u001b[0;32m----> 4\u001b[0m ksg_mi \u001b[38;5;241m=\u001b[39m \u001b[43mmutual_info_ksg\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 5\u001b[0m \u001b[43m \u001b[49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mY\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnn_estimator\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mnn_estimator\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_jobs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mn_jobs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mk\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m0.2\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mverbose\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\n\u001b[1;32m 6\u001b[0m \u001b[43m)\u001b[49m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;28mprint\u001b[39m(ksg_mi)\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/experimental/ksg.py:259\u001b[0m, in \u001b[0;36mmutual_info_ksg\u001b[0;34m(X, Y, Z, k, norm, nn_estimator, n_jobs, transform, random_seed, verbose)\u001b[0m\n\u001b[1;32m 257\u001b[0m val \u001b[38;5;241m=\u001b[39m _cmi_ksg(data, x_idx, y_idx, z_idx, nn_estimator, knn_here)\n\u001b[1;32m 258\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 259\u001b[0m val \u001b[38;5;241m=\u001b[39m \u001b[43m_mi_ksg\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mx_idx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my_idx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnn_estimator\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mknn_here\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 261\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m norm \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmax\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m 262\u001b[0m norm_constant \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mlog(\u001b[38;5;241m1\u001b[39m)\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/experimental/ksg.py:396\u001b[0m, in \u001b[0;36m_mi_ksg\u001b[0;34m(data, x_idx, y_idx, nn_estimator, knn_here)\u001b[0m\n\u001b[1;32m 393\u001b[0m radius_per_sample \u001b[38;5;241m=\u001b[39m dists[:, \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m]\n\u001b[1;32m 395\u001b[0m \u001b[38;5;66;03m# compute on the subspace of X\u001b[39;00m\n\u001b[0;32m--> 396\u001b[0m num_nn_x \u001b[38;5;241m=\u001b[39m \u001b[43m_compute_radius_nbrs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mradius_per_sample\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnn_estimator\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcol_idx\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mx_idx\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 398\u001b[0m \u001b[38;5;66;03m# compute on the subspace of Y\u001b[39;00m\n\u001b[1;32m 399\u001b[0m num_nn_y \u001b[38;5;241m=\u001b[39m _compute_radius_nbrs(data, radius_per_sample, nn_estimator, col_idx\u001b[38;5;241m=\u001b[39my_idx)\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/experimental/ksg.py:497\u001b[0m, in \u001b[0;36m_compute_radius_nbrs\u001b[0;34m(data, radius_per_sample, nn_estimator, col_idx)\u001b[0m\n\u001b[1;32m 495\u001b[0m nn \u001b[38;5;241m=\u001b[39m []\n\u001b[1;32m 496\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m idx, radius \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(radius_per_sample):\n\u001b[0;32m--> 497\u001b[0m nn_ \u001b[38;5;241m=\u001b[39m \u001b[43mnn_estimator\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mradius_neighbors\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 498\u001b[0m \u001b[43m \u001b[49m\u001b[43mX\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43matleast_2d\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m[\u001b[49m\u001b[43m:\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcol_idx\u001b[49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mradius\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mradius\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mreturn_distance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\n\u001b[1;32m 499\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 500\u001b[0m nn\u001b[38;5;241m.\u001b[39mappend(nn_[idx])\n\u001b[1;32m 502\u001b[0m num_nn_data \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39marray([\u001b[38;5;28mlen\u001b[39m(nn_) \u001b[38;5;28;01mfor\u001b[39;00m nn_ \u001b[38;5;129;01min\u001b[39;00m nn])\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/sklearn/neighbors/_base.py:1235\u001b[0m, in \u001b[0;36mRadiusNeighborsMixin.radius_neighbors\u001b[0;34m(self, X, radius, return_distance, sort_results)\u001b[0m\n\u001b[1;32m 1233\u001b[0m n_jobs \u001b[38;5;241m=\u001b[39m effective_n_jobs(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mn_jobs)\n\u001b[1;32m 1234\u001b[0m delayed_query \u001b[38;5;241m=\u001b[39m delayed(_tree_query_radius_parallel_helper)\n\u001b[0;32m-> 1235\u001b[0m chunked_results \u001b[38;5;241m=\u001b[39m \u001b[43mParallel\u001b[49m\u001b[43m(\u001b[49m\u001b[43mn_jobs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mprefer\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mthreads\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1236\u001b[0m \u001b[43m \u001b[49m\u001b[43mdelayed_query\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1237\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_tree\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mX\u001b[49m\u001b[43m[\u001b[49m\u001b[43ms\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mradius\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mreturn_distance\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msort_results\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msort_results\u001b[49m\n\u001b[1;32m 1238\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1239\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43ms\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mgen_even_slices\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mshape\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_jobs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1240\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1241\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m return_distance:\n\u001b[1;32m 1242\u001b[0m neigh_ind, neigh_dist \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mtuple\u001b[39m(\u001b[38;5;28mzip\u001b[39m(\u001b[38;5;241m*\u001b[39mchunked_results))\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/sklearn/utils/parallel.py:65\u001b[0m, in \u001b[0;36mParallel.__call__\u001b[0;34m(self, iterable)\u001b[0m\n\u001b[1;32m 60\u001b[0m config \u001b[38;5;241m=\u001b[39m get_config()\n\u001b[1;32m 61\u001b[0m iterable_with_config \u001b[38;5;241m=\u001b[39m (\n\u001b[1;32m 62\u001b[0m (_with_config(delayed_func, config), args, kwargs)\n\u001b[1;32m 63\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m delayed_func, args, kwargs \u001b[38;5;129;01min\u001b[39;00m iterable\n\u001b[1;32m 64\u001b[0m )\n\u001b[0;32m---> 65\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[38;5;21;43m__call__\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43miterable_with_config\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/joblib/parallel.py:1952\u001b[0m, in \u001b[0;36mParallel.__call__\u001b[0;34m(self, iterable)\u001b[0m\n\u001b[1;32m 1946\u001b[0m \u001b[38;5;66;03m# The first item from the output is blank, but it makes the interpreter\u001b[39;00m\n\u001b[1;32m 1947\u001b[0m \u001b[38;5;66;03m# progress until it enters the Try/Except block of the generator and\u001b[39;00m\n\u001b[1;32m 1948\u001b[0m \u001b[38;5;66;03m# reach the first `yield` statement. This starts the aynchronous\u001b[39;00m\n\u001b[1;32m 1949\u001b[0m \u001b[38;5;66;03m# dispatch of the tasks to the workers.\u001b[39;00m\n\u001b[1;32m 1950\u001b[0m \u001b[38;5;28mnext\u001b[39m(output)\n\u001b[0;32m-> 1952\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m output \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mreturn_generator \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;43mlist\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43moutput\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/joblib/parallel.py:1595\u001b[0m, in \u001b[0;36mParallel._get_outputs\u001b[0;34m(self, iterator, pre_dispatch)\u001b[0m\n\u001b[1;32m 1592\u001b[0m \u001b[38;5;28;01myield\u001b[39;00m\n\u001b[1;32m 1594\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backend\u001b[38;5;241m.\u001b[39mretrieval_context():\n\u001b[0;32m-> 1595\u001b[0m \u001b[38;5;28;01myield from\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_retrieve()\n\u001b[1;32m 1597\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mGeneratorExit\u001b[39;00m:\n\u001b[1;32m 1598\u001b[0m \u001b[38;5;66;03m# The generator has been garbage collected before being fully\u001b[39;00m\n\u001b[1;32m 1599\u001b[0m \u001b[38;5;66;03m# consumed. This aborts the remaining tasks if possible and warn\u001b[39;00m\n\u001b[1;32m 1600\u001b[0m \u001b[38;5;66;03m# the user if necessary.\u001b[39;00m\n\u001b[1;32m 1601\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_exception \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/joblib/parallel.py:1707\u001b[0m, in \u001b[0;36mParallel._retrieve\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1702\u001b[0m \u001b[38;5;66;03m# If the next job is not ready for retrieval yet, we just wait for\u001b[39;00m\n\u001b[1;32m 1703\u001b[0m \u001b[38;5;66;03m# async callbacks to progress.\u001b[39;00m\n\u001b[1;32m 1704\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ((\u001b[38;5;28mlen\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_jobs) \u001b[38;5;241m==\u001b[39m \u001b[38;5;241m0\u001b[39m) \u001b[38;5;129;01mor\u001b[39;00m\n\u001b[1;32m 1705\u001b[0m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_jobs[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mget_status(\n\u001b[1;32m 1706\u001b[0m timeout\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtimeout) \u001b[38;5;241m==\u001b[39m TASK_PENDING)):\n\u001b[0;32m-> 1707\u001b[0m \u001b[43mtime\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msleep\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.01\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1708\u001b[0m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[1;32m 1710\u001b[0m \u001b[38;5;66;03m# We need to be careful: the job list can be filling up as\u001b[39;00m\n\u001b[1;32m 1711\u001b[0m \u001b[38;5;66;03m# we empty it and Python list are not thread-safe by\u001b[39;00m\n\u001b[1;32m 1712\u001b[0m \u001b[38;5;66;03m# default hence the use of the lock\u001b[39;00m\n", + "\u001b[0;31mKeyboardInterrupt\u001b[0m: " + ] + } + ], + "source": [ + "nn_estimator = NearestNeighbors(\n", + " n_neighbors=5, algorithm=\"kd_tree\", p=np.inf, n_jobs=n_jobs\n", + ")\n", + "ksg_mi = mutual_info_ksg(\n", + " X, Y, nn_estimator=nn_estimator, n_jobs=n_jobs, k=0.2, verbose=True\n", + ")\n", + "print(ksg_mi)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "5bbc83e4-a7d1-4639-82b8-f4efe621c7ec", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(10000, 22)\n" + ] + } + ], + "source": [ + "print(data.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "5f2aea06-d2d1-4320-a3fd-3aecca36fb14", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'nn_estimator' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[27], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mnn_estimator\u001b[49m\u001b[38;5;241m.\u001b[39mfit(data)\n\u001b[1;32m 3\u001b[0m nn \u001b[38;5;241m=\u001b[39m []\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m idx, radius \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(radius_per_sample):\n", + "\u001b[0;31mNameError\u001b[0m: name 'nn_estimator' is not defined" + ] + } + ], + "source": [ + "nn_estimator.fit(data)\n", + "\n", + "nn = []\n", + "for idx, radius in enumerate(radius_per_sample):\n", + " nn_ = nn_estimator.radius_neighbors(\n", + " X=np.atleast_2d(data), radius=radius, return_distance=False\n", + " )\n", + " nn.append(nn_[idx])\n", + "print(np.array([len(nn_) for nn_ in nn]))" + ] + }, + { + "cell_type": "markdown", + "id": "9dae4346-5af2-4435-ad9d-a7dab1681d22", + "metadata": {}, + "source": [ + "### Supervised entropy estimate approach\n", + "\n", + "This is tabled until we get \"proba\" estimates for regression trees." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "77cc2540-819a-4fdd-b426-29c377490195", + "metadata": {}, + "outputs": [], + "source": [ + "estimator = ObliqueRandomForestRegressor(n_estimators=n_estimators, n_jobs=n_jobs)\n", + "sup_cmi_est = SupervisedInfoForest(estimator=estimator, y_categorical=False, n_jobs=-1)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "836b2183-9d0e-4454-ae8b-733b3419a659", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/adam2392/miniforge3/envs/sktree/lib/python3.9/site-packages/sklearn/utils/validation.py:1189: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().\n", + " y = column_or_1d(y, warn=True)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(1000, 2) (1000,)\n" + ] + }, + { + "data": { + "text/html": [ + "
SupervisedInfoForest(estimator=ObliqueRandomForestRegressor(n_jobs=-1),\n",
+       "                     n_jobs=-1)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + ], + "text/plain": [ + "SupervisedInfoForest(estimator=ObliqueRandomForestRegressor(n_jobs=-1),\n", + " n_jobs=-1)" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y = Y[:, [0]]\n", + "\n", + "sup_cmi_est.fit(X, y)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "ea70bc36-0bb5-4f96-bcd4-23763c88bb86", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'ObliqueRandomForestRegressor' object has no attribute 'predict_proba'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/6_/sl83qtkd68x3_mvfys07_6qm0000gn/T/ipykernel_32459/3154728669.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msup_cmi_est\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpredict_cmi\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/Documents/scikit-tree/sktree/experimental/forest.py\u001b[0m in \u001b[0;36mpredict_cmi\u001b[0;34m(self, X, Z)\u001b[0m\n\u001b[1;32m 52\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 53\u001b[0m \u001b[0;31m# compute the entropy of H(Y | X, Z)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 54\u001b[0;31m \u001b[0mH_yxz\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mestimator_yxz_\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpredict_proba\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mXZ\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 55\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 56\u001b[0m \u001b[0;31m# compute the entropy of H(Y | Z)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mAttributeError\u001b[0m: 'ObliqueRandomForestRegressor' object has no attribute 'predict_proba'" + ] + } + ], + "source": [ + "print(sup_cmi_est.predict_cmi(X))" + ] + }, + { + "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": 23, + "id": "0a875b34-9a97-4bdf-ab45-14a1be6e60a5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[10. 20. 30. 40. 50.]\n" + ] + } + ], + "source": [ + "# fix the number of samples, but increase the number of noise dimensions\n", + "n_samples = 10000\n", + "\n", + "n_jobs = -1\n", + "n_estimators = 300\n", + "feature_combinations = 1.5\n", + "k_ratio = 0.2\n", + "\n", + "# semi-parametric\n", + "n_nbrs = 0.2\n", + "\n", + "# hyperparameters of the simulation\n", + "alpha = 0.01\n", + "\n", + "# dimensionality of mvg\n", + "d = 3\n", + "\n", + "n_repeats = 10\n", + "\n", + "noise_dims_grid = np.linspace(10, 50, 5)\n", + "print(noise_dims_grid)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "a21a0542-2557-4863-935a-697892700973", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.8879202209746868\n", + "0.9849302324167128\n" + ] + } + ], + "source": [ + "X = embed_high_dims(data[:, [0]], n_dims=n_noise_dims, random_state=seed)\n", + "Y = embed_high_dims(data[:, [1]], n_dims=n_noise_dims, random_state=seed)\n", + "Z = embed_high_dims(data[:, [2]], n_dims=n_noise_dims, random_state=seed)\n", + "highdim_data = np.hstack((X, Y, Z))\n", + "\n", + "# true MI and CMI\n", + "x_cov_idx = 0\n", + "y_cov_idx = 1\n", + "z_cov_idx = 2\n", + "true_mi = mi_gaussian(cov, y_cov_idx, z_cov_idx)\n", + "true_cmi = cmi_gaussian(cov, y_cov_idx, z_cov_idx, x_cov_idx)\n", + "print(true_mi)\n", + "print(true_cmi)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "e95a90ef-2a18-4de3-a0ad-af45a842cad1", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 0%| | 0/5 [28:33 55\u001b[0m geo_ksg_mi \u001b[38;5;241m=\u001b[39m \u001b[43mmutual_info_ksg\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 56\u001b[0m \u001b[43m \u001b[49m\u001b[43mY\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mZ\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnn_estimator\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mest\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnorm\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mmax\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_jobs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mn_jobs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mk\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mk_ratio\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mverbose\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\n\u001b[1;32m 57\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 59\u001b[0m \u001b[38;5;66;03m# regular KSG estimator using KD-Tree\u001b[39;00m\n\u001b[1;32m 60\u001b[0m ksg_mi \u001b[38;5;241m=\u001b[39m mutual_info_ksg_nn(\n\u001b[1;32m 61\u001b[0m Y, Z, norm\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmax\u001b[39m\u001b[38;5;124m\"\u001b[39m, n_jobs\u001b[38;5;241m=\u001b[39mn_jobs, k\u001b[38;5;241m=\u001b[39mk_ratio, verbose\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[1;32m 62\u001b[0m )\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/experimental/ksg.py:258\u001b[0m, in \u001b[0;36mmutual_info_ksg\u001b[0;34m(X, Y, Z, k, norm, nn_estimator, n_jobs, transform, random_seed, verbose)\u001b[0m\n\u001b[1;32m 256\u001b[0m val \u001b[38;5;241m=\u001b[39m _cmi_ksg(data, x_idx, y_idx, z_idx, nn_estimator, knn_here)\n\u001b[1;32m 257\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 258\u001b[0m val \u001b[38;5;241m=\u001b[39m \u001b[43m_mi_ksg\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mx_idx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my_idx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnn_estimator\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mknn_here\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 260\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m norm \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmax\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m 261\u001b[0m norm_constant \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mlog(\u001b[38;5;241m1\u001b[39m)\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/experimental/ksg.py:387\u001b[0m, in \u001b[0;36m_mi_ksg\u001b[0;34m(data, x_idx, y_idx, nn_estimator, knn_here)\u001b[0m\n\u001b[1;32m 384\u001b[0m n_samples \u001b[38;5;241m=\u001b[39m data\u001b[38;5;241m.\u001b[39mshape[\u001b[38;5;241m0\u001b[39m]\n\u001b[1;32m 386\u001b[0m \u001b[38;5;66;03m# estimate distance to the kth NN in XYZ subspace for each sample\u001b[39;00m\n\u001b[0;32m--> 387\u001b[0m neigh \u001b[38;5;241m=\u001b[39m \u001b[43mnn_estimator\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 388\u001b[0m dists, _ \u001b[38;5;241m=\u001b[39m neigh\u001b[38;5;241m.\u001b[39mkneighbors(n_neighbors\u001b[38;5;241m=\u001b[39mknn_here)\n\u001b[1;32m 390\u001b[0m \u001b[38;5;66;03m# - get the radius we want to use per sample as the distance to the kth neighbor\u001b[39;00m\n\u001b[1;32m 391\u001b[0m \u001b[38;5;66;03m# in the joint distribution space\u001b[39;00m\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/neighbors.py:99\u001b[0m, in \u001b[0;36mNearestNeighborsMetaEstimator.fit\u001b[0;34m(self, X, y)\u001b[0m\n\u001b[1;32m 97\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mestimator_ \u001b[38;5;241m=\u001b[39m copy(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mestimator)\n\u001b[1;32m 98\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mforce_fit:\n\u001b[0;32m---> 99\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mestimator_\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 100\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 101\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/ensemble/_unsupervised_forest.py:156\u001b[0m, in \u001b[0;36mForestCluster.fit\u001b[0;34m(self, X, y, sample_weight)\u001b[0m\n\u001b[1;32m 145\u001b[0m trees \u001b[38;5;241m=\u001b[39m [\n\u001b[1;32m 146\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_make_estimator(append\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m, random_state\u001b[38;5;241m=\u001b[39mrandom_state)\n\u001b[1;32m 147\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(n_more_estimators)\n\u001b[1;32m 148\u001b[0m ]\n\u001b[1;32m 150\u001b[0m \u001b[38;5;66;03m# Parallel loop: we prefer the threading backend as the Cython code\u001b[39;00m\n\u001b[1;32m 151\u001b[0m \u001b[38;5;66;03m# for fitting the trees is internally releasing the Python GIL\u001b[39;00m\n\u001b[1;32m 152\u001b[0m \u001b[38;5;66;03m# making threading more efficient than multiprocessing in\u001b[39;00m\n\u001b[1;32m 153\u001b[0m \u001b[38;5;66;03m# that case. However, for joblib 0.12+ we respect any\u001b[39;00m\n\u001b[1;32m 154\u001b[0m \u001b[38;5;66;03m# parallel_backend contexts set at a higher level,\u001b[39;00m\n\u001b[1;32m 155\u001b[0m \u001b[38;5;66;03m# since correctness does not rely on using threads.\u001b[39;00m\n\u001b[0;32m--> 156\u001b[0m trees \u001b[38;5;241m=\u001b[39m \u001b[43mParallel\u001b[49m\u001b[43m(\u001b[49m\u001b[43mn_jobs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mn_jobs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mverbose\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mverbose\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mprefer\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mthreads\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m)\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 157\u001b[0m \u001b[43m \u001b[49m\u001b[43mdelayed\u001b[49m\u001b[43m(\u001b[49m\u001b[43m_parallel_build_trees\u001b[49m\u001b[43m)\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 158\u001b[0m \u001b[43m \u001b[49m\u001b[43mt\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 159\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbootstrap\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 160\u001b[0m \u001b[43m \u001b[49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 161\u001b[0m \u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 162\u001b[0m \u001b[43m \u001b[49m\u001b[43msample_weight\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 163\u001b[0m \u001b[43m \u001b[49m\u001b[43mi\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 164\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mlen\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mtrees\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 165\u001b[0m \u001b[43m \u001b[49m\u001b[43mverbose\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mverbose\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 166\u001b[0m \u001b[43m \u001b[49m\u001b[43mclass_weight\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mclass_weight\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 167\u001b[0m \u001b[43m \u001b[49m\u001b[43mn_samples_bootstrap\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mn_samples_bootstrap\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 168\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 169\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mi\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mt\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43menumerate\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mtrees\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 170\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 172\u001b[0m \u001b[38;5;66;03m# Collect newly grown trees\u001b[39;00m\n\u001b[1;32m 173\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mestimators_\u001b[38;5;241m.\u001b[39mextend(trees)\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/sklearn/utils/parallel.py:65\u001b[0m, in \u001b[0;36mParallel.__call__\u001b[0;34m(self, iterable)\u001b[0m\n\u001b[1;32m 60\u001b[0m config \u001b[38;5;241m=\u001b[39m get_config()\n\u001b[1;32m 61\u001b[0m iterable_with_config \u001b[38;5;241m=\u001b[39m (\n\u001b[1;32m 62\u001b[0m (_with_config(delayed_func, config), args, kwargs)\n\u001b[1;32m 63\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m delayed_func, args, kwargs \u001b[38;5;129;01min\u001b[39;00m iterable\n\u001b[1;32m 64\u001b[0m )\n\u001b[0;32m---> 65\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[38;5;21;43m__call__\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43miterable_with_config\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/joblib/parallel.py:1952\u001b[0m, in \u001b[0;36mParallel.__call__\u001b[0;34m(self, iterable)\u001b[0m\n\u001b[1;32m 1946\u001b[0m \u001b[38;5;66;03m# The first item from the output is blank, but it makes the interpreter\u001b[39;00m\n\u001b[1;32m 1947\u001b[0m \u001b[38;5;66;03m# progress until it enters the Try/Except block of the generator and\u001b[39;00m\n\u001b[1;32m 1948\u001b[0m \u001b[38;5;66;03m# reach the first `yield` statement. This starts the aynchronous\u001b[39;00m\n\u001b[1;32m 1949\u001b[0m \u001b[38;5;66;03m# dispatch of the tasks to the workers.\u001b[39;00m\n\u001b[1;32m 1950\u001b[0m \u001b[38;5;28mnext\u001b[39m(output)\n\u001b[0;32m-> 1952\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m output \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mreturn_generator \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;43mlist\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43moutput\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/joblib/parallel.py:1595\u001b[0m, in \u001b[0;36mParallel._get_outputs\u001b[0;34m(self, iterator, pre_dispatch)\u001b[0m\n\u001b[1;32m 1592\u001b[0m \u001b[38;5;28;01myield\u001b[39;00m\n\u001b[1;32m 1594\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backend\u001b[38;5;241m.\u001b[39mretrieval_context():\n\u001b[0;32m-> 1595\u001b[0m \u001b[38;5;28;01myield from\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_retrieve()\n\u001b[1;32m 1597\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mGeneratorExit\u001b[39;00m:\n\u001b[1;32m 1598\u001b[0m \u001b[38;5;66;03m# The generator has been garbage collected before being fully\u001b[39;00m\n\u001b[1;32m 1599\u001b[0m \u001b[38;5;66;03m# consumed. This aborts the remaining tasks if possible and warn\u001b[39;00m\n\u001b[1;32m 1600\u001b[0m \u001b[38;5;66;03m# the user if necessary.\u001b[39;00m\n\u001b[1;32m 1601\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_exception \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/joblib/parallel.py:1707\u001b[0m, in \u001b[0;36mParallel._retrieve\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1702\u001b[0m \u001b[38;5;66;03m# If the next job is not ready for retrieval yet, we just wait for\u001b[39;00m\n\u001b[1;32m 1703\u001b[0m \u001b[38;5;66;03m# async callbacks to progress.\u001b[39;00m\n\u001b[1;32m 1704\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ((\u001b[38;5;28mlen\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_jobs) \u001b[38;5;241m==\u001b[39m \u001b[38;5;241m0\u001b[39m) \u001b[38;5;129;01mor\u001b[39;00m\n\u001b[1;32m 1705\u001b[0m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_jobs[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mget_status(\n\u001b[1;32m 1706\u001b[0m timeout\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtimeout) \u001b[38;5;241m==\u001b[39m TASK_PENDING)):\n\u001b[0;32m-> 1707\u001b[0m \u001b[43mtime\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msleep\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.01\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1708\u001b[0m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[1;32m 1710\u001b[0m \u001b[38;5;66;03m# We need to be careful: the job list can be filling up as\u001b[39;00m\n\u001b[1;32m 1711\u001b[0m \u001b[38;5;66;03m# we empty it and Python list are not thread-safe by\u001b[39;00m\n\u001b[1;32m 1712\u001b[0m \u001b[38;5;66;03m# default hence the use of the lock\u001b[39;00m\n", + "\u001b[0;31mKeyboardInterrupt\u001b[0m: " + ] + } + ], + "source": [ + "true_mis = []\n", + "ksg_mis = []\n", + "uf_ksg_mis = []\n", + "exp_idx = []\n", + "noise_list = []\n", + "\n", + "\n", + "# we are just comparing I(X; Y)\n", + "for idx in tqdm(range(len(noise_dims_grid))):\n", + " for jdx in range(n_repeats):\n", + " this_seed = seed + jdx + idx\n", + " noise_dims = noise_dims_grid[idx].astype(int)\n", + " # simulate multivariate Gaussian\n", + " mvg_data = simulate_multivariate_gaussian(d=d, n_samples=n_samples, seed=seed)\n", + " data, mean, cov = mvg_data\n", + "\n", + " X = embed_high_dims(data[:, [0]], n_dims=noise_dims, random_state=this_seed)\n", + " Y = embed_high_dims(data[:, [1]], n_dims=noise_dims, random_state=this_seed)\n", + " Z = embed_high_dims(data[:, [2]], n_dims=noise_dims, random_state=this_seed)\n", + " highdim_data = np.hstack((X, Y, Z))\n", + "\n", + " # true MI and CMI\n", + " x_cov_idx = 0\n", + " y_cov_idx = 1\n", + " z_cov_idx = 2\n", + " true_mi = mi_gaussian(cov, y_cov_idx, z_cov_idx)\n", + " true_cmi = cmi_gaussian(cov, y_cov_idx, z_cov_idx, x_cov_idx)\n", + " # print(true_mi)\n", + " # print(true_cmi)\n", + " # print(X.shape, data.shape)\n", + "\n", + " #\n", + " data = np.hstack((Y, Z))\n", + " # print(X.shape, Y.shape, data.shape)\n", + "\n", + " # now estimate both MIs\n", + " # define the forest-based estimator to determine distances\n", + " clf = UnsupervisedObliqueRandomForest(\n", + " n_estimators=n_estimators,\n", + " feature_combinations=feature_combinations,\n", + " min_samples_split=int(np.sqrt(2 * n_samples)),\n", + " n_jobs=n_jobs,\n", + " random_state=seed,\n", + " )\n", + "\n", + " # meta-estimator for nearest-neighbor lookup\n", + " est = NearestNeighborsMetaEstimator(\n", + " estimator=clf,\n", + " n_neighbors=n_nbrs,\n", + " algorithm=\"auto\",\n", + " n_jobs=n_jobs,\n", + " force_fit=True,\n", + " verbose=False,\n", + " )\n", + " geo_ksg_mi = mutual_info_ksg(\n", + " Y, Z, nn_estimator=est, norm=\"max\", n_jobs=n_jobs, k=k_ratio, verbose=False\n", + " )\n", + "\n", + " # regular KSG estimator using KD-Tree\n", + " ksg_mi = mutual_info_ksg_nn(\n", + " Y, Z, norm=\"max\", n_jobs=n_jobs, k=k_ratio, verbose=False\n", + " )\n", + "\n", + " true_mis.append(true_mi)\n", + " uf_ksg_mis.append(geo_ksg_mi)\n", + " ksg_mis.append(ksg_mi)\n", + " exp_idx.append(jdx)\n", + " noise_list.append(noise_dims)\n", + "\n", + " print(noise_dims, true_mi, geo_ksg_mi, ksg_mi)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37c7459e-8bd4-4ecd-8864-e5e0a3709cbd", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "true_mis = []\n", + "ksg_mis = []\n", + "uf_ksg_mis = []\n", + "exp_idx = []\n", + "noise_list = []\n", + "\n", + "# we are just comparing I(X; Y)\n", + "for idx in tqdm(range(len(noise_dims_grid))):\n", + " for jdx in range(n_repeats):\n", + " noise_dims = noise_dims_grid[idx].astype(int)\n", + " # simulate multivariate Gaussian\n", + " mvg_data = simulate_multivariate_gaussian(d=d, n_samples=n_samples, seed=seed)\n", + " data, mean, cov = mvg_data\n", + "\n", + " X = embed_high_dims(data[:, [0]], n_dims=noise_dims, random_state=seed)\n", + " Y = embed_high_dims(data[:, [1]], n_dims=noise_dims, random_state=seed)\n", + " Z = embed_high_dims(data[:, [2]], n_dims=noise_dims, random_state=seed)\n", + "\n", + " # true MI and CMI\n", + " x_cov_idx = 0\n", + " y_cov_idx = 1\n", + " z_cov_idx = 2\n", + " true_mi = mi_gaussian(cov, y_cov_idx, z_cov_idx)\n", + " true_cmi = cmi_gaussian(cov, y_cov_idx, z_cov_idx, x_cov_idx)\n", + "\n", + " # now estimate both MIs\n", + " # define the forest-based estimator to determine distances\n", + " clf = UnsupervisedObliqueRandomForest(\n", + " n_estimators=n_estimators,\n", + " feature_combinations=feature_combinations,\n", + " n_jobs=n_jobs,\n", + " random_state=seed,\n", + " )\n", + "\n", + " # meta-estimator for nearest-neighbor lookup\n", + " est = NearestNeighborsMetaEstimator(\n", + " estimator=clf,\n", + " n_neighbors=n_nbrs,\n", + " algorithm=\"auto\",\n", + " n_jobs=n_jobs,\n", + " force_fit=True,\n", + " verbose=False,\n", + " )\n", + " geo_ksg_cmi = mutual_info_ksg(\n", + " Y,\n", + " Z,\n", + " X,\n", + " nn_estimator=est,\n", + " norm=\"max\",\n", + " n_jobs=n_jobs,\n", + " k=k_ratio,\n", + " verbose=False,\n", + " )\n", + "\n", + " # regular KSG estimator using KD-Tree\n", + " ksg_cmi = mutual_info_ksg_nn(\n", + " Y, Z, X, norm=\"max\", n_jobs=n_jobs, k=k_ratio, verbose=False\n", + " )\n", + "\n", + " true_mis.append(true_cmi)\n", + " uf_ksg_mis.append(geo_ksg_cmi)\n", + " ksg_mis.append(ksg_cmi)\n", + " exp_idx.append(jdx)\n", + " noise_list.append(noise_dims)\n", + "\n", + " print(noise_dims, true_cmi, geo_ksg_cmi, ksg_cmi)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f9a70357-5a71-4dda-8784-62fcb011ff05", + "metadata": {}, + "outputs": [], + "source": [ + "geo_ksg_cmi = mutual_info_ksg(\n", + " Y, Z, X, nn_estimator=est, norm=\"max\", n_jobs=n_jobs, k=k_ratio, verbose=False\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51cc4a64-0f59-4cab-84d6-df1ac5449ecc", + "metadata": {}, + "outputs": [], + "source": [ + "result_df = pd.DataFrame(\n", + " np.vstack((true_mis, uf_ksg_mis, ksg_mis, noise_list, exp_idx)).T,\n", + " columns=[\"true\", \"uf\", \"ksg\", \"noise_dim\", \"exp_idx\"],\n", + ")\n", + "\n", + "print(result_df.head())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "263cea70-d755-431e-8052-b8bbb48d30d1", + "metadata": {}, + "outputs": [], + "source": [ + "# exp 1 is MI = uf min_samples_split=2, twomeans\n", + "# exp 2 is CMI = uf min_samples_split=2, twomeans\n", + "# exp 3 is MI = uf min_samples_split=np.sqrt(2*n), twomeans\n", + "result_df.to_csv(\"/Users/adam2392/Downloads/cmi_exp3.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "bcb9e192-bd3e-4342-b365-41dc770cd931", + "metadata": {}, + "outputs": [], + "source": [ + "result_df = pd.read_csv(\"/Users/adam2392/Downloads/cmi_exp3.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39d9c074-790b-4390-818d-b43535238084", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.melt(\n", + " result_df,\n", + " id_vars=\"noise_dim\",\n", + " var_name=\"estimator\",\n", + " value_vars=[\"true\", \"uf\", \"ksg\"],\n", + " value_name=\"estimate\",\n", + ")\n", + "\n", + "display(df.head())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "148cb19c-b6f7-4d4d-910f-dd5b8c5944d1", + "metadata": {}, + "outputs": [], + "source": [ + "sns.set_context(\"talk\", font_scale=1.0)\n", + "fig, ax = plt.subplots(figsize=(7, 5))\n", + "sns.lineplot(data=df, x=\"noise_dim\", y=\"estimate\", hue=\"estimator\", ax=ax)\n", + "ax.set(\n", + " title=\"MI of I(Y; Z)\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "607e5a3e-f467-4f24-81e6-45dafa1115a0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Text(0.5, 1.0, 'CMI of I(Y; Z | X)')]" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAApkAAAH3CAYAAADwuUTlAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/P9b71AAAACXBIWXMAAA9hAAAPYQGoP6dpAADW2ElEQVR4nOydd5gb5fW2n1HXrqTt1d7i3guuGGOKDaETEuMQIDYdB5IfNfTeCQlgQkjAmA9jh+ZCcULomGAwNrj3bq+3e3svKvP9cTSamV1tVZfO7WsvvzMaSe/Oqjxz3nOeI4iiKIJhGIZhGIZh/Igm1BNgGIZhGIZhog8WmQzDMAzDMIzfYZHJMAzDMAzD+B0WmQzDMAzDMIzfYZHJMAzDMAzD+B0WmQzDMAzDMIzfYZHJMAzDMAzD+B0WmQzDMAzDMIzfYZHJMAzDMAzD+B0WmQzDMGFAdXU1brnlFgwZMgRGoxGCIEAQBNTW1vr1eZYsWQJBEHDTTTf59XEjhW+//RaCICA/Pz9kc/j9738PQRDwxhtvhGwODBMMWGQyDBMSnE4nVqxYgQULFmD48OFITEyEwWBAeno6Tj31VNx3333YtWtXp/sdO3bMI8AEQcC5557b43OtXLlSdZ9HH3200zGPPvqo5/Zg43Q6MWfOHLz88ss4cuQIDAYDMjIykJGRAY2mdx/T+fn5EAQBV199dZfHNDY24qGHHoLRaMQDDzwAAGhra8PYsWMhCAJmzJgBp9PZ41xnzJgBQRAwduxYtLW19fr37IqOf9O+/Hz77bc+P39vOXr0KKxWKwRBwB133NHtsaWlpUhJSYEgCLjuuutUt91///0wGAx4+OGH0dzcHMgpM0xIYZHJMEzQ2bBhA0aPHo3LLrsMy5cvx8GDB9Hc3Ayr1Yqqqir88MMPePbZZzFu3DjMnTsX7e3tXT7Wl19+iaKiom6f7//9v//n71/Br3z55ZfYtm0b9Ho91q1bh4aGBpSVlaGsrAw2m81vz/OXv/wFZWVluP766zFw4EAAgNFoxLJly6DX67FhwwY899xz3T7Gc889hw0bNkCv12PZsmUwGo0+z0ur1XpEdW9+lMLbZDL5/Py9ZdCgQfjLX/4CAHjppZewbt26Lo+9/vrrUV1djby8PLz44ouq23Jzc3HNNdegpKQEf/3rXwM6Z4YJKSLDMEwQWbNmjWg0GkUAYkpKivjMM8+IBw4c8NzucDjEn3/+Wbz33ntFm80mAhBramo8tx89elQEIAIQ8/PzRQDiU0891eXzFRUViRqNRoyPjxdTU1NFAOIjjzzS6bhHHnnE87jB5rnnnhMBiJMnT+73Y+Tl5YkAxKuuusrr7c3NzWJiYqIIQNy1a1en2x977DERgGgwGMTt27d7fYwdO3aIBoNBBCA+/vjj/Z6rL6xatcrzd5o/f36f77927VoRgJiXl9fvOZx99tkiAHHw4MFiY2Njp9sXL14sAhAFQRC//vprr4+xdetWz3ugtbW133NhmHCGI5kMwwSNgwcP4ne/+x3a2towevRobNu2Dffeey+GDRvmOUar1WLKlCl45plncPToUfzyl7/s8vGuuuoqAMDSpUu7POatt96Cy+XCvHnzEB8f77ffxZ9IS6YWiyVgz/H++++jtrYWEydOxJgxYzrdfv/992Pq1Klob2/H/PnzO0WP7XY7FixYgPb2dkybNg33339/wObaFbt27fKkA0yZMgWLFy8O+hwA4I033oDNZsORI0dw1113qW47duwY7rzzTgDAzTffjNmzZ3t9DOnvUFVVhVWrVgV8zgwTClhkMgwTNB588EHU19fDZDLhww8/9CzZdkVycjI++ugjJCQkeL399NNPx6BBg3Dw4MEuly4lAXrNNdf4NPfeUFdXh8cffxyTJk2CzWaD2WzGsGHDcNNNN+HIkSOdjr/66qtVOaL/+9//eswd7S+vv/46AOCKK67wertOp8OyZctgMpmwY8eOTs/9+OOPY9u2bTCbzVi2bBm0Wq3f5tYbampqcMkll6CxsREZGRn48MMPg7pUriQnJweLFi0CALz66qv48ssvAQCiKOLqq69GQ0MDhg4d2mPqgfS3CJVYZphAwyKTYZigUF5e7onYXHnllRg+fHiv79tVMY6y0OXNN9/sdPu6detw8OBBDBkyBLNmzer7pPvA7t27MXbsWDzyyCPYunUr7HY79Ho9Dh06hFdffRWjR4/G6tWrVfdJSEhARkaGJ8Kq1+tVuYf+imzW1dVhw4YNANDteRg5ciSeeeYZAHLuJQD8/PPPePbZZwEAzzzzDEaMGOH1/pI47q74qD+4XC5cfvnlOHz4MPR6PVatWtXjBUqgueaaa3DBBRdAFEVcd911qK+vx6JFi/C///0PGo0GS5cuRVxcXLePcdpppwEAfvjhBzQ0NARj2gwTVFhkMgwTFNauXQuXywUA+NWvfuW3x7366quh0WiwcuVKNDY2qm6TCn6uueaagFaNNzQ04KKLLkJRUREGDBiATz75BE1NTaivr8e2bdtw8skno62tDVdeeSW2b9/uud9LL72EsrIy/OlPfwIAnHLKKZ6CH+V+X/n+++/hcrmg1+tx0kkndXvsrbfeijPOOANOpxMLFixAdXU1rrrqKjgcDpx55pm45ZZb/DKnvnDffffh888/B0Dn7NRTTw36HLzx+uuvIykpCYWFhfjtb3/rqdi/4447MHPmzB7vP3nyZOh0OjidTvzwww+Bni7DBB0WmQzDBIXdu3d7xj0Jnb6Qm5uL2bNno7GxEStXrvTsl7Y1Go0ndzNQ/OMf/8DRo0eh1+vx2Wef4fzzz/dUQE+YMAFffPEF8vPz0dbW5hEiwWTjxo0AgBEjRvRYDS4IApYuXQqr1YqDBw9iwoQJ2Lt3L2w2G958882gWzy9//77nmXn66+/Pqz8PbOysvD3v/8dAPDpp5+ipaUFo0ePxpNPPtmr+5vNZk9U+McffwzYPBkmVLDIZBgmKFRVVXnGycnJfn3sa6+9FoB6yXzFihVoamrC2WefHfCl1ffffx8AcOmll2Ls2LGdbrdarbj77rsBkBipq6sL6Hw6UlJSAgBIS0vr1fF5eXmenEPJHmrRokXIy8vr9n6iKEIUxW4LsfrCjh07PH/bGTNm4JVXXvHL4/qTefPmISMjw7P94osv9snWKTU1FYD8N2KYaIJFJsMwEc+vfvUrJCYmYt26dTh06BAA9VJ5IGlvb8eOHTsAAGeddVaXx5199tkAKL9wy5YtAZ1TRyoqKgD0Tdxfe+21mDhxIgCqhA5G4ZSS6upqXHLJJWhubkZ2djZWr14Ng8EQ1Dn0hqeeegrl5eWe7SVLlvTp/tLfRPobMUw0wSKTYZigkJKS4hlXV1f79bFNJhMuv/xyABTNPHjwIH744QckJSXhkksu8etzdaS6utrTJWfAgAFdHqeMpp44cSKgc+pIa2srAPTZOF2q6u+quj9QOJ1OXHbZZTh69CiMRiNWr16NrKysoM6hN2zZsgVPPfUUAOD8888HQN2llGkbPWE2mwHIfyOGiSZYZDIMExSU3oxbt271++NLkbZly5Z5oklXXHGFXzrSRDqSwK+pqQnxTHrH3Xffja+++goA5buefPLJIZ5RZ9ra2jwFUdOnT8eaNWs8Fzp/+MMfeh2ZlC64lBdhDBMtsMhkGCYonHnmmZ5imA8//NDvjz916lSMGTMGRUVFnnzCYCzxJicnezwju2tvqbwtPT094PNSIuVi+juCHAjeeecdvPDCCwBIrEk5meHGI488gl27dsFkMuGtt96CVqvFyy+/jIyMDFRUVODmm2/u1eNIf5Pe5ssyTCTBIpNhmKCQkZGBuXPnAiAhceDAgV7fVxTFXh0nCZL29naMHz8ekydP7vtE+4jBYMD48eMBAF9//XWXx0mROY1Gg0mTJgV8XkpGjx4NADh69GhQn7evbN26Fddffz0A8pCULhbCjQ0bNnh6jj/99NOeCvGUlBS89tprAIBVq1ZhxYoVPT6W9DcZNWpUgGbLMKGDRSbDMEHjySefhMViQUtLC37961+juLi42+Nramowd+7cXldjz58/H3feeSfuvPNOj3l4MPjtb38LgITFrl27Ot3e2NjoseE5//zzg57jKJl+nzhxAseOHQvqc/eWyspK/OpXv0JLSwtycnKwcuVK6HS6UE+rEy0tLbjqqqvgdDoxa9Ys3Hrrrarbf/nLX+LKK68EQJHY7vJvjx496llWP/300wM3aYYJESwyGYYJGsOHD8fy5cthMBiwe/duTJw4EX/+8589FeEAFX1s3boVDz/8MAYPHowPPvig14+flpaGv/71r/jrX/+K8847LxC/glduuukmDBo0CHa7Heeddx4+/fRTj/H8zp07cc4553iKWHrroehPRo0a5VmilzwzA0F/O/44HA785je/QUFBgaflaLBTCnrL/fffjwMHDiA+Ph5vvvmmJwVEyd/+9jdkZWWhsrKy22Vz6W+RkZGBkSNHBmzODBMqWGQyDBNULrnkEnzzzTcYOnQoKisrce+992LYsGEwGo1ISUmBwWDApEmT8MQTT6Curg6XX365p+1iuGK1WrFmzRoMGDAARUVFOP/88xEfH4+EhASMHz8e69evh9FoxL/+9S9MmDAhJHOUilLWrFkTkufvjlWrVmHt2rUAyOLpggsuQGZmZq9+pGXrYPDdd9/hpZdeAkBtN4cMGeL1uOTkZM+y+erVqz0+qh2R/hbS34Zhog0WmQzDBJ2ZM2di3759ePfdd3HllVdi6NChMJlMaGhoQHJyMk499VQ88MAD2Lt3L9555x3o9fpQT7lHxo4di927d+PRRx/FxIkTodPp0NbWhiFDhuD3v/89du/ejUsvvTRk81u4cCEA4OOPP0ZTU5PfH1+Z+tDXanClfU97ezvKy8t7/dOxlWigaGpqwjXXXANRFDFnzpweOw9ddNFFmD9/PgDgj3/8Y6dl88bGRnz88ccA5L8Nw0QbgtjbjHqGYRgmopk9ezbWrl2Lt956CwsWLPDrY//rX//C/PnzMXjwYOzbty9sLwy+/fZbnHnmmcjLywtpfuqyZctw1VVX4cwzz8Q333wTsnkwTCDhSCbDMEyMIBmHP/fcc72u2O8tklB69NFHw1Zghgsul8tTCPb000+HeDYMEzhYZDIMw8QIM2bMwKWXXordu3f3qStNb1i7di3GjBnjqaxmumblypXYvXs35s2bF5ZG8wzjL8LPH4JhGIYJGH/5y18wZswY2O12vz5uuHtwhhN2ux2PPPJI0PvBM0yw4ZxMhmEYJmYIl5xMhokFeLmcYRiGYRiG8TscyWQYhmEYhmH8DudkRjj5+fk4ceIETCYTBg0aFOrpMAzDMAwTxRw9ehStra1IT0/vMeWEI5kRTlxcHFpaWkI9DYZhGIZhYgiz2Yzm5uZuj+FIZoRjMpnQ0tICs9mMUaNGhXo6DMMwDMNEMXv37kVLSwtMJlOPx7LIjHAGDRqEmpoajBo1Cps3bw71dBiGYRiGiWImT56MLVu29CpFj6vLGYZhGIZhGL/DIpNhGIZhGIbxOywyGYZhGIZhGL/DIpNhGIZhGIbxOywyGYZhGIZhGL/DIpNhGIZhGIbxOywyGYZhGIZhGL/DIpNhGIZhGIbxOywyGYZhGIZhGL/DIpNhGIZhGIbxOywyGYZhGIZhGL/DIpNhGIZhGIbxOywyGYZhGIZhGL/DIpNhGIZhGIbxOywyGYYJHk4HUHEAaCgL9UwYhmGYAMMik2GY4NFWD7RUA40nAFEM9WwYhmGYAMIik2GY4NHeBLQ1AM42wNEa6tkwDMMwAYRFJsMwwcPeQuLS3kKCk2EYholaWGQyDBMcRBE48i2wcyVQXwK0N4Z6RgzDMEwA0YV6AgzDxAhlO4HP7gFEF1BTAGSOC/WMGIZhmADCkUyGYYLDwS9IYAJA+S6gpQFw2kM7J4ZhGCZgsMhkGCY4lGyVxy4HUL6Dl8wZhmGiGBaZDMMEh7Id6u3SbVz8wzAME8WwyGQYJvA0VwO1x9X7TuxhkckwDBPFsMhkGCbwFG7svK/yAIlMlyv482EYhmECDotMhmECT9HPnfe1NwGVBwE7RzMZhmGikZgUmWVlZVi+fDluueUWzJgxA2azGYIg4IwzzvD5sdeuXYsLL7wQaWlpMJvNGDlyJB566CE0NfEXKRPDKIt+lHBeJsMwTNQSkz6Z7733Hm6//Xa/P+7LL7+MW2+9FaIoYuDAgcjJycGePXvw5JNPYvXq1fj++++RnJzs9+dlmLCnbJc8jk8Dmirk/SwyGYZhopKYjGTabDacddZZuO+++/DBBx/goYce8vkxN2/ejNtuuw0A8Nprr+H48ePYsmULjhw5gsmTJ2Pv3r244YYbfH4ehok4aouAphPy9qhfyuOKfSwyGYZh/IkohnoGHmIyknnttdfi2muv9WwXFxf7/JhPPPEEXC4XFixYgBtvvNGzPzs7G++++y5GjhyJDz74ADt27MD48eN9fj6GiRgKf5THpkRg6FnApiW03XQCaCgFUocBenNIpscwDBM1OO1A1SFA0ABpI0I9m9iMZPqbxsZGfPbZZwCgEpgSw4YNw+zZswEAK1euDOrcGCbkFG+Wx8mDAVsWLZlLlGzhaCbDMIyvuFxA9RGgvgSwNwMuZ6hnxCLTH2zduhVtbW0wGo2YNm2a12NmzZoFANiwYUMwp8YwoUdZ9JM6DDDZgExFNL90J3f+YRiG8QVRBGqOAo3lcs57GMAi0w8cOHAAAJCbmwu9Xu/1mCFDhgAA9u/fH7R5MUzIEUWgfI+8nT4a0McDAybL+yr2Au3NwZ8bwzBMtFBXBDSUAw1loZ6JipjMyfQ31dXVANBt5bh0W01NTY+P99prr2Hx4sW9eu69e/f26jiGCQkVB4C2enk7fQxgiANyZ8j7ao4BLbWUS6T1fpHGMAzDdEGjO7e9oRiISwXKd4dFPibAItMvtLa2AgAMBkOXxxiNRgBAS0tLj49XWlqKLVu2+GdyPiCKIupbHXA6uSML0z8Mh76HxT12xqWjTpMItGmBxDFI0pkhOFoA0YX6gq1wWIYCpoRQTpdhGCayaKsHqo8CdYWAMRGmHR/BvOsdtE69Ge22kbDFmyAIQsimxyLTD5hMJgBAe3t7l8e0tbUBAMzmnitos7KyMGnSpF499969e3slXPtDfasDEx77IiCPzcQGD+q+xPXuT5k1DcNw+5ITAMjOaJl+KE7T7gQAvPbFNrzyaX5oJskwDBPxmDFR2INVhuUQBBfMG1/CX7+vwi0PLkKCOXQrRCwy/UBSUhIAedncG9Jt0rHdsXDhQixcuLBXzz158uSwiHoyjDfGa454xjtcg1W3bXKN8IjMqZr9QOgLIRmGYSKSOLTiRf0r0Am08ljoSsP7zjNwS4jnxYU/fmD48OEAgOPHj8Nut3s95vDhw6pjGSba0cKJccJRz/Z21xDV7ZtE+b0wSXMQAjgtg2EYpj88rFuGQZpyAIBTFHCb/WY0Ii7Es+JIpl846aSTYDAY0NbWhp9++gkzZ87sdMy6desAADNmzOh0W7hiM+mw/ZFfhHoaTISiKdkC83JKIREFDd5ccBKQPREwxNMB9WMgvvosBNEFm9CMHb+uh2v4+YDR0vWDMgzDxDKiSDmY9SVAazWQMBC6wh8R/+23nkPsw87D/5t5OpA+BjZTaGUei0w/YLVacc455+Df//43Fi9e3ElkHjx4EN988w0A4NJLLw3FFPuFIAghzeVgIpwKOY1DsA1AQkIiYLUBGvcCij4bSBkKVJIFmLVyOzBqDmDuOaWEYRgmJqkpABwVgKMSSM2hwp8fn5dvTxsJ05gLYTJqALMeCGHRD8DL5X3i1FNPRX5+PhYtWtTptoceegiCIGD58uVYvHgxRHfv0NLSUlx++eVwuVy45JJLMGHChCDPmmFChLLTT8pQahupUXzk6AxAluL9UL6LO/8wDMN0RUMZWRXVl1LnNK0e+PZZ2SZOZwLOvB/QaEM7TwUxKTILCwuRmprq+bn33nsBAD/88INq/3PPPae6X1FREQoKClBbW9vpMadOnYoXXngBABXu5OXlYdKkSRg0aBA2b96MESNG4PXXXw/478YwYUPpDnmcNgLQe8kPGjhVHlfsZ5HJMAzjjeZqsimqLwYsqZR2tOsDoHiTfMwpfwRsA0I3Ry/E5HK50+lEVVVVp/0Oh0O1v7m5b11IbrvtNowbNw7PP/88Nm7ciBMnTiAvLw+XXnop7rvvPlgsnGvGxAjtzUDVIXk7fbR3kZmnSC1prqQP0NRhFPVkGIZhgLYGWiavKwaMVsCUSD3Kf3pNPib/VGDEBUBLDXVV0+jDIqIZkyIzPz/fs5zdF44dO9bjMXPmzMGcOXP6MSuGiSKKNgGi25NIa6BIpsGLyEwZAlgygUZ3K7SSLRTdZJHJMAwD2FtJUNYX0fJ4fBrgaAO+eZK6pAFAXApw2p/oM7e5CkjIAWzZoZ23m5hcLmcYJsAU/SSPE3NJNOpMnY/Tm4HMcfJ26U6gvTHw82MYhgl3nHag+jDlYIouwJpJ+39eQsJT4vR7KLrZXA0YrFQ8GSYFlCwyGYbxPyVb5XHKUFoq76rKUZmXeWIPLbUzTCxRVwTUHAv1LHzD5eL3rj9xuciqqKEcsDdRZFLQ0CrRzpXycWPnAjnTKLrZWgfEp7qPDW1VuQSLTIZh/E+ZsuhnpPd8TIlchXdsXSHQXCMvAzFMtCOKQFMF0FRFQiFSaSih4r2mzvUOTB8RRaD2GNBYDrRUAQkDAY2OROS3z8rHJeUD026kcXMlRS/jkgGTLRSz9gqLTIZh/EtzDVB7XN7OGNN9jqXSoF10AaXbeMmciR1cDopaOdvpJ1KxtwAt1ZRf3Y+aB0ZBfQlFMBtKKSqpNdA5/e55EpMAFfbMfgjQGQF7M+VumlPCJhdTgkUmwzD+pXCDPNbHAUmDZBHpDYMFyBgrb5dtZysjJnZwOahgw+WI7Ai+sx1oraeflppQzyZyaXK7bNQXAZYMeRVo/6fAse/k46bdQIWTAEXC41IBS1rYFU2yyGQYxr8U/SyPkwYBehNdbXeFRgNkT5K3y3dzbhcTOzjtgMsJOCNYZIqi+/dwUKStsZyjmf2htU62KjInycvedUXA+r/Jxw2YDIxzdw9srQdcImBOlAuDwggWmQzD+Bdl0U/qsO7zMSVypsvjqoPUwcLl8v/cGCbccDkA0f0TqcvlLgcJZdFJy+atdUBrbahnFVm0N8s9yfUmsiUC6NyufQpwtNK20QaccS8VAYkuEvWWVBKY2vBrA80ik2EY/1K2Sx73VPQjkXsyILiNgx1tQMU+qqhkmGjH5XTnZNoBV4RGMp3tJIa0eorANVcDjSdCPavIwdHu9sIsBQTQMrnEluXAib3y9qw7ySsToLQErQEwJwPx6UGdcm9hkckwjP+oLQKaFF8uGWO9m7B3JD6Nop4SJds4L5OJDVwO+SdSl8vLdgBr/gj8+HcSPe2N7mhmfahnFv64nCQwG0oBZytgzZLth8p3A1uXy8cOPw8YfLr7fg4S8/FpdB9NeMq58JwVwzCRibLox5RIfXR7E8nU6qjKXKJ8F4tMJjao3A9sfRuoPBi5y+U//gOoPAAUbwF2vA+YEqjzTGN5qGcW3ogi+aM2lpMotw2QW0G2NwPfPEVL4gBgzQZO+T/5vs1V1GJSsi0KU1hkMgzjP4o3yePkwYDe2Ps8IWVeZsU+FplM9COKwL9vA3avBr5/kSKAkZiLXHVYHu/5mCqc2xooL7ON7ci6pK4QaCgjkZkwQP1Zuf5v5D0KUP7l7AfkVSFHG0WJJeP1MIZFJsMw/qNT0U8f7DTyZsrjlhqg7jgVETBMtNJaB9QcpXF7A1UWR2JeZkOpPG5vBA58TpXRzdUczeyKhnLKwawvAWxZ6ra7R74FDnwmb580n/yGJZoq3K0jkymaGcawyGQYxj+IIlC+R95OHw3ou/HH7EhSvvqqvHgLRzOZ6Ka5Q3echrLIWzJ3tJEJu5KdK6kKurWOLhjZkkxNczVdRNcXUTTSYJFvazwBrHte3k4fDUyaL2+3N9M5j0ul5fUwh0UmwzD+oeIAWQ9JpI/pXdGPhM4IZE6Qt8t2sshkopumSvV2Q3nkFf/Ul3Te11gOFKwn8dRSw9FMJe1Nbi/MEjo/5kT5NtEF/O9ZSjUAaCVo9gPUUlJCMl6PTyWrozCHRSbDMP5BWfQTn+7+EOyDyASAgVPk8QnOy2SinKYK9XZjBIrMuiLv+3e8T0u6LbUUubO3BnVaYUtjOf1otYClg+3QzlW0giNxyi3qaGVrHa0YxSVTRXkEwCKTYRj/ULxZHqcMoatsqVKyt+TOkMd1hRTpibQvXYbpLR1FZlNF5C2X1x6Xx6ZEeVx5AKjYCxjMVADE0UzC0UY/ynMFAFWHgJ9el7cHnQYMP1feFl2UXmFJIx9NrQ6RAItMhmH8Q+l2eZw6on89dLPGKxLZRaB0GxUSMEw00tGwvKki8gp/6grlcdZ4YIBiNWL7e1Sc0lJD0UxHhAnoQOBsp7+xspLc0QZ886T8t49LJdN1yS8TUBivJ8lm7BEAi0yGYXzH6aCohUT6qL4V/UgYLEDGOHm7bDsXDTDRS8dIZktV5EXulcvlpiRgwmXyduFGqjzXGiia2RTjXYCcDsDpJCN1ZZ7lT4vJL1PijHvJa1QiQozXvRE5M2UYJnwp3U5X4wB5uqWP6lvRj4QgAAMmydvlezgvk4leOlaXN1fL76NIob5YHusMQNJgIHmIvG/HCurD3VLN6S9SFFOjo89JACj8Cdi1Wj5m3G/UuelAxBive4NFJsMwvlP0kzy2DaCrcF0/lssBdV5m1SGqWI9Eg2qG6YnmDtXlLgfti6TXu9Ij0zaQIpbjfyPvO/QVYG+m/OzWus7R21jC2eZeKndHMVtqgW+flW9PHgxMvU59H5XxevhbFnWERSbDML6jrIhMGUr5mP1d0smZBmjc+UrOduDEHsDO0UwmCukYyQTIKzOS8jLry+RxYi69dwdOlfMGXQ6K1JmTKVLbVEn9umMRp52WzDUGqhL/7i+yx6hWD8x+kKzclDRVUPQyLgUwWjo/ZpjDIpNhGN9RFv2kDe9f0Y9EXAqQOlzeLtnKeZlMdNJc3XlffWnkVJjbW4HWGnnbmg3EJdHqw7hL5f171wCCFhDgzs2M0Wimp+hHB+z7BCj4Qb5t2kKKZCppb6JIpjmFzm0EwiKTYRjfsLcA1Yfk7fQx/Sv6kdBogeyT5O3y3VxhzkQnLTWd90WSV6YyHxMCYEmlTj+iCAyeLX8OtDeRqPJEMysiKyXAXzjb6W/beAL48e/y/oFTgbG/Vh8rinSe4tPItigCjNe9wSKTYRjfKNokL39pDWRf1J+iHyU50+RxxT6gjUUmE2W0NwEOLwbljSciR2QqK8vNidR8IT6VqsydbcCoi+Tbd66kFQ7RSbmZ3lIFoh1HO/3Nf1gk/+2NNqomFzrIsbZ6AAIV+1gygz1Tv8Eik2EY3yhUFP0k5pL5ss7Hq+68U+Rxax1Qe4wipgwTLXRsKenZH0GG7EqRGZdCeYXxaVT452gFRl1Iy+QA2Rcd/R/lFzZXU8RWFEMz71DhbKf81MqD8r7T76Jzp0R00esjPg2wZkaM8bo3WGQyDOMbJR2LfuLUJsL9ITEPSMhRPMdWtjJioouOleUSkWTIruz2I4lMnZHyMk2JlPoy9Cz5mO3vAQYbia3Weu85qdGKy0m56/s+kfeNvADIn9X52JYaOo/mxIgyXvcGi0yme4q3AB//Edj7n1DPhAlXynbK47SRvhX9SOgMQNYE9XOwyGSiiaYuloubI8iQXdntJz6d0mUA6sltTqQ0F2WuYdUhoHSru6d5VWxFM53t1M8d7t/XNgCY8YfOx3mM19MBW7bvF+whhkUm0zXfPAW8fiawdTmw/uVQz4YJR5prgNoCeTtjFEUy/YHSkPjEXhaZTHSh7H5jUFjTtNRETmpIfYk8tihEpiGeIplGKy2PD5wqH7f9PRKgjlZKhWmtDeKEQ4ijTf1ZOeVa75+VTZWUp2lOop8Ih0Um0zXDzpbHhRuo+wrDKCncII/1cUDSEPqC8Qd5p8rj+mKgsSJyIjwM0xPKvuVJ+fJYdEZO9XVDR5GpV2+bk8lwfJzCnL3oZ6D6qDuaWQ00lAdtuiHF0a7+XRNzvRzTRtHf+FSKYkYBLDKZrhk4lexoJDb8I3RzYcKTok3yOGkQ5RF1NBPuLxljKBoiUbKFrYyY6EHpFWnJUEczG8ojIy+zQWHEbs2UmygAVPxjslH6TPJgyteW2LGC3tvtTe5oZl3QphwyGsuo4l7C6qViPMKN173BIpPpGkEApl4rb+/5iE2xGTXKop/Uof6LYgL05ZQ5Vt4u28GvPyZ6UIpMU6K6wKMhAgzZ25vV4tCaLS+XS8S7o5ltdcD4y+T9h76i3FNzIuUfxkI0s+aYPDbEUyqBkvYm+pubk6MmigmwyGR6Ytxv5LyRtgZg+zuhnQ8TXpTtksdpo/xT9CMhCMAAZV7mHs7LZKIHpU+kOZGimRKRYMiuzMcUNLTE29Fqx5xE0UxBBwyYTKIToJSAXR/Q7e0NJFbbGoI391CgrMTvGMWUjNfjUinNwF+rQWEAi0yme0w2dT7Npv8Xurkw4UVtkbp4IWOM/4p+JHJPlsdVh+nLKBJy1RimJ1QiM0kdyYwEkan0yDQler/A1Gjo94pLIhE5bp58295/Uw6iyUa5mcoc1WhEWYlvzVLf1lYH2Xg9A9EEi0ymZ5RL5uW7geLNoZsLEz4oi35MiYBtoH+XywHKC5aKCVx2oHwXYOdoJhMFKD0izUkUwZKIBEN2ZWQuPkWdj6kkTmo16QSGnCl/RtibgH3/oeXh1jqqqo/WlQqXE6hTtOBUdvARXWRnFZ8e8cbr3mCRyfRM1gR1L+kN/wzdXJjwoVhR9JM8mLwttV180fQXczItw0uUbuO8TCY6UPYtj0sGEgbI202V4V/4o4zMxaV2/d7X6qiQxZTs7gJ0sXzbzlWUFmO00PlojNLcTGc75dlK2BSRzOZq6kseBcbr3mCRyfSOqdfL433/AVpioBqQ6Z6S7fI4ZZjv/cq9odGoL3DKdnOFORP5ONrUr2NzitrSJhIM2bsyYveGJZ2qze2t1M9c447WNVUAh7+RrY6aa+iYaKOjyJQimS4Hieu4NBKeEW687g0WmUzvGPNruRrO3kIG7UzsIoq0dC2R7kcT9o7kTJfHlfvIR45hIpnmDt1+zIlAYr683VID2MM8Yq8yYk/rXmRKrSbNiSSkVK0m36f7GsxAa5RGMx1t6pxTKZLZVEHiOy45KozXvcEik+kdhjhgwhXy9ualsdMOjOlM5UGgrV7eDkTRj0TeTHnc1gDUHImcjigM440mRd9yrZHyFBMGKg4QSZSEc5GbMjIXn9Fzqoyy1eS4S+X91Ycpzz8uhcR1czWJsmiirlid/mDJpNSBtib6vaPIsqgjLDKZ3jNFUQBUdRAoWB+6uTCh5bii6Cc+nXKJAiUyEwYCiXnydvFWzstkIptmhcg0WWn52JRABTISDWXhnZfZMcewJ5EptZo0WOl3Va5QbH8P0Jnop6U2+irNa4/JY6ONgjaNFSQw41L8XzAZRrDIZHpP+kggd4a8vfHV0M2FCS3Kop+UIbQcFqiqSK0OyJ4ob5fv5LxMJrJpVBixG20kMrUGdYV5fRgbsrc1qn0tLZndL5dLxKfR0nBLrdrOqHgTrY7EJQOt1STCwz0ntS/UdPDIbG+kC4i4pKiOYgIsMpm+oiwAOvA5WS8wsUepougndXhgin6UDJwqj0/sjV6rEyY2UPrLmhLoQkqrk83KAaApjL0yVUbsWjJi12h7vp85kXL7pVaTqcPl23asoNUQjY4KS6MpmqnyyMx0RzFT6e8dRcbr3mCRyfSNURdRJSBAfVg3vxna+TDBx+kAKvbJ2+mjA7dULpGryMtsKKU2dOH6BcwwPaGMZJoS5GprZSeYxhPh+xpXGrGbk/rW6cvibjXZWqtuNXn4G/qd45LJnL25kj5rIh2XC6hXeGSak0mYxyV7718eZcS0yFy7di0uvPBCpKWlwWw2Y+TIkXjooYfQ1NT3KMnVV18NQRC6/fnss88C8FsEGZ0ROGm+vL1lGRcAxRplOyhpHaB2coGsLJdIHyVf3ADUM52jmUykoszJNCbKIlO5dBrOhuxKI/a45L7545qT3a0mtUD2JLnDjegEdq0CDBaqQG+tU5+nSKWjfZEpgZbJLRm9i/5GONFlLd8HXn75Zdx6660QRREDBw5ETk4O9uzZgyeffBKrV6/G999/j+Tk5J4fqAM5OTnIzc31eltSUpRYFEy5Glj/Eo1rC4BDXwPDzur2LkwUUbhRHtsG0IdmoEWmwQxkjQeOfEvbZTtIZJoTA/u8DBMIlNXl5oQuRGYYG7KrPDLT+iYypVaTzdVUTT5uHvDj3+m2vf8BJi2gYpimKsB0go6NZDHWUWSakyh/NdCfmWFCTEYyN2/ejNtuuw0A8Nprr+H48ePYsmULjhw5gsmTJ2Pv3r244YYb+vXY1157Lb7//nuvP9OnT+/5ASKB5MHA4DPk7Z8Wh2wqTAgo3iKPU4ZSRagmCB8lAybL4/LdHMlkIpeOfcs9IlNhYxTOxS/K5fL4HjwyvRGXShenohMYfCZFLwHyBt37H/e2i3p6d/QUjTTsLer0CHMiteCM8lxMiZgUmU888QRcLhfmz5+PG2+8EYLbZT87OxvvvvsuNBoNPvjgA+zYsSPEMw1jlAVAh7+mSkgmNigLctGPRO4p8rjmKEVBwtlHkGG6oiuRqez601ofvhdSfTFi94an1WQS4GgBRv9Svm3XKuqEE5cMNFWHv19oT9QXkZiWkCKz/m7BG6bEnMhsbGz05EbeeOONnW4fNmwYZs+eDQBYuXJlUOcWUQw/V86lcTmAn5eEdj5McLC3AFWH5O1AmrB3ZOAU+erf5QDKdoZ/VxSG8UZLtTyOS5ZFZidD9rLwFFgNSpGZ2T/BZEkn30x7KzDyAoruAZQmcPhrwJgAiA7KzVSer0ij5pg8lnxCYySKCcSgyNy6dSva2tpgNBoxbdo0r8fMmjULALBhwwavt3fH2rVrMW/ePMyePRu//vWv8dRTT6GgoMCnOYclWj0w6Sp5e9s7gMvZ9fFMdFC8Wf47aw1A6ojgiUxTAlWyS5RuD99ID8N0hdMtnCTMKbLINFrV7QXD1ZC9oUweWzNlgdgXdEYgLtHdalIDDDtbvm37+/S/OYkEZuOJyC0wVRZJWdLpu7Ovkd8IJuZE5oEDBwAAubm50Ou9vzGGDBkCANi/f3+fH/+7777DqlWrsHbtWnz44Yd48MEHMWzYMDz33HP9n3S4Mvkq+nAA6Mp2/39DOx8m8BxXFP0k5lBBTl/sS3xBEKgaVaJ8N5uyM5FHS41625wkNzLQGtRemfVl4Vdh3lqvft9Ze2nE7g1LhrvVZAMwdq68v+YoUPQTXVg6Wt3RzJouHyasqVUWSaWSyIyhSGbMVZdXV1PYvbvKcem2mprev6iHDRuG559/HrNnz0Z+fj6MRiN27NiB559/HitXrsQ999wDi8WCm2++ucfHeu2117B4ce+Kafbu3dvrOfqdhIHAsF8AB9zWTD8tIR9NJnopURb9DAN0ZhJ/wSJnOrDpDRpX7lN3HWGYSEBpy6PRUfTSs62haFelO8DRGIZ+sJ2M2NP6X/inbDWp1VJHueM/0m073qf3uzkJaKmSPTQjCVFUe2TGpVLUV8siM2ppbSV/P4Oh6ysvo5FeAC0tLb1+3AceeKDTvunTp2PFihX4wx/+gH/84x944IEHsGDBAlgslm4fq7S0FFu2bOn2mLBh6vWyyDz2HVBdACTndX8fJnIp2ymPU0cEr+hHIu8UAAIAkZbKqw4BaSOCF01lGF9R2hcZbZ3zGa1Z8jgcDdmVleVxyeQu4QvxaVQIVXscGP8bWWQWbwEqDwDJQ4DqI+5oZm1k2ZY529Wi3Oz2FI2Roh8gBkWmyURviPb2rpcg2traAABms3++uJ5++mksWbIEtbW1+Oabb3DxxRd3e3xWVhYmTZrU7TESe/fu7ZMY9jtDZlNEs64IEF3Az4uBc54K3XyYwNFcQ76oEplB6PTTEVs2WWhVH6btkq1kgcIik4kUVCLTKudjStgUIrP5RPgtl6uM2FM6z7+vmBJIbOvN9HhpI+WOYjtWALMfJGHZ7M7NjDSR2ajIXzUnUmoBL5dHL5IhurRs7g3pNn+ZpyckJGDMmDHYunUrDh482OPxCxcuxMKFC3v12JMnTw5t1FOjBSZfC3zzOG3veB8469GYulKLGQoVhXD6OCBpSPBFpkYLZE2URWbZTsoPi08J7jwYpr+o+pbbOhuN2wYojq0Kv8Kfuo45hj4WsQgCpQi01JAgG/cb+fvk8De0WhafClQfpWhmW4M6xSCcaWvqYLyf4u5THzsiM+YKf4YPHw4AOH78OOx272/ew4cPq471B9LyvMMRBb1YOzJpvnw121QB7P4wtPNhAkPRJnmcNIiuxvU+LpX1h5yp8rhiH1eYM5FFo1JkJnS+IE/IkcfhaMiuzDGMT/dPpbSy1eSASXLKgOgCdq6i7xdTAi2rN5T7/nzBoq6QfgcJawblZAajeUWYEDu/qZuTTjoJBoMBbW1t+Omnn7wes27dOgDAjBkz/PKcDocD+/ZR+H/gwIE9HB1eFDYU4pZvbkFFc0XXB1nSyedMYtP/C/zEmOCjLPpJHRq6tmh5M+VxYzlQVxJ+X8QM0xVNis9SU2Ln5WalV2ZbQ/gVtylFpqWPLSW7QqOhohhzEnX5GTdPvm3ff+gcmJMoktlaHznv95qj8ticTDnsMWRfBMSgyLRarTjnnHMAwGsF98GDB/HNN98AAC699FK/POdrr72Guro66HQ6j9F7JPDxoY8xd81crC1ci0fWPwKxO58yZQeg4xuAir7bPzFhTtkueZw2KvhFPxKpI+gLSaJ0C0czmchBWV3uVWTmgIrb3DSUhpchu7KQJd4H+6KOxKdRtNLlpLbFRhvtd7QCe9e4C2Z0gLON9kUCKo/MNHc7SRaZUc9DDz0EQRCwfPlyLF682COeSktLcfnll8PlcuGSSy7BhAkTVPfLz89Hfn4+Vq1apdr/5Zdf4p577umUb9ne3o6XX34Zd9xxBwDg97//PbKyshAptLva0eKgoqJ1xeuw6sCqrg/On0UFGQAAkfuZRxt1xepcsmB2+umI3ghkKd6bZTtYZDKRQ5OypWRi55xMQ7zaqifcDNmVy9W2fnb78Yay1aS9uUOryQ+oiEZroP8dbf55zkCjzF+Nc7ffjKF8TCBGRebUqVPxwgsvAKAim7y8PEyaNAmDBg3C5s2bMWLECLz++uud7ldQUICCggI0NqoNoJuamvDcc89h+PDhyMzMxJQpUzBlyhSkpKTglltuQXt7O+bOnYvnn38+KL+fv7h02KU4dcCpnu3nfn4OxY3F3g8WBGDKdfL2rtXULoyJDiRbEYCiL7YBoROZALWYlCjfwyKTiRw69S3vINK0ekpBkmgII0P21jrArniv+WLE7g0pmim1mpQEbHMVcOjryBKZokgX5xLxKTFnxA7EqMgEgNtuuw1ffvklzjvvPDQ1NWHPnj3Iy8vD/fffj02bNiE1NbXnB3EzefJkPPTQQzjrrLNgMpmwb98+7Ny5EwkJCfj1r3+NNWvWYNWqVd16c4YjgiDgsVMeg1VPlXytzlbc8909cIldLN1MvEL+wGmpIfsJJjoo3iyPkwfTB2Uol31yFfnSNceo9Vw4LSkyTFco+3CbkzsvlwsC9QOXCCevTKVo0ugoOufPZgx6ExCXREITAIadI9+2430S5M72yFgud9op1UHCnETnLMZyMmPOwkjJnDlzMGfOnF4f31VOYk5ODh5//HF/TSusSI9Lx4MnP4h71t0DANhesR3L9izD1WOu7nxwXDIw+hJgp1tcbn4TmLwgaHNlAkjJNnmcMiz0vpQDJpMJtKMVEJ1A6Q4gYyxg7L7RAcOEFJeLDMUlvIlMQG3I3hRGIlNZ9GNODkxULj6dIpfVR4Gxv6bCH4AuJst2AEn5kRHJdLape7ybk2LOIxOI4Ugm03vOG3Qezso9y7P9ty1/w5G6I94PnnaDPC7ZohYnTGQiikC5ougnfRTljYUSo5VEpUTpNl4y7yvtTdRJpbma/sZM4GmtpYsiiThF33Ilnbr+hMlyeU0HI3Zvc/cVowUwJlCrSaNN7Sax252b6WwL/5WLtkZ1akR8OuXfxpiHNItMpkcEQcDDMx5GkonM6e0uO+76311wuLx4fg6cSpXHEj93zm1lIozKg0BbvbwdyqIfCUEgPz2J8t1kys70noYyoKaAnCAq9lGKCxNYlKJD0HTdvSZBYcjeXBk+hT91CpEZnxa4pV9LGgnw1lpqNSlRup0imk4HCc1wprYAgPviTdAAloyYi2ICLDKZXpJkSsJjMx7zbB+oOYDFO7xUkAuC2s5oz8dAa5j5vDF947ii0098On25hFpkAkDOyfK4cn/4+QmGO/ZmEpZ1hRTRrDhAgrO1vuf7Mv1D2f3FYO260lhlyF4VnsvlgRSZpkSKYupMdC7SFYGLQ19Fho1R9TF5HJdCKUYxlo8JsMhk+sCZuWfi4iFy3/XFOxZjX/W+zgeO/40sQtoagO3vBGmGTEAoVnT6SRlCV+OBWCbrK3mnUIQAAOwtJDTZ0aB3ONqAioPUxi95EH351RYAVUdIaFYepOU+xr8oI5kmL33LJRJz5XF7E1V1hwNKkWlND9zSr9Rq0ixFMy+Tbyv6GWiuBRxhkkLQFUqPzPjUmKwsB1hkMn3k3mn3IiMuAwDgFJ340//+hPaO+UImGzBWYWS/5a0gzpDxO6Xb5XHq8NAX/UhYMoDkIfJ2yTbOy+wt294BVl8DfHYf8P1L9OWXPIg6r9QcBaoOAxV76f/25lDPNnpQtpQ0JnT2yJSwDaAWixLhYsher6iWtvjZvqgj5mR3lbkGyJoI6N154KILqDkS/pHMjh6ZGj1HMhmmJ6wGK5469SnPdkF9AV7a8lLnA6cplszLdwMFGzofw4Q/Tgfl60mkhUHRj4RGA2SfJG+X7eS8zN6y4315vO/fwIoFwOG1tASalE8NZ6qP0jL6ib005iix7zR16FveVSRTb6bol0Q4GLKLIkW+JSyZnT0+/YnUajIumVpNKqO7tcfDu8JcFIH6Ink7LtndsYhFJsP0yPSs6bhi5BWe7eV7lmNr+Vb1QVkT6OpTYtOS4EyO8S9lO+SIgaABMkaFRz6mRM50eXxiL0cye0NHtwCAcjPXPgX8904SNJYMICmPcgGrjwDVh8n0vvZ4+C9ThjOqvuW2rkWmVkeCXyIcDNlbayktRcKf3X66Ij6VzpPLCdiy5f11ReEdyXTa2b7IDYtMpl/cNvk25FgpOV2EiHvW3YNme4dlNWUB0L7/kFUKE1kUbpTHtgG0xBdOIjPvFHncXAnUFoVPkUS4UnVYXSSlLD4p3gKsuhbYvJTEqC0LSMyhL/Saw0DlYeDEbvqS5/Pcd5o69C3vTqRZlYbs5aE/3yojdj1FGf1pxO4NrV5uNRmfIu+vL3FXmIfpa9DZrjZij0umC4cYaykJsMhk+olZZ8Yzs56Bxv0SKm0qxXM/P6c+aOxc8jME6Ap46/Igz5LxmeIt8jhlCC3jacLoYyN1GEXdJEq3cjSzJwoVqSuJucC8pepKfZedRObq66nTk85IFxi2AUB7A0U2Kw9RGkx9CUWZmN6haimZ2HVOJuDFKzPEgkpZ9BMXICN2b8SnU2qBWZE+0FgW3hXmrfVqSzBrFgnzcPrsDBKx9xszfmNC2gRcM/Yaz/bqg6vxY7Gix7UhDhj/W3l7y/LwSF5nek+ZsuhnRPgU/Uho9ZSaIVG6nUVmTxQp3AISc0nsnPsMcNZjFJ2SqCsEPrkT+OZJWoXQmymqackE2mopT7PyIInNhnJ+b/cGlcjsotuPhHJ5uKki9MvlKiP21O7n7k+kVpOpw+R9TRX0Pg/XvMyao/JY0JJQjsF8TIBFJuMjN0+8GUMTh3q27/v+PjS0K5bilEvmVQeBo98FcXaMT9hbgKpD8nb66PAp+lEyYIo8PrGHRWZPKLtwJQ+hpe/WWmDw6cBv3qIVCEHx1XDoKyoM2vtvquw1xAGJebR82VJNy+9VB+jcN1Zw96DuUKYM9SgyByruFwaG7EojdksAPTK9EZ8OpI2UX5eiiy6CwlZkHpPH8akU9dWxyGSYPmPQGvDsrGehE+jDsqq1Ck/8+IR8QPpIdXHGpjeCPEOm3xRvlpdCtQYgbUR45WNKKNvO1Ra4v5A5quYVpwOo2CNvZ0+kaGZLHdnT6EzAKf8HXPJP+ntLtDcC654H1vwfiUqAUmES82jptPGEexndLTa5VWVnRJFEuYQ5ufvq7MQwM2QPlhG7N4wWEmvK1Jja4+G7XK7yyHSfqxjMxwRYZDJ+YETyCNw88WbP9qfHPsXXBV/LB0xV9DM/8DktrTHhz3FF0U9iDi2XhttyOUA2RpL4FV20ZN6xCI0hTuyRoz+CFkgbDSQNItsiiLI1TNoI4Jf/AE65RX1hUb4b+OAGYMOrFOkWBKr+TRpEorOh1N09iFtVdqK9Ub3kHZfYfU6msuuPvSX057K+RB7HB9CIvSssaXRRIxHONkYqj8yUmDViB1hkMn7imrHXYGzKWM/2I+sfQXWr+6p99MVk4QBQsvbmN0MwQ6bPlCiLfoaR2Ah0NWl/MMYDmfJrj/Myu0FZ9JMwEDAnANYMyndLzKMq3trjVLig0QJjfw38Zhkw+Ez5fqIL2PEesPJqoGA97RMEyu1MHkSvk/oisj2qOEBFQhzVVFeWAxQB7u79ZMtWL6fXl4Q2Qq8UmdYAG7F7w5RIxYee+RTR90k4rlrUdfDI1Og4J5NhfEGn0eHpWU/D6F4SqGuvw0M/PARRFOkKbuLv5IO3vQM4uSI17CnbKY/DsehHyYDJ8rh8N5uyd0XRZnmcMlSOUhotlPOWlEsRtOZKss0RXbRMedYjwHl/7lDxXA58fj/wxUNyJxtBQ1+qSYNo6b22gJZ6+e+hLvoxxNP56Q6dSe2V2VgeurxMUaTnlwh0tx9vCIK6w1dDmdvGKMyimV15ZLLIZBjfGJQwCLdPvt2z/V3Rd/j34X/TxtRr5QNrC4BDXwR5dkyfaK6hv5NERpgW/UjkKvwyKw9QJI7pjLJFaNpI9VK4VgckD6al80S3EXttobzEmzMdmPcmXTAqI2zH1gErrwJ2rARcDtqn0dIyocFCbSm5D7o6kmnsxohdQqNR5yA2lIauwrylRp3/aM2k10uwUVaYN5TRnMItL9PZru6MFJ9G7wcu/GEY37l85OWYkiFX+z7909MobyqnL6/80+QDN/2/EMyO6TVKE3Z9HP39wrHoRyLnZLny1NEKVO7jNogdsbdSFbhExtjO0WlBIAP21OEkNo0WoKZAjkTqTNQydu4StXWUvQXY8Arw4e+p85KEIQ6wN6rN32MVZUvJ3ohMoIMhewi9MpXLv1oDXUCEgrSR8tjRCjScCL8OVC01QGudvG3Nitl8TIBFJuNnNIIGT536FOJ0JEia7E24b919tGw+TVEAdPgbte8aE14U/SyPpaVPfQ/Le6HEkqaOcpRsU1fyMhTFlNwCNHogbVjXFw4mG6VIJORSbmBDGXkTSrmVSfnAhYuA0+8hwSRRdQj46Gbg+xdJWOrjSNzam9i0vVEhMrvrW64kXERmqIzYO2JJp7xhiZqj4RfJrFZ4ZGp0lG4So0vlAItMJgBkW7Jx77R7Pds/l/+M9/a9B4w4j6oSAVpW28zRzLBFWfSTOjS8o5gAReCyJ8nbJVsp+qKMwMQ6yqKfpDzAYO2+A4nOQMI9KZ9+7K10PqUlcUGg9/Rly4Dh5ynuKAJ7PgZWXEW+uIIGaG/haGZzh5aSvRGZtgHyOJSG7EpLnrjU4FeWS+jNZLklUReGFeYqj8w0ukDnSCbD+JdLhl6CWQNmebaf3/w8CpvKgEkL5IO2vx967zfGO2W75HHaqPAu+pFQ+rFWHaac0tpCiixwdXOHFqHDevc3FQSqQk8ZSl/uOpN7+VxhEWVKBM64B7joJbXFTEs18M0TwPZ3AUczF/8oczLNib0UmQpD9qYQGrJ38n0MkcjUGugCSaK+KPwimapzlUqrBhzJZBj/IggCHp/5OGwGWkprc7bh7nV3wzl5gZw711BCXUSY8KKuWJ0/ljGGcuvCnXyFKXtzJSAK5FdXX0yiM9aXa8t2yOP0kX2LTpuTKB8uMZeKUepL1NXSAOVozl1CvrjKL9VDXwIn9nMks0nZUjKhd4UzSkP2lqrQ5R+G0ohdiSBQfrhEXYm7wjyMghXKzkiSRyaLTIbxP6nmVDw04yHP9q7KXVha+BUwZI580OalwZ8Y0z3KZVVTIi3Z6cO4slwieQhgVfR7Lv6Z5l5fSl+SlQfD68somLQ1qHPFMsb2/cJBb6KCoMRciia1NtB5VYp3rR446Upg3lJ17+3S7bRkHqvnH+iwXJ7Uu0imMjLsaCOhGQqUHpmWjNBFMgF6DUo0lpGFUThFM+s65K9qDbxczjCB4tz8c3Fu/rme7Ve2vYJDYy+WDzi2jr78mfChaJM8Th5MuXmRYL+h0QIDFHmZG18F9q4BEgZQPlt9EdkbxWLVeeHPANwpAzqz++/ajxQIjYYEZvIQd5cgLaUldPySt2UDg06Xt6sOAI6m2I5m9qVvuYQlQx0Fqy8Njfl4Q6k8tmaENjKnbHfaUgO01oZPXqbToT5X5mSKWMdoS0mARSYTBB48+UEkm5IBAHaXHXcd+wB2KaFddHE/83CjZJs8TummAjkcOflmdbXzxleBn5dQXmFrPeVoVh6IPd/Gop/ksdSVp7uin56IT6Ev+6Q8wJxC57W1Vn1M9knyuGIf0BrjVkad+pb3QmTqDGpD9oay4OdliqK6FbA1O7QiMylfLdpqCsJIZLarjdgtmfR39uW9FuHE7m/OBI0EYwKemPmEZ/tQ3WG8NljhsbdzFfnsMaFHFIFyRdFP+qjIEpnpo4AL/qr+Yt61Gvj2GYoKOVspZ6ryENBSG7JpBp2ORT/+yLE1xLmFZi4toTfXUBRHdEfaMsdRf3SA3t8n9sSuyLS3AnZFsVRcYu9EpiB0MGQvC36FeXOVuquONaP7nuuBRh9HF40StcfDZ7m86YS6wM2WFdNRTIBFJhMkTht4Gn499Nee7SX1e7Db5F6ua6oAdn0YopkxKioPAm2KbjmRUvQjYbAAyUPJv1GZu3XkW+Dz+ygXThRJaFYd7txPOlopV7QI7WvRT3dotO4uQYMowiS63F/6bVS9njFaMYddgKMlNtMVmju8zuJTu+9brqRjK89g57Uqi360RorChhKdSV1hXlcYPpFMpX2R1kBR/khINQogLDKZoHH3tLuRFU8fmE7RhfuyBqBN+pzd8hbbzIQDyk4/8enkiRcJRT8SOneSfVwqcM4zwMCp8m2l24H/3ObuI6wnMVR9VF3UEI00Vqh/x4xx/reksmaQp2ZiPmBMoHPb1qD2Lq084G4xGYPRTOXFjM7UN5GvLKBqqgi+yFR6zcanhL6IRaMBkhQV5vXFFGkNh++PTh6ZRo5khnoCTOwQr4/HU6c+5dk+inb8PTGRNgo3ArtWAY4Yrj4NB4oVRT8pQwC9MTQ9in3BEE/tEO2NwC+eBIadI99WfQT4+I8U+TBZ3V6axymvKxy+pAKB8sLBaKWlxv4U/fSE0UrL54m55O/YeIIinBKVB6jdnjJSHisoI5m9bSkpkRBiQ/ZwMWJXkjJEHjeUUcFNOCyZ1xTI4/hU+uwMtSgPMSwymaAyNXMqfjfqd57ttxKs2Gw0AhCB1dcDf5sAfP4A+eoxwUdZ9JMaYUU/EtZMaj+nNVKO4Gl/AiZeId/edAJY80eK7sW7i1bqikiAhqJyN9AoW4QmD6b0h0AVImj1JACS82n5PDFPFiXOdloyb2+KXkHfFcpIpsnWt5zGUBuyqyKZbnPxUKOsMG8sp3zXcBCZynMVlxzzRuwAi0wmBNw66Vbk2SinRhQEPJiWjGYpP6m+GPjx78A/Twbe+AWw6f9RVTATeOytVAUskTaaooKRht5M+ZgJObTUX1cITL4GOOUWAO7XWVsD8MmdQNlOwJZJr7v6YqDqIEVFoomSrfI4ZXjguzcJAi3xpg4DUodQoZHEiT0kBpRFMLGAsm+5sZd9yyWURS6hMGSvK5THlvTwEE2pI+F5L7sc5E0ZKqN6Jcr8VbPbIzMczlcIYZHJBB2TzoRnTn0GGnfnnyK9Hs8NGAzPhwZABQSFG4H/3A68OBpYfQNwdF10RprCheM/ytEAQQNkREg7SW/ozSRyEgYApgT6ohx5AXDWI+rI2pePAIe+pi/yxnLqIFJ5IHwKCXylo1tARhDdAkwJtDScOV7eV3mQIpmxlpep7KBlTuhbNFBpyO60qx8rGISTEbuEOUFddV97LPSRTJdT7ZEZn0YRay78YZjgMy5tHK4fd71ne7XejnljpuOVkTOxKyEDKinZ1gDsXAG8dSHwyhRg7TNAbVGnx2R8wNEOFPwgb9sGUMQlEpfLJXRGiqLZBgCmZBKauScD5/9VjtCKLuD7F4Ftb9OyZGs1LXlJRSqRTm2huv1jxtjg/k2NVmDgFHm76hBF42JOZCqXyxP7FsmMT6NiIYmGsuBebKt8H0NsxC7RscJccjQIJfZWtci0ZMZ8PibAIpMJIb8f/3uMSJJza/Y1l+DVtkJcnmzEnKEj8cigMfjaYpWX0gGynfnfs5S7uewS8tgMh2WSSKehhKqvJVKGUBvBUPrh+QOdgYRmQjZVy9cWUYTz4pcpv0xi67+A9X+jZV57s2zaHumpGsoWoeZkEgnBjE4bLBTJlESSywGUuvMyY2lVQikyzYl9e1/p9LRMLRFMQ3aXq4PIzAoPkanVuTtOuakrCn0ks+mE2u/ZFibnKsSwyGRChl6rx4tnvIihiUM73VbpbMYHaMBtaUmYlZ+H3+fk412rBSU694ezywEcWQusvg54YSTw79uBku2dHofpBe3NQFMVcGKvvC91eGRHMZVodUDKUBKQ1kzK37KkA7/8h/qLav9/gS8fBeJSANFJkc+qQ+p2gJFG8WZ5nDKEBGZv/Rn9gd5EUWNloUblPvoybo+haKay209v+5YrsWTK44bS4FWYN3coNLJlhU/3mmTF90Z9CeVSB9veSUn1UXmsM8k5mTFOmLxamFglx5aDDy7+AB9c/AFuGHcDRiaPhAD1l2A7XPhB58LTqck4J2cAfj1wAF5KSsA2owFOgJYDN/8/YPFpwKunAj++Qj1tmd7RUAoc/FKd4J8xNjKLfrpCo6V+27ZsWj6vLyXBdfHL6pzBwg3Af/9EBUOC1u2leVjdVi+SULkFhOjCwWgBshQdvioOULQ4llp7KlMW4pL6ntdoVYjMxhPBE1PKQhadCTAnBed5e0OqoqCssZy8MkMZzaxRiMz4NBKYvFwOvxng7dixA59//jkKCgrQ0tKCN96Q+1Hb7XZUVFRAEARkZWV18yhMLCIIAoYlDcOwpGG4ZdItqGurwxfHvsDXx7/G1hNb0exQ58Yd1GtxMDEBSxITkOR0YlZzC05vbsEpLa2wlO2kiuGvHwOGng1MugoYelb4XH2HG631tBy2WdE/PnU4kDYycot+ukKjIQsfjZYKm+qLaMn8/L8Aa58Cjn5Hx1XsI4uj8/9Cdj+1hbRs6GynAqFgRgJ9QRSBE7vl7VC1CDVYgQFTgC3LaLvmKBnE2wZ0f79ootnHSKbSkD2YIlNlyZMSHkU/Emmj5HF7I6UkONqBUOk6lZ+o+1xxJNN3kVlXV4drr70WH330EQBAFEUIgtBJZE6YMAE1NTXYvn07xowZ4+vTMlFMgjEB80bMw7wR82B32bGpbBM+PfopNpZuREmTujtLjVaLNVYL1lgt0IkiJre24Qy36MzZ9x9g33/oA3rspcD036uNjRmKYm79l2yxImiACZfTFXi0LJcrEQSq1hW0NK4roujMnEeA9S8Dez6i4+qLgY//AJz7LC2t1x2nIiGXnbraRMJFS8U+dYFNxtjQXDgYLUC62w6rvYnOY9l2uphx2sNLuAQCp11tQB+X3A+RqfTKDKIheycj9jASTbYsyvmVeoXXHAEGTOr+PoGkkyDnSCbg43K53W7Heeedh48++ghxcXG44IILYDKZOh0XFxeHa665Bi6XC6tWrfLlKZkYQ6/RY0b2DDw+83F8funnWHPJGtw04SaMSRkDraBOnncIAjaaTfhzShLOz8nGxQOy8EJSIja1V8Gx/m+0lB4rvap7Q3M15RzuXi3vG36e24Q9yLl7wUQQgMQc6kyTmEtdaFqqgZm3AlNvkI9rqaE2lFWHqGCmrpDEZ/VhsisJd44rOv1YMkhMh0Jk6owUEU5X9DGvOgQ4YqTFZMec3rjkvr+3EhUis7kqeIU/KiP2tPC6INCb1fZOtcdDu1zeyYhdF/MtJQEfI5lvvPEGNmzYgCFDhuB///sfsrOzkZWVhRMnOvt4zZ07F3/961/x3Xff+fKUTIwzKGEQbp54M26eeDMa2hvw9fGv8VXBV9hSvgUNdvUX1lGDHkcNeryZaIPN6cTIdjsyP7se2UPORnZ8NjLjM5Ftof+NsfZh4HJRFPPHV2TrD6MNGDu3772VIxVbthzRrC2iCNtJV1IXoP/9hYp/7C3Ap/cCZ9wD5J3qLjBwUuFZ8pDw9sAr6Vj0Exe6CweDOy9T6j5UIRX/NNIXcjSjbCmp0VP6QF9JyJHHLVXBs+tRCqdwMWKX0BnIxkhKCakrCp2NUUePTEsmFRxGwopHgPFJZL777rsQBAEvvvgisrOzuz32pJNOgkajwb59+7o9jmF6i9VgxSVDL8ElQy+B0+XElvIt+PTYp9hQugGFDYWqY+u1Wvxk1gKNh4Hthzs9Vqo5FVnxWciKz/IIz+z4bM/YZrBBiKbIXnMlcOwHtTfm1OspUqEzRVfRT3dYM9x2MgJVndeXUq9zcxIZtTtaSWyufRqYthAY8yuKZooOEqVpo8L3i6R0hzxOHRHaHFvJL/PnJbRde5zOta37742ooGNLyf5cmCTmy2OXkwpd0kd1ebjfUBmxp4dXJBOgCz2JumIq/hHF4F9MOdrUVk/WTI5iuvFJZO7cuROCIOAXv/hFj8caDAYkJCSgqqqqx2MZpq9oNVpMzZqKqVlTAQCF9YX47Nhn+F/R/7C7chccYvfLm5UtlahsqcTOyp1eb4/Xx6tEaMdxqjkV2kjxlHQ56QP5x7/L+1KGUYFUfTGJkViIZErEp1IuqqBxC81iYOBU4KJFFMVsraXjfnqN8uGm3UjH6OKAtrrwqriVcNrVLULTR4f2b2q0AsnDyYhcOp/lO4GM0fQFHc25a0qRaexj33KJuGRyPLA30XZ9Ka1GBPoCRxmds2aGVyQT6FBhXkY2Ro7W4F9QNZSQwJWwZoX3KkcQ8UlkNjc3w2q1wmDo3cm02+3Q6fxW0M4wXZJjy8EN42/ADeNvQLO9GZuXzkFp/XGU6LQoSM5BYUIWKloqUNNaAxFij4/XZG/CodpDOFR7yOvtOo0OaeY0WAwWxOviEW+Ip//13f/E6eNg0VtorIuDWWcOfMS0sZw6KNUWyPtO+T+guQKIS3N/oXXOrY5q4pLdX/4aoL6QxGbKMOCXfwc+vVuO6Oz+gHLipt9IPo8tteEpMkt3qFuEpo+ivMhQodUDRnde5vH1tK/ykNvKqCHKRaYifcxk63vRD0Bi0pIu2+RIhuyaAJ43l4s+KySsWX1rhxkM0kbK46ZKei2FQmQqPTL18fSZwJFMAD6KzNTUVJSWlqKxsREWi6XbY48ePYrGxkYMHdrZeDtUrF27Fs8//zw2btyIxsZG5OXlYd68ebj33nsRH9+/5cLVq1fj73//O7Zt24b29nYMGzYMv/vd73DrrbdCrw+zN2iMEKePw6zJNwFr/o92NDmAeZ8BJivsLjtKG0txtP4ojtcfR3FDMUqbSlHRUuGJbtp7kWTvcDlQ2lQKNPk2V42gQbyOxKc3UWrWmWHSmmDUGel/rREmnQkmnXusuM3rPmigqTok28kAFMFMzAFaGygfUZn/FUuYEoDUIYBGWjovIpudX74CfHYvULGfjjv6PyoUmraQojvBiCj1laKf5LFtgHuZNsQXDgYLkD1RFpkVe6kRQFuDuvtStNGoFJmJ/ROZAL3WlCLT2R5Ycd50gnKPPc8fRkbsEilD6Hy6HABEoKYAyAiBe03NMXkcn0r5mNF84dQHfBKZ06dPx0cffYRPPvkEl112WbfHvvzyywCAWbNm+fKUfuPll1/GrbfeClEUMXDgQOTk5GDPnj148sknsXr1anz//fdITu5bQvqf/vQnPP/88wCAIUOGID4+Hrt27cJdd92Ff//73/jiiy9gNPILLySM+RXw6T0UOWlvAna8D0y7HnqNHrm2XOTacr3eTRRFVLZUoqC+AAX1BShsKERJY4lKhDba/Wcq7RJdaLA3dCpi8id6CDBlJsIoJsAEwGSog3HLszDqLTCZEmEwxMOgMcCgpR+9Rk9jL/uMWmO3t3vdpzVAK2ihFbThl+dqtFIEU1o6ry0kAX7hi8BXjwKFbvFWthP4/gXgopcpomlKCOm0O9Gx048uDNwCjBZKQ5BoKKWKfVt2aPLogoUqJzOh/9FAq8JjuikIXplKI3Z9HLXDDDcMFrqIklZl6gpCU2He0SNTow+//NUQ4ZPIvPbaa/Hhhx/ioYcewqxZs7os/nnttdfw0ksvQRAE3Hjjjb48pV/YvHkzbrvtNgA0txtuuAGCIKCkpAQXX3wxNm/ejBtuuAGrV6/u/oEUfPjhh3j++edhNBqxYsUKXHzxxQCAffv24fzzz8d3332H+++/3yNCmSBjtAKjLwG2v0Pb2/4FTLu+x7sJgoC0uDSkxaVhSuYUr8c025tRUF+AooYi1LfXo6G9AU32JjTaG9Fkb0Kzoxktjha0OlrR4mhRjVud9L9LDF4fZztE2LUaeGRsi2JJrC5o0wAA6AQdtBoSnFqNFnqN3jPWCp23dRoddBqdZ59O6LytPFYraKERNPJYo1E/p+KxVWOXC9rmKmjb6qGt3gFtfBp0J82F1qiDpngLdACE5mJoD34EwVkHrTULGkEDjaDxiGdv/2uggUajof8F7z8d7ycd2ydBXqbILQ510Y+EwUoFLPGpsvAq3w1kTaRK81Au5wcSZXV5X/uWKwm2IXs4G7FL6IxkYySJzNrjoakwr1UUmsa520nycjkAH0XmBRdcgLlz52L16tWYMmUKrrjiCrS0UIP4xYsXo6CgAP/5z3+wa9cuiKKIG264AdOnT/fLxH3hiSeegMvlwoIFC1SiNzs7G++++y5GjhyJDz74ADt27MD48eO7eSSZxx57DABwzz33eAQmAIwcORJLlizBnDlz8Morr+Dee+9FWlqaf38hpndMuUYWmSVb6QvOD0srcfo4jEoZhVEp/av2FEURrc5W1LfVo769HnVtdWhob6AfewMa2xs9orXF0YJ2ZzvaXe30v7Mdbc42tLvaYXfaVdvS7c4eip5CiUN0wOF09HxgOJGZLo9LP6efICBA8C5MoekgSgVoTA3Q5GRDIwJC7Xpov94BjUbvVbyqnkMQPG1dPf8r9sn/Carblcd3ehxBkI+3N0NIT4HQIkAQAaHqB2BrCQSDFYLOqD5WkJ/Hs1/xXMp9yvMgnRfl+QLgdX9HEd/V4/gUcW8pABLctkWOCmD/+4C+swCR5qUcS8+rETQQXLUQrBYIAIT2UgiH10ATn9LpXCj/l+bv9Vz2cI5R/D0Es4mez2KFULUTaC3r9Bje/v69pTf58F3heX3ZkiG4VwiFuiNAxXYIotwlruP8enpdq+bl+U+EKIqq25T70HAUopFqU0SjEag7BFEn/3b9+T07nk9vr8GejhmbOhb6EOfRCqLnLPWP1tZW3HDDDXj77be9ngTp4a+99lq8+uqrIS/8aWxsRGpqKtra2vD9999j5syZnY45++yz8dVXX+HBBx/EE0880eNjHjx4EMOHDwcAFBUVYcCAzl1lhg0bhkOHDmHJkiW47rrrfP9F3EyePBlbtmzBpEmTsHnz5p7vEMuIIvD3KWQEDZBlzwXRH1l2uBxobapA80+L4fjxJbQKAloFATVTr0GjMR7N+ji0mhPQotOj1dFKItUtUNtd7bC77LA77XC4HDR2yeOO/3fc1+5sh8Pl8OnLhGEYhuk73//2eyQY/Z/K0xfd4bPiM5lMWL58ORYuXIglS5Zg/fr1KCkpgdPpRGZmJmbOnIkbb7wRp512mq9P5Re2bt2KtrY2GI1GTJs2zesxs2bNwldffYUNGzb06jGl4wYPHuxVYEqPeejQIWzYsMGvIpPpA4JAvcy/fIi2d38EnPNM1FtN6DQ6WBrKYdn8FuBwRzVzTwZyzwQgAEmDgLQRAcuJE0URDtGBdkc7WpwtaHO0kQh1RzE7ClOH6CBRKzrgdDlhd9nhFJ2efQ6X4sd9jFN0eh5D2pZ+XKILThf97xJd8j6x8z5prDre5YDL0QqXyw6n0w4XRGibKuAC4IKAdpMNTq0eoiDAJbogiiJccHnuL22LokjbLLgZhokR/BZWPPXUU3Hqqaf66+ECxoEDBwAAubm5XVZ7DxlCBq/79+/v02NK9/PHYzIBYsLlwNePUTVicyWw52Ng/LxQzyqwNFcDPy0m6x2AqjGnXEuVvUmDqLAlgEUXgiBAL+ihN+gRjwg1eXe5qLK3oYQ8Ctf9Va42H3MycNqfep16IYlNSYh6RLFCHLtcLjhER2dh7HLS/bzcLooinKIT+rXPQle4ES4BaMmeiOaJV8BlzVCLbagFME1MvRSonKvqVMDldRnRc3sHIS3dX4QI0eWC2FCKhO3vQ9vWABFAw4CJaMkYC1jSIGoNEEURnn9ih/9pkl5vk86LCPn8iqKoEvkd79fVMdK5kcb9R4Tt0FpaRQHQmD0ervjO6VKq5Vgvvz8AiE474gt/hgjAJQBNaSPgMlq9nh/pb9TpMTos+6rGinMLAPr6EsBBPdLtJhucJitECKrXRsdxf5bM+5OKoFq6djmha5b9t+1GG+C+6PO27O3539s+5bw6LK9LqQWd9rlc0LXUQoAIAYDdlABBo/Pk3nZMK+nL7+fZ7sWFacf7dEyFCRUxZ1pZXU19ZLurHJduq6mpCcljvvbaa1i8eHGvnnvv3r29Oo5xY0kDhv0C2P9f2t66PLpFpihSpfGOFfK+cZeS0IxPo443sdLdxxc0GiB5sFuMa4H0MbLILN5Egr29uVfFK1JepBbuAhB/e/hXFQJt7uKHjKlA3llAwsDu7xNMKvYDBbuBQ1/SdrMTyDkHSB8ZfR2AmquB9YPk7QvuAvI7p2j1ClEEns0F2uppe/qjwIQrAmcr9PxI2Yz9vNuBydeE56pPezOwaJxcYDX7OgomJHhfVfQ7+z8D3nW76xitwG/+SU0GLOnd3y9G8ElkajQaZGVlobi4uOeDAQwaNAiFhYVwOEKX6N/aSvYG3RnISzZDUhFTsB+ztLQUW7Zs6dVzM/1g8jWyyDz2PVBzHEjybmEU8TRVAuueJ+NmAIhLBYafD2i1VDFqjbIv9UAiuFMLBC0w/BwyZgeoorX6KH2phbpCuqVGbbKfMSb8ujcZLED2SbLIPLGHepi3Bc62K2Qo7YsELflk9hdBACwZsshsKA+cIbvLqfb3tGaGZ3U5QP6vSbmyyKw9HlwbI6VHZlwqnadw64wUQnyOZPa1bsjHOiOfMZnIkLi9vb3LY9rcUQCzuXe2H/5+zKysLEyaNKlXz713795ei2HGzdA59GHdWE59qbe8Bcx5KNSz8j8uJ7B3DXD8R3nf1GvJxDkhn6Jb4WauHO4IApCURzYp0msIoIuVrHGhj8QV/kQ91QH68k0ZEn4i0+OXKQAQgdY6oOowNQFwOsjIOlpQ2hcZLb4LNUsGUHWQxg2lgTNklz4bJazZ4etjqtHQxV+xOzBTVxhcG6O6Dh6ZWgMbsSsI6ru5vb0dmhB/qSUlUQs4aYnbG9Jt0rHBfsyFCxdi4cKFvXpuqcqL6QMaLTDxSjLTBqjF4pkPRJ/gqiuUf0cAyBgLpI2ilmfxaeFnIB5JmBOB3BnAno9ou/hnoH0BYG8NbUvOok3yOCmPhGa4feEZLJS2kpgjm1hXHQRyppGxfTi26ewv/uhbrsSmMGQPpFem1EYVoHSacP+sSFbUQ9QVUx/xYBn8d/TI1Og4kqkgaN+qtbW1OHHiRK+FW6CQrIaOHz8Ou937G/Tw4cOqY3v7mIcOee9r3Z/HZALMpAXyuPY4cPib0M0lEDjtwMbXFIbKAjD5KncuZipgC6McvUjElAAMPl3ertxPy5etQXaz70jpNnmcMpSimOEWgdJoqb9zxlh5X+V+dx9z/3XPCguaKuSxKcH3SKZNkWcYyK4/kWDEriRthDxuKKOIeLCWzJWdkSwZFIn39WIiiuhTJHPHjh3Ytm2bal9LSwuWLVvm/Q6g5fHa2lqsWrUKLpcLJ510Ur8m6i9OOukkGAwGtLW14aeffvLqk7lu3ToAwIwZM3r1mCeffDIA6s9eXFzs1caor4/JBJjkQUDeTKDgB9reshQYdlZIp+RXynYCm9+Ut0eeT/lglgz6ooqmJclQoDPSkq/BQvmELif15E4dSsVUoUAUgfJd8nbayPDo9OMNowXIniTnRpfvJYEZbXmZUjoF4G4p6eP7TlnA1VRJy+WBQNUmMTX8I3NpikYYbXXkpOFoC/zrXxTVUV9rJnf66UCfXvEffvghHn/8cdW++vp6XHPNNT3eVxRFCIKAO+64o28z9DNWqxXnnHMO/v3vf2Px4sWdRObBgwfxzTcU1br00kt79ZjDhw/HuHHjsHPnTixevNjT/Ufim2++waFDh2AwGFTdgJgQM/lqWWQe/BJoqgbi+9avPiyxtwLfPkut+gCqeBxxAS3lxKfS/4zvmJNJaB5ZS9uFPwFjfk22L6Gowq0vVX/hpY8Ov3xMCYMFGDCJimFEJ2BvoiXzxNzQnb9AoOpbnugHkZkjj5sr5YI+f1OnWAKOTwv/SGZiLqAzAw73Z15NARWXBRp7c4cCqazoee36iT4tlycmJiI3N9fzA1CFuXJfx5/8/HyMHz8eV155Jb799lucc845AflF+sJDDz0EQRCwfPlyLF682FOMVFpaissvvxwulwuXXHIJJkyYoLpffn4+8vPzsWrVqk6P+cgjjwAA/vznP+Pf//63Z//+/ftx/fXUI/vmm2/mlpLhxKiLKE8KoKWVbf8K7Xz8xYHPgIOKVocTLiexEZcaXlY2kY4pAcifJW+X76IoSqiWzIs2ymODhb54w1lkxqUAyfnyvqpD7iXzKIpmKpfLzYm+i8ykPHncUgu0B6joU7lcbkkPf5GpN9PrXaL2WHCWy2sKOhRIZXEkswN9esXfeuutuPXWWz3bGo0GaWlpOHr0qN8nFkimTp2KF154AXfccQcWLlyIJ598EqmpqdizZw/a2towYsQIvP76653uV1BA1iCNjZ3zhubOnYvbbrsNixYtwsUXX4whQ4bAYrFg165dcDqdOPXUU/HMM88E/Hdj+oDeTJ6Rm/4fbW9/Fzjl/8Ivh60vtNYD3z4tbycNomVJayZVPodbEUgkY4gH8k8FNHqKKNmbKZqZkEOFLcGmSNHeLWkQ/a1DWYTUHRoNnb+McVRZDpB/pr2Zin/iU0I7P3+hMAn3SyRTKaQgUnOAzN41AegT9aXy2JIR/svlWh2QlE+5vUDwKsyV9kWmBEoD4c9YFT4V/jzyyCO48847/TWXoHLbbbfhyy+/xHnnnYempibs2bMHeXl5uP/++7Fp0yakpqb2+TFffPFFrFixAqeffjoqKytx4MABjB49Gn/+85/xzTffeKyOmDBi8tXy+MQeMi6PZDYulk3CAeCk39HyeFwyLXsx/kMQKP8yc7y8r3Aj5RY6Q+AFXLpdHqcOpeKacMZoBbIny9sVe+kiKZoimUqRaU7yXWQa4tVemw0l1I3K3zQoRKY1M/xFJgCkDJbHdUVBimQek8dxqXTBGe5R3yDj0yteWiKOVObMmYM5c+b0+vjeeHzOmzcP8+ZFcQeZaCNrAnVvObGbtje/CQycEto59ZeaY8CPf5O382dRgVN8OkXXIjlCG66YEqiDS4n74qR0O0XiWuuCG41zuegiSSKci34kJFN2jY7avDragMoDtIRubwn/+feGZoWtXVyK7yIToAub1loaB8KQ3emgynUJS6Z/5h1oUobJ4/oS+j2c9sCKPmXjg7hkEuO8XK4iyowBGaYfKKOZe/9NbcoiDVEE1j4t5wPqTMCoiymfypoR+k400YrR5rYycgv45iqgfLcsAoJF9RG18XfG6PAXaYZ4wGwjqyWJ6sP0/ouGaKYoAi1KkemHSCYAWBRemQ1l/q8wbyyXDf0BSrOJhAvUtJHyuKmSiskCHc1U5q7Gp5F1EUcyVfjt8mT9+vX4/vvvUVRUhKampi6jfoIg4I033vDX0zKM74z/DfDFg2Tg21oH7Fql9tGMBI5vAHaulLdH/5KKfMwpFIlgAoNGCyTmk1CSOrEUrAdyTyZbo2D55RX9JI/NydShJdx70gsCRTMzxwMV+2jfiX1AexOJzEjv/dxWTxFaCXOyfxo+BNqQXen7aLAAJpt/Hz9QpA4HBA0JZNFJNkzpo4FABhY7FkjpjJEhyIOIzyLz4MGDuOKKKzp1nZEsi7ztY5HJhBXmRGDkBXIv6q1vR5bIdDqALx6Qow+WTGDwGfShl5gTfZ2Mwg1TAnX/kURm6XaKxrXWBc8uqljx+ZuUHzmt7QwWYMBk6roF0HJ5Sw15jwarY0ugUNoXQSCR6Q8CbcgeaUbsEqYEuriqd88/0BXmotghdzUrMnJXg4xP3z5VVVWYPXs2Nm/ejPT0dMybNw+iKMJkMuF3v/sd5syZA4vFAlEUkZKSgquuugoLFkTQlzcTO0xReL0WblQXz4Q7295WFyyNn0dfRJZ0Kq5gAovJBgw+U96uLaD82GBaGZXtkMepQ8PXuqgjRguQNV4WxC6728qolYRmJKPMx/Rn1bHKkL3C/8vlSiP2+AgwYpfQGYAkRfV9zfHAisz2JrVFlTU7cs5VEPFJZC5atAjFxcWYPn06Dh8+jPfeew8AkJCQgGXLluGLL75ASUkJ7rrrLlRWVsJsNuPNN9/s4VEZJgTknaowOhaBzW+FdDq9prWecjElsiYCA6dRBMKaHbJpxRQ6I5A+kqxeJAp+cC+XBqDytyMOO3Bir7ydNir88zEl9HEUzUxVtAWskvIyI11k+rlvuUSiwpC9KQCG7Coj9nSqmI4UkjtWmAfQxqjmmCJ3VXAbsUfA6kGQ8UlkfvLJJxAEAU8//TTi4rxfOcfHx+PPf/4zbr31Vrz22mtYuXKl1+MYJqRoNMBJ8+Xt3asD1xfYn3z7LNBYRmNBC4z9NVmOJHDryKBiTgRyTpa3S7aRUGoPQgFLxR4StBLpoyOn0EsQKHc0U9H4omIvYI+CFpONiiiX0ea/Cu1EhSF7Wz1F1PxJpBmxK1EWkdUXU559L1xh+kWNwh/cnEjvOY5kdsInkXn48GEIgoBZs2ap9re3dw7f33vvvQCAxYsX+/KUDBM4Js2nxHGAqjalvsrhSuVB4GdF04BhZ5O5dVwKefIxwcNTZe6mch9V6bbUBv65C3+Wx5Z0WuKMlOVygFI6Bir8MqsO0VKzvYmKpyIVpQ2QyeY/sZaQC4+bAaAu1PEHDRFmxK5EGRFvLKMof6CWzDt6ZGr1HMn0gk8i0263IykpCTqdfIUWFxeHhobOV6AZGRlISEjAjh07Ot3GMGGBLZsKZiS2hnmbyc/ulfOxTInA6EvIm1HZ35gJDoZ4KmAxWGjb5aSK/7b6wEVSJEq2yuOkwZFT9CNhsLijr+5qeNHlXjJviexopkpkJvovkqk3qgvK6kv9m5ah6vYTYcUs6QobI3uLW2gGaMm8poNHpkYfWecqSPgkMrOzs9HcrPYUzMjIgMPhwJEjR1T77XY76uvrUVcXor6+DNMbJisKgI6sVX/ghhMHPgcOfSVvj/kVVRXbBlACPBNcBIGix0oj/5LN7iXzAOcWlu+Ux6nDIiuKCdAyo95MuaQSVYcARxDOXSBRVpf7o2+5EmX+b0OZ//IynXZ1MYstI7LSbqzZ6o5INccCJzJVaQXu8xQsy7IIwieRmZeXh9bWVhQVySd76tSpAIB//UsdBVq6dClcLhcGDBgAhglbRpxHy80AfeBuXRba+XjDaacopkTKUGD4uTTv+L63Q2X8hMlGBWQSZbuA1prAVpm3N6udENJGRk4+phKpylzixJ7IN2VX9S1P8q8AsSq8bxv9aMjeUAZAEXmPtOJBnRFIUuSs1hYEbrm8vqPIjKDVgyDik8iUcjG//fZbz7758+dDFEU8+eST+MMf/oDXX38df/zjH/HHP/4RgiDgkksu8eUpGSawaPXA+Mvk7R0rglMh3BfW/506vAAABGDC5fQhl5jb7d2YAGO0UYtJqRrX3gwUbQ5sXmbpdvlLVNDQcmGkVJYrMViBAYoocM1RMhpvb4mMAjxvNPm5b7kSm0L8+dOQXZnfabRFngWaIABJg+Tt2uOBiWSKonqVy5rJK0hd4JPInDdvHnJzc/H111979l1wwQX47W9/C4fDgVdffRW///3v8c9//hN2ux0jR47Eww8/7POkGSagKJfMqw4Bx74P3Vw60lgBrPurvJ0/k4zArZmA3hS6eTEUqbJkAJnj5H1FP1NumL8rgCWKN8ljWzZFzCJtuRygSGbqMDLUlqg5CjiaIjea2aIQmXFJ/rUCsnX0yvSTyIxUI3YlKUPkcX1xYCKZbQ1qiypbNkcyu8AnkTlmzBgcPXq0k/fl22+/jddeew1nnnkmhg4dismTJ+PBBx/E+vXrkZCQ0MWjMUyYkDacijgktoSRZ+YXD8h5avo4YOJ8SjpXLp8xocOUAOSdIm+X7QRaGwK3ZF6yTR4nDaJoSiQV/UjozfSTPlreV3kgsot/mmvkcVyyfyOZgTJkj1QjdiWpw+VxfSl1RPN3NLxaYV8kaLjbTzcEJKNXEATccMMNuOGGGwLx8AwTeCZdLXfROfApLXmaE0M4IdDS644V8vaoi6m7S0JOZLffiyaMNmDQGcCPrwAQKdpxYjdgy1QvcfoDUQTKd8nbqcMjM4opYbCQX+bxH2m7fI/cxzzSaG8GHC3ytr/6lksoU2P8aciuMmJPi8xIprKArKWaLvAcrf79XVQemUmAzsTL5V3ATY0Zxhtjfy1/Ybc3AdvfC+18XC7gk9vhScq3ZQPjf0PLs0ZLSKfGKNAZqCOLcsmuaCPlZ9pbur5ff2itI6sfiUjq9OMNow3IUeRl1heRDZCjldpMRhLKpVRALib0F0pD9vZGipb7gzpFTmZ8RmSKzOTB6qhizVHA4efWmyqPzBR6Pl4u9wqLTIbxhtECjLlE3t7xXuD9Drvju+eoyENi0gISmP6OjjG+Y0oAcmbI26U7qEWiv5fMS7bIESyNLjLti5QYLbTkH6dwSKg+EpnRTKV9kT7O/+I/YYDcOAJQVzr7QkOJPLZmRuYSsCFO7RUciApzZVpBXCrlY0eiIA8CflsuX79+PXbs2IGamhrY7d2H7rn4h4kIJl8LbHuHxiVbgbIdQNaE7u8TCH78B/DtM/J29mRgyFmUl8W+bOGHyQYMOQPYtpy2awsommLL8m/ubJGi6CdhIFUCR7LI1BnlvMxj39G+ygPUIKG9AUBaKGfXN5T2RUarf/MxARI0camy4XuD25Dd1yV5VcV0hHX7kdBoyTO42h3lrz3uf5GpTCuwpNNrl1OWvOLzK//TTz/FzTffjOPHj/d8sBsWmUxEMHAKeVBWHaLtzW8BF74Q3DlsehP4/H5525wMnHwzJeWHOkeU8Y4hntrbWTKotSQAFG4EMsfTsp2/creUke3kwe6inwgUBUoMFiB7oiwyy3a5I5mNtJIQKV/kSkNzU4L/RSZAFywekVlOUW2ND0u2jna1OLZmRu5FbMoQ4LDb9aau0P82Rsq0gkiN+AYJn17533zzDS6++GI4ndRfdujQocjIyFC1mWSYiEUQgElXAV8+RNt7PgbOeSp4eW/b3gE+uQOePExzEnDGfVT9rqwuZcIPcwKQMw3Y+2/aLtvpzp2rpciHr7icQPlueTvSi34kjFZ116SmE+QDmZhLea1S68lwJ1gis8zdprnBbcjui7NAQylURuyRnIqTOkwe15cAzjb/XqSo0gqyWWR2g0+v/MceewxOpxNTp07Fu+++i8GDB/trXgwTHky8Avj6McDloGT+PR8DE34b+Ofd9SGw5v+ojzNARRGz/gRkn0QCk/N/whujDcg/XRaZFftILNmy/SMymyppGV4ibXR0iEyDhTwgrVlu0QPKy0wfRXmZkSgyjbbAiEybontekx8M2TsasRsiuKAwVdHDvLEcsLfRkrk/AgStdUCLwp7Klh2ZtmFBwqcEji1btkAQBLzzzjssMJnoJD4VGHa2vL3t7cA/577/Ah/eSMIWoA/7WXcCOdOpZVpccuDnwPiG0QYMmCR/UbsctGTe2uAfz76in+ULEJ0JSB4U2ZXlEjqDOy9zjLyvYh9gj7Din0aFyDQnBuaiUCky/dH1p1ZpX5Qa2Rey6QqR6XIA9X5cMq86Io+lBgwcyewSn0SmXq+H1WrFkCFDej6YYSKVydfK42M/KFo6BoBD3wCrrpHNlfVm4NTbgbyZtGTojygYE3g0GhIXSlP/8p3Uwaa13vfHlzxcAXpdGOKjI5IJUJV59kR5u3wX0NZMuZnh1uK1K5QWRv7uWy6hMmSv9N2QvZNHZgQLJ3MyiT+JmmP+E5nKz39zMnVa40hml/gkMkeNGoWWlha0tkaYhxnD9IWhcwCLuypYdAJblgXmeY6tB96/Uq6E1BqBmbcCg04nIWHN6P7+THhhSgDyT5W3S3cAzbWUl+krUi4e4C76MUZ+0Y+EweIW5+78uZYaoKnM3Z4zQqKZzQHsWy6hNGRv9oMhe0eR6c82mMFGq6NVH4kaP1aYd/TI1OgjW5AHGJ9E5u9//3vY7Xb861//8td8GCb80GgpN1Ni5ypqVeZPijYB7/yGihsA+tCaeQswZA6Ze7PAjDxMCUDuTPnL2t5M3pZt9VS401+cduDEHnk7dXh0LJVLGK10UaeM1FUdpvPX1hi6efWF5mp5HBcgkZmUL4/tzdSVzBeUFdOWCDViV5KsWGGtO+6/SGadwknHkk6CNlKr8IOATyJz/vz5WLBgAW677Ta8916IO6IwTCCZfJU8risEDn3lv8cu3Qn8a64cpdFoyaZo2C/cEUzuSx6RaPWAJRXIHCvvK91OLQd9MWavK6aKWYn0KCn6kdDqAYMZyFCct4r9dN4iJS9TJTKTAxMVtGapxY2ycKc/NESBR6aSlKHyuK7Yf5FMZe6qJYM7/fSAz5dXS5cuRX5+Pq688krcd999mDJlCqxWa5fHC4KAN954w9enZZjgkpRPeZEFP9D21uXAiHN9f9wT+4Hlv5SXUAUNMO33wMgLqGsFC8zIxmgDck8hM3+ARGZbA4nM/hZwFf0sjw0WICGXupxEEwYLOSkc+JS2y3dR8U97M0VywznK5mhXL+v7u2+5hEZDy9oNZbTtqyG70pbHEgXej2kj5HFDKa0++eO1U98h4hstaSoBwmeR+frrr2PRokUAgIKCAhQUFHg9ThAEiKLIIpOJXCZfLYvMQ18BDScAqw+FOFVHgWUXyvlbggBMvZ7aWSbkUIcYJrIxJVDHmg3/ACBS7lzFXvrb9lcQSIIVoLwzvTm6IpkAFf8MmEQXXaKLhHlDGS2htzWEt8OCMh8TULfJ9DeWTIXILOu/IbujTR19tWYGRhgHk/RR8ri9kWyeHG1+EJkKMW7L5khmD/gkMj/++GMsXLgQABAfH48ZM2awGTsTvYy6GDDeSTl1jlZg+9tU+d0faguBty4g6xGJSVcDYy+lL1IWmNGBIY6+iJIHy23uSrYCOSfT66ivXZtEsUPRzxCKpIRzZK8/GKwkzpIGyeet6iCQMYYEQ1iLTEVludZIgjlQ2LKA0m009sWQXSmcIJDBeKRjG0gR8XZ3Hm/tMcAx1be/R3M1vW89zzEg8iO+AcYnNfjcc88BAM4991y8//773S6TM0zEozcBY+cCm9+k7R0rgFNu7fsVf0MZsPQC9bLLxN8BEy6nIp9I7rTBdMaUAOSeLIul0h3u7j91fReZjlbgxF55O9qKfiS0Ovq9MsbK5+3EPmBoBPhlNilEZiD6litRGbJX9N8rU/lZZEqIjvQLvQlIzANOuDtj1RzzPS9TaV+k0VG6Ai+Xd4tP8fBdu3Z5lr9ZYDIxwRSFZ+aJPUDRT327f1MVsPR8dbeW8ZcBkxa4I5gsMKMOk41sqCRqjtIXXmsdRSb7Qk2BOlKWNjpyuuD0FaMFGHCSvC3lZdpb/N+L2p8oRaYpQN1+JJQV+L4YstcVyeP41OiJziUPkse1hf4VmXEp1AiBl8u7xWcz9oSEBGRl8dIeEyNkjVdXvW5+q/f3bamlCGbVYXnfmEtIuCYMBBIGdHVPJpIxWKkIQWkOXbyFbGfa+2jJU6i4qDEn0XJpNEYyAYoCZk6URZqjFagvdVsZhXE0s0mRAhOovuUSCTmK5/XBkF1ZMR0XRSIzRWljVOT7xYkyOBCXStX90Zaq4md8EpkTJkxAQ0MDGhrC+A3PMP5m0gJ5vP+T3nVwaWsAll1MRR8SIy8Apt/EAjPa0WgoojVwqryvfCcJzL56G5Yqin4S8yiSEm1FPxIGC6UTKIVC1aHwtzJStpQ0JQZWZHY0ZO+vyFQasVvSokc4pQ6Xx/XFgLOt76sHSmoUItOSTmJcEPr/eDGATyLz1ltvhdPpxD/+8Q9/zYdhwp/xl8lLJK11wK7V3R/f3gws/xXZ10gMOxuY8X9ugTmw6/sy0YExARik6P5zYi8tb/bFL9PlAsp2ydvRWvQjodFSKkDGOHlf+R45kumLWAgkzUqRGeBIptKQ3dFK3ZH6gzInMz4KjNgl0hQ9zJsqqT2pL0vmyrQCawa3k+wFPonMiy++GA8//DAeeughPPvss2hpafHXvBgmfDEnAiPPl7e3v9v1F569FXh7ntrbcPAZwKl3ssCMJUw2IGsKRecAwOVwL5m3UE/u3tDepI6Ep42IjgKN7jBa1P3fK9wi09FG5y4caerQUlIbQJHZsce4UgT1BWV1uTUKPDIlUocDgmRYLwK1R31bMleeX0tW9JynAOLTq3/27NkAyL7ogQcewBNPPIHRo0f3aMb+9ddf+/K0DBN6plwL7P6QxoU/UWQqY7T6GEc78N7lQMH38r68mcBpd5O4TMwBEyNo9YA5gQzGj62jfeW7gLbzKZrZm+Kdyv3qZeK0KOv04w2DBcgcRysHzjYqbKkvJnuatobwFNmqvuWJgY1kCgIQnw7Uu8VPQ1n//FejrduPhNFKn7VSLmVNgW8iU3mebNkcyewFPr36v/32W9V2S0sLNm/e3O19BM5fYKKB/FmUdF9XCEAEtiwDzntWvt3pAFZeBRz+Rt43cBpwxv2UR8UCM/Yw2YC8U2WRWbqDcjJbanvnKlC8SR5b0qllZbQW/UgYLCQUUoeRKAeAigNUfNfWQIIo3GjuEMkMpMgEKPIoiczGfhiy2zsss1uzoyfPUBAopUASmbUF/V8ub6pSF+rZsqNHjAcQn179jzzyiL/mwTCRhSAAJ/0O+PYZ2t79IXDWo+TN5nICH9wA7P+vfHz2ScCch4EkFpgxiykByD8FWKcnIWBvAsq302vC3tKzYCzZJo8T8ym6p49S+yIJjYaivFnjZZF5YjdgP5e+8H1poxgoWhSdc8wpgReZtmxASqnsjyG7que5QD3Ro4mUIcDR/9G4rrD/kcyqQ/JYa3B7ZHIksydYZDJMf5l0FfC/P1Pbu8YyEpWjLwHW/BHY/YF8XMZY4KzHqAWgshqUiS30ZvLWyxgjd2kp2w0MOpOWzLsTmU4HUL5b3k4eAuiNgc33CxcMFiBrMrDtHdqu2E/RN0crCU2TLbTzU+Jyqh0DzIlUwBRIlFHw/nhlKkWmOREwRFl0PHWYPK4v7n8ks6NHptbAkcxeEGaXgAwTQdiyqIhHYtvbwH//JH8ZAlTd+IunaMmGBSZjtAG5M+Ttkq3Upq4nK6O2eqDygLydNiL6l8oljBYgY5Scfyq6gLrj5NrQV5/RQNNSA0BRBBjIvuUSSvuzpn6ITGUxSzR5ZEooK8zrywCHvX+m9UqPzPh0usAL9AVEFMAik2F8YfI18vjQV8CmN+TtlKHAOc9S1wkWmAxAS+aDTgPgznlrriTx2N7U/TJe2S45AiMI9MUZ7UvlEvp4WjJPGyHvqzhA6Qbh5pep7Paj0fa9bWh/SFB8tvTHkF1pxB5N3X4k0kbJY2ebO5rZjyXz2o4embxU3htYZDKML4w4j5ZOOpKUTwIzxS0woyWRnvENo5UiT8mD5X3lu+Re5l1RskUe2waQeImVSKZG464yHy/vK9/ltn9qplSCcEHZ8tNoC47fZGKe4vmr+i4yVUbs6dHjkSlhSVd/RvfXxqhWaV+UwT3Le0mvE3oGD6YPxaFDh+KLL75Q7esLgiDg8OHDPR/IMJGAVg+M+w2w8Z/yvoSBwLnPAWnD6AuABSYjIQgkNHOmAdXuz8HS7bLItKR7v5/SyD8h1130E4b2PYHCaAEGTAG2uNu4Vh12e2U2A+0NVMUdDjR1EJnBWE5NUohMZzsJXWWXpJ5Q5mRaMgBNlIlMjZY+h6Wq/5p+VpjXK0SmjT0ye0uvI5nHjh3DsWPHUFRU1GlfX3/Cga1bt+Kyyy5DZmYmTCYTBg8ejFtvvRUVFRU939kLjz76KARB6Pbn1Vdf9fNvwYQF02+k9n4AVWae9xcgfQQLTMY7pkT3krmb6iNA7XGgtcF7rpijDTixR95OHRo7RT8SBgsZaxulIh8RqDnq7v4TRnmZTUHs9iNhTpI/fwCgrrjrY72hNGK3RJERuxKl6K493neRKYpAfQePTF4u7xW9fge8+eabAICEhIRO+yKNDz74AL/97W9ht9uRnp6OMWPGYP/+/fjb3/6GlStX4vvvv+9XlBYA0tPTMWzYMK+3ZWVFmTUEQyQPBn77HnDkGxIPKUNZYDJdY7QBqSMpatl4gvaV7yIXgtY6yotT0lKjtk9JHRFbUUyAcjINZiB9FFC4kfZV7AMyJ4RXXqb09wTcIjMIUUFBoAiklDPYUNo3a6dORuxRFskEgJSOFeZ9XC5vqgAcig5TtoG8XN5Lei0yr7rqql7tC3eKi4sxf/582O12PPTQQ3j44Yeh0+lQV1eH3/72t/jss89w2WWX4aeffuqXcfx5552HpUuX+n/iTHgzaBYtoRjiyKSdBSbTFVodLf8OnArs+4T2le4ARlzoXWSWbqc2lABFxlKHx57IFAQ5L1MSmWU7yTLM3kLdtcLhS18VyUwMTiQTIEN2j8jsgyF7e7M6F9g2IDo/u5RFY/UlVAAkir3/XZUXeToTYE7mSGYvibnCn7/85S9obm7Gaaedhscffxw6HX0IJCQk4J133kFCQgI2bdqE//znPyGeKRNRaHVA+kgu8mF6hymBuv9IlO8m+5m2evJaVFKsKPpJyKGczlgp+lFicOdlStQeJ4Fpbw6faKay8CcYHpkSSq9MyZC9NyiXyqWIaDSSrqgwb60FWur6tmRec1Qex6fS5300RnwDgE8i89prr8Udd9zR6+PvvvtuXHfddb48pc+sWrUKAHDjjTd2ui0pKQnz5s0DAKxYsSKo82IYJoYw2YABk0k4AYDopLzL9iZ1ZEkUgbId8nZiHkVSetPrPNowWsgOTFkpXHVYLv4JB5SFP8FoKSlhU3plVvTeB1JZzGJOit6Ll6R8dd5qTR8rzKuPyeP4NMpb5WBCr/BJZC5duhTvvfder49fuXJlSJeSCwsLUVxMSdGnnXaa12NmzZoFANiwYUO/nmP79u244oorMHv2bPzyl7/EQw89hN27d/d8R4ZhYge9mSKS2SfJ+8p2UhFLa628z94CnNgrb6cOpVZ2sWgCrY+j86aMSlXsla2MwoFmZUvJpOBFuxIGyuO+GLIri4Ti0qKz6Aeg94zSq7i6jyKz9rg8tmRwO8k+ENTlclEUez4ogBw4QB0zDAYDBg4c6PWYIUOoCu3IkSOw2/veFWDbtm149913sXbtWqxZswZPPvkkxo0bh9tvvx1Op7PnB2AYJjYw2YC8U+Ttkq0kMNsaqHADoMiY0gQ6ljr9dEQQKIKbOUHeV7qDxIKjrXOaQSgIdt9yiYQcedwXQ3aleIpGI3YlSYPkcW0fbYyUXqLWKK3ADxBB9cCorKxEXFzoEtarq+kDICkpqcuinuTkZACAy+VCfX09UlK8GG17ITs7G48//jjOOeccDB48GFarFQcOHMA//vEPvPrqq1i0aBH0ej2ee+65Hh/rtddew+LFi3v1vHv37u35IIZhwg+jDcg9hSqQXXZa9q08QEvibfWU01e8mdooAhQ9SRoSm0vlEkYbMFCRl9lYRsLO2U6iIZTnRhTdbSXdmBOCF3FWemU2V1FhS2+IdiN2JSlDgYOf07iuqG+RTKWXqDWbI5l9ICgis66uDkuWLEFzczPGjx/f8x0CRGsrXbkYDF1fhRiN8ounpaWly+M64i3Hc9y4cfjnP/+JQYMG4Z577sGLL76Im2++Gfn5+d0+VmlpKbZs2dLtMQzDRDhGKxCXDGSMAUq30b7y3UDOyZSXaU4ESrfKxyfmkXtBrEYyAcrLTMwhP8fGMtpXdYjOob0ltCKztU52AQCC07dcQrkU7HKQlVLK0J7vp/LIjFL7IglVhXlx7yOZoqg+T2zE3if6tFz+2GOPQavVen4AoLy8XLXP209ycjLuvvtuCIKAuXPn9muit912W49m595+zjjjDM9jmEyU+Nve3vVSQlubfHVjNvvnw/zOO+9EdnY2HA4H1qxZ0+PxWVlZmDRpUq9+/DVHhmGCjKf7z3R5X/FmWi5vraN2iWW75Nukop9Ysy9SojfTOUgfLe+r2Ofu/tP7oEBAkDrKAPS3jUsO3nObEtSvi/peGrLHghG7hDKXt/EEYG/tXe5qx2r9hJzoPk9+ps+RTGVepSAIvc6zNBgMmD9/Pu69996+PiUAwGKx9HrpWonSPD4piVqP1dTUQBRFr0vm0pK6RqOBzWbrdHt/0Gq1mD59Oj788EMcPHiwx+MXLlyIhQsX9uqxJ0+ezFFPholUTAnksfrTYgAiVQbXHqP+5k0nOhT9DIvdoh8lBguQNYGaHwAU/bW39K9VoD9RVpYbrMFfUrVmUvcooPeG7LFgxC6RNgIQNJR+IjopLzNjdM+/s9IjUx9H/qe8XN5r+iQyr776ak9kUBRFzJ49G8nJyVi9enWX95HE2vDhw32Kuj355JN48skn+31/ABg+fDgAimQWFhYiNze30zFSX/VBgwZBr/ffG05aonc4HD0cyTBMzGC0UfeQ5MFyL/Py3SSiqo8ADYpIU+oIWi6PdYxWYOBkebulhgRDUn7IpgRA7ZEZrL7lSqxZCpFZ3rMhe1sj5f5KKG2QohGjjc6RFOWtOUYRcKOl+/upPDLTyCMz1i/0+kCfRGZeXh7y8uQE49zcXGRkZOD000/3+8QCQW5uLrKzs1FSUoJ169bhyiuv7HTMunXrAAAzZszw63Pv2kXLXl1VtTMME4NI3X9ypsois2QbMPIioFnx5Waw0HJ5LC+VSxgsJBYSBlIBB0DCPPeU0Hb+aVR2+7EFr7JcwqpoWywt8XYXcVMZsWuj14hdQhDoQkQSmbXHehf9rjkmj+PTudNPH/HJwujYsWPYuHGjv+YSFKScUG/V2zU1NVi5ciUAeEzZ/cEnn3zi8cr8xS9+4bfHZRgmCjAlAPkK397qw1TUcmKPvC8pj3p3x3LRj4Te5M7LHCPvqzzgtjIKYV5mk7JveWLwRabSK7OxF16ZyrxNcxKd12gneYg8ri3snchU2jxZo7wCPwAE1CfT4XBg69at2L59e8g9MiXuuusumM1mfPfdd3j44Yc93pV1dXW44oorUFdXh5NOOgkXXXRRp/ueeuqpyM/Px6JFi1T7d+/ejYULF2L79u2q/S6XC++++y6uuOIKAMCFF16IqVOnBuYXYxgmMjHaqB+5JV3ed2IfUN6h6Edr5EimhNECZCmcSmqOkW2PPYR5maq+5QlkTRVMVIbsvej6U6fo9hOfEhvFLKnD5HFvbYxqlR6ZWZyP2Ud8Epn79+/H448/jmXLlnW67dtvv0Vubi6mTJmCSZMmYdCgQVi/fr0vT+cXcnJysGzZMuh0OjzxxBPIzs7GlClTMGDAAHz22WfIyMjAihUrvBYFFRUVoaCgALW1tar9drsdixcvxsSJE5GSkoJJkyZh2rRpSE1NxRVXXIH6+nrMmjULy5cvD9JvyTBMxKB3t4lU9uUu3QZUKooEU4bRcZwLRhisQJqiWri5in5CGskMUd9yCaWNUXMvDNlVRuzpsSEy00fK44ZSimT2FABTRnxt2bxc3kd8EpnLli3DY489huPHj6v219TUYO7cuSgrK4MoihBFEcePH8cFF1yAsrIynybsDy699FJs3LgRl156KQBg586dSEtLwx//+Efs2LEDQ4f2wl9MQX5+Pp588klccMEFSExMxKFDh7Bt2zYYDAacd955WL58OdauXYvExMQA/DYMw0Q8pgQgb6a8XbZDbeydNpKXypUYveSoVh0OrY2RsvDHFMS+5RLKwqfm6p6XgpV9y+NjZBlYmWJhb6YLg+6imS5Xhwr87NDl/EYoPr0LvvmGLCQ6el++8cYbqKmpQV5eHpYsWQKz2YybbroJu3btwt/+9jc8/fTTvjytX5g0aZIn/7K3HDt2zOv+xMREPPDAA36YVfCRLgKY2EbylWVChMkGZE+iopb2RrnLD0D5cgkDeKlcic5IOaoJOUDlftonVQv3xronECh9MuNCIDKV1eGiE2gsVy8Pd0TZt9ySHvzl/VBgzaILutY62q4+AuRM6zoftb5YbbCfkMORzD7i07uguJhepFK/b4mPP/4YgiDgmWeewZw5cwAA//znP3Hqqafi888/DwuRGcu0tLSgrq4ODQ0NbKnEeNDpdLBarUhISGCT/2BjsLjzDCcCBd+rb0vKJ1HFIlONwQIkD5JFZl2hor1kCM5Vc4j6lksYLWTv1NZA28qcS28oq8utMdLFRqMBEvOBMnf9hHRh0hWSJRRA59ZoiY2Irx/x6XKvoqICiYmJqjaNdrsdP//8M3Q6nap45pRTToFOp8OhQ4e8PRQTJOrr63Hs2DHU1NSwwGRUOBwO1NTU4NixY6ivr+/5Doz/EASKZuae3Pk2LvrxjtGqjtTVF7srzENQ/COKapEZl0j2VMHGkimPJUP2rmhQpK5ZM0Iz31CQPFge1xZ0/3pRisy4VBLivOLTJ3x6VWk0GjQ1Nan2bd26Fe3t7ZgyZQri49V9ZBMSEtDQ0ODLUzI+0NLS4ok+WywWJCUlwWQyQROKpSUmrHC5XGhtbUVNTQ0aGxtRXFwMvV7PEc1gYrSR16NGT0baEp6iH36fqjBYgFRF8U9dMeXZhSIvs72Jqtslgtm3XIktC6hyF4x1Z8je1gC0K76Lo92IXUmqouairrD7SGZNgTy2ZHBleT/wSWQOHDgQhw4dwt69ezFqFL3ZP/nkEwDAzJkzVceKooj6+nqkpaX58pSMD9TVUR6KxWLBwIEDOQeP8aDRaGCxWBAfH4+ioiI0Njairq6ORWYwMSUA8cnUl7tMYYeWPoqjmN7QGdzVwgIAkURebSGQ0rfCTb+gzMcEaLk8FCjFYmN514bsynxMTQwYsStJHSGP60u6j2TWKkSmNSM2Ugr8jE+XxqeffjpEUcSdd96JEydOYNu2bXj11VchCALOP/981bH79++H3W5Hdna2TxNm+o8URU5KSmKByXhFEAQkJSUBAK86BBuNlqx5cqbL+ywZQHwqi8yusKSpO91UHwlNJFNZWa6PC52xuUpkdmPIrjJiT46tCF3GaHncXEXtNbs6T3XskekrPonMO++8E0ajEZ9//jmysrIwefJkVFRUYMKECTj77LNVx3722WcAgGnTpvnylEw/EUXRk4NpMsVAZwem30ivD4fDwc4DwcZkA4adTZ1JtAZg5IXuoh+OKHtFKv6RqDlGrSV7MiL3N02KSKYpBH3LJXpryK4sCpJyDWOFlGHq4p2ao10vmSsjvtYsLvrpBz6JzBEjRmDNmjUYNGgQRFGEIAg4++yz8fHHH3c69s033wQAnHnmmb48JdNPlGKBczCZ7lC+PlhkBhlTAhCXApxxL/C71VQIxJXlXWOI71z842wLfjRT2e3HmBA6MaI0ZG+q6NqQXWXEnhZbIlNnABIU56krkel0UMqBBNsX9Qufy8nOPvtsHDp0CBUVFbBarV6jZHa7HX/7298AgNsqMgzDdIXOSPY7OjMt4+lMNOYLQ+/ojN1UmNuCNw9V3/KE4NsXSSgN2Vtruu6ApDRit8SIEbuS5EFA9WEa13RRYV5fRH6jErYBvFzeD/z2TuiuoEev1+P000/311MxDMNEL0YbReiaTpAROy+Vd0/6WHncVEGdkkIZyTQlhk5k2hQ1D6II1JcBqcM7H6cyYs+IPZGZMhQ49BWN6wq9i8wqhX2RKREwxnNb137gl8vjoqIi3HHHHRgzZgwsFgt0OvUbrKamBk8//TSeeeYZ9mZkGIbpDlMCmT4DFMnkpfLuSR5E0V6J6iPB98oMdd9yCb2ZBJFEfReG7Coj9szYWi4H1MK7rtj7cnmNQmTGp/FSeT/xWWR++eWXGDduHF566SXs3bsXzc3NnfK4kpKS8NFHH+HBBx/Ef//7X1+fkolxli5dCkEQkJ+fH+qpMIz/McSTWNAaSWSGontNJGGIVy8TV7t7mAczn7iTyAxhZNDaC0P2RqURe1bsRejSFf6qDaXkr9rx9aLyyIzBlAI/4ZPILCwsxKWXXoq6ujpcdNFFWLVqlcf+pCPXXnstRFH0+GgyTEeOHTuGRx99FI8++miopxIwFi1ahEcffRTbtm0L9VSYcEUQaMncZKUcMB0vl3eLzqT2xqwrJBPy7ky2/Y3SwsicHLrlckBt6SQZsitprSPzeAlbDNoKKkWmy+72y+zwelF5ZGZyPmY/8emd8Pzzz6OhoQG/+c1v8N577wEA/vCHP3g99pxzzgEA/Pzzz748JRPFHDt2DI899hgAdCs0ExISMGLECAwYEHldKhYtWoSCggLk5+dj4sSJoZ4OE66YEgCTOx+Ti366R2/uUPzjFgyOluD5Var6lodYZCq9MhvKOhuyq4zYdRSlizXMSfR7N7oLtqQUC+XrpbaDRyYvl/cLn94Jn3/+OQRBwBNPPNHjsYMGDYLRaMTRo0d9eUqGwa9+9Sv86le/CvU0GCZwmBNpCZiXyntGowXSFF1c6gppudzeQmIiGLSEQd9yCZVXphdDdmU+ZlxK7EbokgbJIrOmoHMkU2lYb8sm6yOmz/h0iXz8+HGYzWYMGzas54NB7Qw79jpnGIZhvBCfwpXlvSVjjDx2tAG1RcGrMHe0qZef40LUUlIiMUceN1V2FpnKLjaxZsSuJGWIPK47ri4Wc7TLAhRgj0wf8ElkajQauLwlFXvB4XCgvr4eNlsQvcsYv3Hs2DHcdtttHgeBuLg4jBw5ErfeeiuOHz/u9T779u3DjTfeiOHDhyMuLg4mkwk5OTk4+eSTcf/992Pfvn2eY/Pz81VG/YIgqH6uvvpqz23dFf48+uijEAQBZ5xxBgBgzZo1mDNnDlJSUmCz2XDKKafgo48+Ut1n+fLlmDlzJpKSkmCxWHDaaafh66+/7vJc7Nq1C48++ihmz56NIUOGwGw2w2az4aSTTsKDDz6IysrKTveR5lVQQHk+11xzTaffsSOtra1YtGgRTjnlFCQlJcFkMiEvLw8LFizoNqczPz8fgiBg6dKlaGxsxMMPP4xx48bBarVCEAQcO3asy/syTEQSlwpYFAUv1YeDV2He1OH9Hte1nV9QSMyTx94M2ZVG7JYYM2JXkqKsMC9SRzLrigBIhUACd/vxAZ9i+nl5edi7dy+OHz+O3Nzcbo/97rvvYLfbex31ZMKHt99+G9dddx3a2uhNaDQaodFosH//fuzfvx9vvvkmVq1ahV/84hee+3z55Ze46KKLPPfR6/WIj49HUVERioqKsHHjRhgMBk/uZVpaGurr61FTUwMAyMjIUM0hISGhz/N+5JFH8Pjjj0Oj0cBqtaKhoQE//vgjfvWrX+HVV1/FjTfeiGuuuQZvvfUWdDodzGYzmpqasG7dOpxzzjn4+OOPccEFF3R63AsvvNAjFk0mE+Li4lBTU4Nt27Zh27ZtWLp0Kb7++muMGCEv4VksFmRkZKCiogIulws2mw1mc9dRquLiYpx77rnYtWuX5/zFxcXh+PHjWL58Od5++20sWrQI//d//9flY1RVVWHy5Mk4cOAADAYD4uJ46ZWJUvQmIHmwXDUttZd0OQNfOa0s+tGZqNo9lCgjma11VDmtRLkMHB/DVdPKFIv6EvVFSdVheWxOor+pl0AA0zM+RTLPOussAMCrr77a7XF2ux0PPPAABEHAeeed58tTMkHmyy+/xIIFC+B0OnH33Xfj6NGjaGlpQVNTE/bt24d58+ahoaEB8+bNU0U0b7rpJrS1teEXv/gFdu7cifb2dtTU1KClpQW7du3CY489popE/vzzz/jggw8822VlZaqfl156qU/z3rZtG5566ik89dRTqK6uRm1tLYqKijwFaHfddRceffRRrFixAq+++irq6upQX1+PAwcOYMqUKXA6nbj55pu9RupPP/10LF26FAUFBWhpaUFVVRVaW1vx1VdfYdq0aSguLsYVV1yhus+f/vQnlJWVISeHvgBeeumlTr+jhNPpxNy5c7Fr1y4kJCTgX//6FxobG1FbW4vDhw/jwgsvhMvlwq233opPP/20y3Pw6KOPor6+Hh9++CEaGxtRU1ODwsJCpKfHYKI/E93o49QV5sFsL6mMZBpD2LdcQlldDlEtKgF133JrRmjtlkJJhsLEv60eaKqmVpIAtZqUsKTHbt6qH/BJZN5+++0wGAx4/vnn8cYbb3g9ZsuWLTjrrLOwceNGWK1W3Hzzzb48JRNEXC4X/vCHP8DlcuGVV17Bn//8Z89SrCAIGDFiBFasWIGLL74Y9fX1eOGFFwAAJ06cwOHDdCW4dOlSjB0rv5lNJhPGjBmDhx9+WLUE7m/q6urw2GOP4f777/dEQQcMGIAVK1YgPj4eDQ0NePzxx/H6669j4cKFnijfsGHDPE4Jx48fx/r16zs99ltvvYWrrrpKFb03GAyYM2cOvv76a2RkZGDLli34/vvv+zX3VatWYePGjQCAFStW4Morr4TBQEtagwcPxocffojp06dDFEXcfffdXT5OS0sL/vvf/+KSSy6BXk9fJAMHDuSIJhN96IxAmmL5s75I0V4ywCgry0220FaWA3QulHmhdR1EprLwxxKDRuwSCQMBvSLqXKNIsegoMmP1HPkBn0RmXl4elixZAqfTiRtvvBEZGRme5c5TTjkFAwYMwNSpU7Fu3TrodDosW7YMqampfpk4E3i+++47HDx4EKmpqbj++uu7PG7BggUAyG0AAKxWKzRu25XS0tLAT9QLJpMJt912W6f9NpsNM2bMAADk5uZ2ijgCwJAhQzB0KEVFduzY0afntVgsnhaq/RWZ77//PgBgxowZqhQECZ1Oh0ceeQQA5Yfu3LnT6+Oce+65OOmkk/o1B4aJONIVxT+NJ9xLxUGIZCoLRIwJ4bH83JUhuyh2MGLPjF2LLI1GbeJfc0zOy1TmrVqzOJLpAz6/uq688kp8+umnGDJkCCoqKtDe3g5RFLFhwwaUlpZCFEUMHToUn332GS6++GJ/zJkJEj/88AMAigpmZ2cjMzPT688NN9wAAJ48RbPZjDlz5gAgofPwww9j48aNaG9v9/5EAWD06NGIj/eeGyXle06ZMsVrwY3yGOmiqSP/+c9/cNlll2Hw4MGIj49XFfCsWLECALVb7Q+bNm0CIKejeOPMM8+EVqtVHd+RmTNn9uv5GSYiSRlMOZESVYeCtFyuEJnmhNBHMgHAqjBYb1QYsrfWqs+J0u4oFkkeJI9rC+RIZl1Hj8wwuHCIUPzybjj77LOxf/9+fPfdd/jhhx9QUlICp9OJzMxMzJw5U/WFyEQOJSW0rGK321FeXt7j8S0t8ofXkiVLcPHFF2P79u144okn8MQTT8BgMGDq1Kn45S9/ieuuuw7JyckBm7vVau3yNp1O1+tj7Ha1/YfL5cLvfvc7vPvuu6pjk5KSPEvadXV1aG1t7bdd14kT9KXVndm8yWRCamoqysvLPcd3hHMvmZjCYKHK6sr9tF19hAzZA01ThTw2JYY+JxMAEpSG7OWyIbvKiF1PVfmxjNLEv7ZQFpnKPNaEgWxf5AN+u+QSBAGnn366Z6mQiXycTicAYPr06diwYUOf7pubm4stW7bgyy+/xH//+1/88MMP2L59O3744Qf88MMPeOaZZ7Bq1SrMnj07EFMPGG+88QbeffddaLVaPPDAA5g/fz4GDx7sSQ8AgPnz5+Nf//oXxGD2TvYCX9gxMYXORN6HksisKySPSEdbYJc7VS0lk8IjkmnrwpCdjdjVpI2Ux/XF9Fqxt6iLuWwD+Dz5QIwmYzC9ITOT8nqkZfC+otFocM455+Cll17Cpk2bUF1djbfffhu5ubmoqanBFVdcEdQldH8gFQVdf/31eOyxxzB06FCVwASgqhTvD1IEsrvl9tbWVlRVVamOZ5iYRm8GUpXFPyWyaAgkTVXyOFxEZqLCUrCpQhaZymXg+Bg2YpdIU/Qwb6oA2huAGkU+pqABbFnhEZ2OUFhkMl0i5fSVlZV1mffXF6xWK6644gqPE0F5ebmqaEUp1kIdBeyKwkL6kO6qoKaxsdFTGe4N6Xfs7vebMmUKAHRrCP/tt9/C4SC7jalTp3Y/aYaJBby2lwxChXmzUmQmh4clkEpkVsqG7J2M2MNgrqEkbSQguAWk6KIlcykSDlC0V89uHL7AIpPpkjPPPNNTZX377bf3GHWsriYrj56OU5qQK4WlshtUbW1tX6cbFCQ7pO3bt3u9/YknnkBDQ0OX95d+x+5+v9/+9rcAgB9//BFffPFFp9sdDgcef/xxAMDYsWNVFlEME9Mo20vaW4D6wsBHMjv2LQ+HqJeyoKetHmhvpHEnI/YYj2Tqjer81eojQOVBeZvPkc+wyGS6RKfT4dVXX4VOp8P333/vabeoLIY5cuQIXn31VUydOhX/+Mc/AADr16/H+PHj8eKLL2Lv3r0eQ3NRFLF+/XrcdNNNAMizcfz48Z7HGj58uKd4ZsmSJWEZzTz33HMBAK+//joWL17sEdRlZWW4/fbb8dxzzyElpevexZIgXLVqVZeV63PnzsX06dMBAL/5zW/wzjvveM750aNHMXfuXPz4448AgOeee84/vxjDRAPx6fQjUX0ksJFMp4OskiTiUsKjM4w1i5Z6JaSCH2XhjyWTI5kAkDRYHtccox8JawbnY/oIi0ymW+bMmYOVK1fCarVi48aNOOussxAfH4/U1FSYTCYMGTIEN910EzZt2qSyA9q5cyfuuOMOjB492lMJbTAYMHPmTOzcuRM2mw3vvPOOqjglLi4O8+fPBwDcfffdsFgsyMvLQ35+Pv70pz8F/Xf3xp133omRI0fC4XBg4cKFMJvNSEpKQnZ2NhYtWoSFCxfiwgsv7PL+N954IwRBwPr165GWlobs7Gzk5+eruh9ptVqsXr0aY8aMQV1dHa688kpYLBYkJSVh8ODBWLNmDTQaDV566SXuoMUwSqT2khLVR0lkeunc5ReUUUwgfKq1tTr1XKQIZoOi8MeawVE6QN0pqva4F/siFpm+wCKT6ZFLLrkEhw4dwiOPPIJp06bBYrGgtrYWRqMREyZMwPXXX48PP/wQd911FwDKEVyxYgVuuukmTJ48Gampqaivr4fJZMLEiRNx9913Y+/evZg1a1an53rllVfw6KOPYty4cQCo605BQQEqKys7HRsKEhMTsX79etx2223Iz8+HVquFTqfDGWecgXfffbfHFqunnXYaPvnkE5x11llITExEeXk5CgoKOhVXDRgwAJs2bcILL7yAk08+GWazGc3NzcjJycH8+fOxefNm3HLLLYH8VRkm8tDHAanK9pJF7grzAC2ZK6uQNXpqKxkudDRkdzqBBqURe1Z4RF1DjbJTVF1R53OkYyHuC4IYjmuSTK+ZPHkytmzZgkmTJmHz5s1dHudyubB/PyU0jxgxolNFNMNI8GuFiWh+eAn48mEaWzOBX79O3YDiu05j6TdHvwPeuojG5mTghm/UBt+h5J3LgAOf0XjytcDpdwMvKCx7rvsSyJkWmrmFE0f+ByxzN4rRGQGdmUzrAeDCRcDEK1lodqC3ugPwo08mwzAMw4Sc9NHyuKGcciaDEck0hUm3HwmboqClsUxdWa41qPubxzIZisJJR5vcWhKgc8h5qz7BIQqGYRgmekgZqsg1FKn4xx6g4h9Vt58wE5mJOfK4qUItMtmIXSY+xbvgFrSALZtTCnyERSbDMAwTPRitQFK+vB3I9pKNipau4SYyE/LkcVNFByP2NC76UaJ8vUjEp5LBP+MTLDIZhmGY6EHXocK8pgBw2OWuN/6kucNyuTaMRGZSB0P2TiKTl4E9JA/pvM/CHpn+gEUmwzAMEz3ozUBKh/aSzgC1l2wKw77lErZsedzeCFQdlrdZQKlRtiOVsGTyOfIDLDIZhmGY6EGjBdI7tpdsDYwpe3MY9i2XsGSouw+V71Lclhke7S/DBWU7Ugkb2xf5AxaZDMMwTHShrBi2N5MZub3Z/8/TqW95GIlMjVbd/Ug5V2sGL5crSR/TeZ9tABux+wEWmQzDMEx0YclQd7ypPhyYCnOlcItLDC+RCZCZuDe4alpN8iDK5VViG8gV+H6ARSbDMAwTXehNalN0qb2kP3uPuFxAS628HZcafsJNmZep2j8wuPMIdzQaIDFPvS9xoDrdgOkXLDIZhmGY6EIfB6QMk7c97SX9GM1srQVEp7wdjubmSkN2Ca2R8kcZNUpHAq2e8lYZn2GRyTAMw0QXOiOQqhSZxYCz1b8V5sqlckETnsJNacguEc9G7F5Rvl7i0/gc+QkWmQzDMEz0oSzmaCgFWhv8G8lU2hcZreFZSNNxCRhgI/auyJ0hj1NHsMj0EzEnMmtra7FixQrcddddOOOMM2C1WiEIAvLz8/3y+Fu3bsVll12GzMz/396dRzdV7XsA/yZNmqRDOk+0QFvaQi8ylEmRGRXsYxSQAk5430XeQhTvRRxQpEgBEZ8T+hCcuHgVoYhewCsqQxUU5DIIV62UobRQoAyd55Ts98cxaULSNmnTJm2+n7W6Vs6wT3Zycppf9tl7/8KhVqsRGxuLefPm4erVq40XJiIixwiJrwumhCG9pCNbMk2DTK1rTgnkZ6XvJSdit67LSGDQPKDLHUDfmRxZ7iAuNhSu5WVkZCAlJaVFjr1161ZMmzYNOp0OoaGh6N69O06ePIk333wT6enp2L9/P2JjYxs/EBERNY9KC/h3Aq6flpYdHWSW35Ttx9VGlgPWg0yfMLZkWqNQAd0nAWE9pPeIc2Q6hNu1ZGo0GgwdOhTz58/Hxo0b8fbbbzvkuHl5eXjggQeg0+mwaNEi5OXl4ciRI8jLy8Pdd9+NS5cuISUlBcKRoxuJiMg6i/SS56TBPzdqHXP8svy6x2p/1wwyvYItW1h9wtmSaY1MJgWaHp7S+8NA3CFc8KpoWaNHj8bo0aONyzt27HDIcVetWoWKigoMHToUL774onG9n58fPvnkE8TExODw4cPYsWMHxo0b55DnJCKieig10gjzU99IyyV5Up/M2krAw7f5xy836QKl8XPN6W7kcqlVruRC3TqfUAaZ9VGopOmv5AoGmQ7idi2ZLWXLli0AgEceecRiW0BAAO69914AwObNm1u1XkREbknuAYR2q1suvgDUVjtuUnaz2+UullLSlPamCdmtTWtEEoUG8PSWAkxXm/O0jWKQ6QDnz59HXl4eAGDo0KFW9xkyZAgA4ODBg61WL2oZr7/+OlJTU3Hu3DlnV4WIGhJmMsK8pkwaZV7roH6ZpgN/NP6u2zp484Ts1vppkkSpAZTeUmsmOYSL/vRqW7KysgAAnp6eiIqyfgF36dIFAHD27FnodDoolS76D4ka9frrryMnJwfDhw932KwERNQCfDtIOcUrC6TlgjNAZF/HHLuioO6xq+UtN+VnMlemQi0FxGSd2g8I6Ax4+ji7Ju2Gi14VbUtBgfTPJiAgALJ6mtgDAwMBAHq9HiUlJQgKqj87xNq1a7Fu3TqbnjszM9PO2hIRuQnlH4N/8gxB5lmpJVOI5t8ONQ0yvVz4drlpkOkdzPkfGyKTAV6Bzq5Fu+KiV0XbUlUl9fHx9Ky/o7BKVXdhV1Y2fLvm0qVLOHr0qGMqR0TkrpReQFAckHdYWi4+/8cI85rmBVtC1LWOAlJLpqv24YsdBkAGQADhvTighVpVm+mT+cQTT0Amk9n9N3z48Bavm1ot9d+oqampd5/q6mrjY41G0+DxIiIi0KdPH5v+GjsWOc769eshk8mQk5MDABgxYoTZZ239+vUAYFw+d+4cvvvuO4wZMwYhISGQy+X44osvAAAzZ840K3Ozc+fONZgkQK/X4+OPP8aoUaMQHBxs7Krx0EMP4ffff3fwKydqo25OL1n8xwhzXUXzjltTJgWqBl7BzTteSwpNBCa8BfSZCfT7s+v2HaV2qc20ZPr4+DR4i7k+fn5+LVAbcwEBUs7awsJCCCGs3jI33FKXy+XQarUNHm/27NmYPXu2Tc/dt29ftnq2krCwMAwaNAiHDx9GdXU1brnlFrPPV1hYmNn+n376KZ577jlotVrExcXBy8vLIfWoqKjAlClT8NVXXwEAOnTogO7du+P06dPYsGED0tPT8dlnnyE5Odkhz0fUpt2cXrK6XBph3pzf56YjywEpH7grixog9TdUa9mSSa2qzQSZaWlpSEtLc3Y1rEpISAAgtWSeP38enTp1stjnzJkzAICYmBgO+mmjkpOTkZycjOjoaOTk5GD16tUNtpQ///zzWLhwIV544QXjOTd0rWiORx99FF999RUGDBiAd999Fz179gQA6HQ6pKWl4cUXX8R9992HkydPIiQkpNnPR9SmhSRI/SX1tYDQS/0yQ+IbL9eQiut1jz19XL+fo4dSeg/knGScWlebCTJdWadOndChQwdcvHgR+/btw3333Wexz759+wAAAwcObO3qNYsQAiVVDsqQ4URataLeQVkt5e6778bSpUvN1hm6VjTVb7/9hr///e8ICQnB9u3bERoaatymVCqxZMkS/Oc//8Hnn3+Od999FwsXLmzW8xG1eWo/afBLYba0XHCm+XNlms2RqXXdQT8GHp7SHJByuWtOGk/tlotfGW3H5MmTsXr1aqxbt84iyCwsLER6ejoAGCdlbytKqmrRa8k3zq5Gsx1fPAp+mtZtQZ45c6bDj/nZZ59BCIEJEyaYBZimJk6ciM8//xwZGRkMMokUaiCoS12QWXhOmpRdr5eCrqYwzfajctG85aY8PKU5INmKSa3Mxa8M1zJt2jQcPHgQU6ZMwSuvvGK2bcGCBXjvvffw/fff44UXXsDixYvh4eGB4uJizJgxA8XFxUhKSmJKSTeSmJjo8GOeOHECALBr1y4MHjzY6j5FRUUAgAsXLljdTuRWlBogKAE4vUtaLsmTBu3UVkrZXZrCNMhU+1nmB3c1nl6A4o9Ak6gVuWWQGRxcNxJQp9MBkLL2mK6fPn06Vq9ebVbu8uXLyMnJwbVrN3X6BtCxY0ds2LAB06dPx9KlS7F27Vp07NgRv//+O8rLyxEWFobNmze3+i1bch5v7yZ+gTXAEECeO3eu0YxDFRXNHEFL1B7IPYDQrnXLxReAG3+kl2xykHml7rHG3/VvQXt6AyGJbMmkVueWQeb169ct1un1erP1paWldh93ypQpiI2NxYoVK/D999/jP//5Dzp06ICHH34YixYtqvf2pivTqhU4vniUs6vRbFq1a33UDT82hBBWt5eXl1td7+MjZaJ4+eWXsWDBgpapHFF7E3ZL3ePqEqD0MhAQA6CJo8LN+mT6u/7tcoCpEskp2sCV4Xj1fbE3JiMjo9F9+vTpY+x/2R7IZLJW78vo6hzRGm1o5czPz7e63ZCq9Ga33HILvvjiC/zyyy/NrgOR29BGAuoAoKpQWr5+GuiQ1PTjmQaZGhfO9kPkZG1mMnYiV2GYAL+xzE0NiY+XplA5cOCA1e1r1qyxun7KlCkAgC1btiA3N7fJz0/kVpRqIDCmbrkgG9A1/fo1m8JIE8gJzonqwSCTyE5dunQBAHz33XdNPsaYMWMgk8mwfft2fPrpp8b1VVVVWLhwYb2t5r169cLMmTNRUVGBO+64w2odfv/9dyxevBjbt29vcv2I2hVDekmDohwpvWRt/VnaGmQaZLpy3nIiJ2OQSWSn6dOnAwBWrlyJrl27YtiwYRg+fDh27txp8zHi4uLw2GOPQQiB6dOnIyoqCv3790doaCheffVVi0FnptasWYPJkyfj9OnTGD58OMLDw3HrrbeiT58+CAoKQmJiIl588UWrfY+J3NLN6SVLLkrTGNU2sTXTLG95gOvmLSdyMv78IrLTjBkzUFRUhPfeew8nT5409p+0d17M119/HTExMXj33Xdx+vRpVFZWYsSIEVi8eDECAwPrLadWq7FlyxZs374dH374IX766SccO3YMCoUCHTt2xLhx43DPPfdg9OjRzXmZRO2LWXrJi1L+cV2VNAWRPXSV5rfaXTlvOZGTMcgkaoI5c+Zgzpw5VrfZOrBMJpPhiSeewBNPPNGk44wbN47zrhLZKrQbIPMAxA1Af0PqlxmSYP9xKm66Q+DNIJOoPrxdTkRE7Z/aD/CLqlsuPNu0wT+mI8sVGmmicyKyikEmERG1fwq1+eCfgmypX6a9U9pVtLG85UROxCCTiIjaP6UGCDIZ/FN8QUovaW9rZrnJ7XIGmUQNYpBJRETtn9zDvA9myYU/RphX2Xcc05SSKn8GmUQNYJBJRETuIdwkvWRVsRQw2tuSWWaat9yPQSZRAxhkEhGRe9B2BFTauuVrp5vQktkG85YTOQmDTCIicg+eGiAwtm65oAkjzCuYt5zIVgwyiYjIPSi9gKAudctFOVJqyRu1th+j/Ka85QwyierFIJOIiNyDQgUEmw7+yQNu2JlespJ5y4lsxSCTiIjch2l6yZKLQE2FfbfMK27KWy7n1yhRfXh1EBGR+wjtBsj++OrT1wKF2bYHmTd0QHVJ3bJXiOPrR9SOMMgkIiL3oQkwTy9ZcMb2EebMW05kFwaZRETkPhRq8xHmhpZMW9JLmk5f5KEEPL0dXz+idoRBJhERuY+b00sWnQf0Oin7T2NMWzJVWinQJKJ6McgkIiL3IfcAQrrVLZfk/ZFe0oZ+maZzZKr9GGQSNYJBJlErWr9+Pfr16wdvb2/IZDLIZDIUFRU5u1pE7sU0vWRloXQbXGdDv8yyq3WP1UwpSdQYXiFErWT9+vV4+OGHAQBdu3ZFcLA0aECh4GVI1Kr8OgGePkBNmbR87TQQ3qPxcuWmQaY/g0yiRvAKIWolb731FgDglVdewfz5851cGyI3ZkgvefmEtGzrCHOLINOjRapH1F7wdjlRK/ntt98AAP/1X//l5JoQuTmlFxAUV7dsSC+pv9FwOdMgU+MPyNknk6ghDDKJWkllpTSwQKPROLkmRG5OoQKCTUaYG9JLNjYpu+nocg1TShI1hkEmkR0Mg3Xqk5qaCplMhtTUVOO66OhoszIxMTHG45juR0StKPRPdY9L8qT0ko3dMjcNMr0CGWQSNYJXCFEL69+/P6KiovDDDz8AAPr16weVSgUA6NSpkzOrRuS+wv4kpZcUeildZFGueeumNcxbTmQXBplELSw9PR0AjK2Z6enpiI6OdmKNiAiaQEDbASi+IC1fPwVED6p/f70eqCqqW/ZiSkmixjDIpIYJAVQVO7sWzaf2Axq4zU1EbsaQXtIQZBZkN9wns7JQavU08A5p2foRtQMMMqlhVcXAys7OrkXzPZ0jjQYlIgLq0ktmfy8tF58HbtRI2X8UKsv9TbP9yD2kH65E1CB2KCEiIvdTX3rJ+lozy02CTOYtJ7IJg0wiInJP4d3rHldclwb21DfC3LQlU+XLkeVENuBVQg1T+0m3mts6B9/aEkJYncqovLzcoc9DRC3IP0aamF1XIS1fP2We19yUaUsm85YT2YRXCTVMJmNfRhPe3t4oLy9Hfn4+wsPDLbZnZWU5oVZE1CSeGiAwBsj/VVouOFt/S+bNKSV5u5yoUbxdTmSH+HhpHr0DBw5YbDt79iy+/vrr1q4SETWV0ksa/GNQeE4KMvV6y32Zt5zIbgwyiewwduxYAMBzzz2Hc+fOGdefOXMGKSkp0Fv7ciIi13RzesniC9LE7NZaM83yljOlJJEtGGQS2WH+/Pno3LkzMjMzkZCQgB49eqB79+6Ij49HdXU15s6d6+wqEpE9Qm5OL1lufYS5aZ9MBplENmGQSWQHf39//PDDD3j44YcRGBiIkydPorKyEgsWLMCBAweg1WqdXUUiskd4dwB/DOK7UQMUnQdqrQSZpiklvQIAOftkEjWGP8WI7BQZGYkPPvjA6rbU1FSkpqZa3SaEaMFaEVGTeAUBvhFA6UVpueA0oLvdcr+K63WPNYHMW05kA14lRETkvhRqICi2brkg27IlUwig0rQlM6h16kbUxjHIJCIi96XUAEEJdctFOUCtThoAZFBdAuhr65a9g1uvfkRtGINMIiJyX3IPIKRr3XLJReDGTeklTQf9QCbdLieiRrldkFlUVITNmzdjwYIFGD58OHx9fSGTyRAdHd2s465fvx4ymazBv2eeecYxL4KIiBwnzCS9ZPlVoKLQfBoj0/6YKh9p6iMiapTbDfzJyMhASkpKix1fq9WiR48eVrc1N5AlIqIWEBgr3TY3tF4WnALCTaY2Mm3JVDGlJJGt3O5K0Wg0GDp0KPr3749+/fqhoKAAjz76qMOOn5SUhIyMDIcdj4iIWpinFxAQA1z5TVq+fgbQmbRkmmX7YZBJZCu3u1JGjx6N0aNHG5d37NjhxNoQEZHTKb2AoLi6INOQXlIIQCa7KdsPg0wiW7ldn0wiIiIzjaWXNA0yVX6AB4NMIlvwSnGw3NxczJw5E+fPn4dGo0FiYiImT56M2267zdlVIyKi+oTelF6ytkLqo6nUMG85URPxSnGw7OxsZGdnG5e//PJLvPLKK5g2bRref/99eHl5ObF2RERkVdgtdY9rq4CiC0DQH62bZtl+GGQS2YpXioMEBATgqaeewrhx4xAXF4fAwECcO3cOGzZswMqVK/Hpp5/ixo0b2Lx5c6PHWrt2LdatW2fT82ZmZja36kRE5B0M+IQDZZel5eungU5/3IGySCnJr04iW/BKcZAJEyZgwoQJZusSEhKQlpaGnj17IiUlBenp6di/fz8GDx7c4LEuXbqEo0ePtmR1iYjIlEItTWVkCDILztZNaVRhklJS4y9N4E5EjWozQeYTTzyBN954w+5yw4YNc/qUQlOnTsWrr76Kn376CVu3bm00yIyIiECfPn1sOnZmZiYqKysb35GIiOqn1EiDf3J/lJaLcqTBPzdqzYNMppQkslmbCTJ9fHwQFBRkdzk/P78WqI39br/9dvz00084depUo/vOnj0bs2fPtum4ffv2ZasnEVFzyT2AkG51y8V5Ut/MygKg1uSHPINMIpu1mSAzLS0NaWlpzq5Gk3l6egIAamtrnVwTIiKyKsw0y88VoKoYKL5ovo8Xg0wiW7WZILOt++WXXwAAUVFRTq4JERFZFRQv9c00zI95/TQAUbdd6QUo1U6pGlFbxMnYW8GJEyewc+dOAMCoUaOcXBtqjujoaMhkMqf38yWiFuDpDQRE1y0XnAFKL9ctq7WAXNnq1SJqqxhk2mHatGmIjo7Gk08+aba+pKQEKSkp+PHHHyGEMNv29ddfIzk5GTdu3ECvXr0wadKk1qwyERHZSqkBgrrULRdkS7fNDVRMKUlkD7e8WoKD6/rU6HQ6AMD58+fN1k+fPh2rV682K3f58mXk5OTg2rVrZuv1ej02b96MzZs3w9fXF7GxsVCpVMjJyUF+fj4AoHv37ti2bRs8PDj1BRGRS1KogOAEAF9Ky8XngfKEuu0afwaZRHZwy6vl+vXrFuv0er3Z+tLSUpuP5+3tjVWrVuHAgQP45ZdfkJOTg7KyMvj5+WHEiBGYMmUK/vznP0OtZl8eIiKXFmIy+Kc4z3widrUf58gksoNbBpk339K2VX398JRKpcUtdCIiaoPCTdNLVgLXsuqWmVKSyC7sk0nkIPv370dAQABkMhlWrVoFALhx4wbeeecdDBo0CP7+/lAqlQgLC0Pv3r3x+OOPW00LWlVVhSVLliAhIQFqtRodOnTAww8/jPPnz2P9+vWQyWSYOXNmK786IjfhEwp4h9YtXzG5RjUBgAcH/hDZij/JiBxg+/btSElJQU1NDT788ENjEHj//ffj008/BSBNXxUfH4/CwkKcPHkSx48fR2xsLBITE43HqaiowF133YUff5SyjnTt2hXe3t745JNPsGPHDsyZM6fVXxuRW1GogaDYugE/el3dNjVbMonswauFGiSEQKnO9v6prspX6QuZTNYix/773/+Ov/zlL1AoFNi6dSvGjx8PADh27Bg+/fRTaLVabN++HUOHDjWW0el02LlzJ7RardmxXnjhBfz4448ICwvDtm3bMGDAAABAfn4+pk2bhhUrVrTIayCiPyg10nyZuQcttzFvOZFdGGRSg0p1pRi0cZCzq9FsP0z/AVpPbeM72umVV17BU089Ba1Wi23btpkFkidPngQAjBw50mw9IPXjHTdunNm60tJSvPPOOwCAdevWGQNMAAgLC0N6ejq6dOlinBGBiFqA3AMI6Wp9m5f9qY2J3Bn7ZBI10dNPP40FCxYgLCwM3333nUUg2alTJwDATz/9hHPnzjV6vH379qG8vByhoaEYO3asxfbg4GBMnDjREVUnooaE3WJ9PfOWE9mFQSZREzz55JN4+eWXERsbi/3796NXr14W+9x2220YPHgwLl26hPj4eNx1111YunQp9u7di+rqaov9s7KkUaw9evSAXG790uzdu7dDXwcRWREUD3h4Wq73Dmn9uhC1YbxdTg3yVfrih+k/OLsazear9HXo8U6fPg0AiIyMREREhNV95HI5vvzySyxbtgwbNmzArl27sGvXLgCAn58f5syZg8WLF0OlUgEAysrKpLr61l/XhrYRkYOofKT0kqbTF3mopPVEZDMGmdQgmUzWIn0Z27p3330XS5cuxb59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FAoMGTLE6j5yubzZ2YwGDBhgdb2HhweCg4MBSO+/NWFhYcbHhYWFzaoHEdlH4ewKEBG5kpv7OJpSKKR/maYjpq9cuQIAjbbWRUVFme3fGH9/f2zcuBEzZszAr7/+isceewwA4Ofnh6FDh2Lq1KlISUmBUqk0lrl48aLxsa0tlBUVFTbtZ43htQQHBxtbFK0xvPamsuWc1LePYTuARke6E5FjsSWTiMhF3XnnncjOzsaGDRvw0EMPIT4+HsXFxdi+fTseeOABJCUlIS8vz7j/jRs3jI8rKyshhGj0rz3nTCci52KQSUTUDKGhoQCACxcuNLifYbthf1t5e3vjgQcewPr165GVlYULFy5g5cqVUKvVZi2cABAeHm583Jzb4LYyvJZr166hpqam3v1MA2Eich8MMomImsG0r2VxcbHVfYqKisz6bjZHZGQknnrqKcyfPx8A8O233xq39e/fH56engCA7du3N+t5bGF47bW1tdi3b5/VffR6PVNJErkpBplERM0wefJkKBQKVFVVYeXKlVb3Wb58Oaqrq6FUKjF58mSbjltdXd3gdo1GAwBmI7q9vb0xY8YMAMDKlSuRm5vb4DHqyxZkq549eyIxMREAsGzZMuj1eot9Pvjgg0ZbeYmofWKQSUTUDJGRkZg3bx4A4KWXXsLixYtRVFQEQGrBXLRoEVatWgVAmlQ9IiLCpuOuXLkSycnJ+Oijj8yCtOrqamzevNl4zDFjxpiVW758OTp06IBr165h4MCB+Oijj1BaWmrcfvXqVXz22We45557MH369Ca/boNly5YBAPbu3YsZM2YY61pVVYV33nkHc+fONU7HRETuhaPLiYiaafny5Th//jw2b96MF198EWlpafDz80NxcbGxdW/69OlYunSpzcfU6/XYuXMndu7cCUBqudRoNCgsLDROY5SYmIhXX33VrFxERAR27dqFiRMnIisrCw8++CDkcjn8/f1RXV1tNufmnXfe2dyXjnvuuQfPPfccli1bhk2bNmHTpk0ICAhAaWkpamtrMWTIEAwePBgrVqxo9nMRUdvClkwiomby9PTEpk2bsGXLFiQnJyMoKAilpaUICgpCcnIytm7dik8++cRsuqHGPPLII1i3bh2mT5+OW265BV5eXigpKUFAQACGDBmC119/HUePHjUb7GOQmJiIEydOYO3atRg1ahSCg4NRUlICIQTi4uJw7733Yt26ddi8ebNDXn9aWhp27NiBkSNHQqvVorq6GomJiXjppZewe/duYz9RInIvMtHQzL5ERERERE3AlkwiIiIicjgGmURERETkcAwyiYiIiMjhOLqciMjNzZs3D5s2bbKrzBtvvIGUlJQWqhERtQcMMomI3FxxcTHy8/PtKlNZWdlCtSGi9oKjy4mIiIjI4dgnk4iIiIgcjkEmERERETkcg0wiIiIicjgGmURERETkcAwyiYiIiMjhGGQSERERkcMxyCQiIiIih2OQSUREREQOxyCTiIiIiBzu/wFuy8HT+yUgiAAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sns.set_context(\"talk\", font_scale=1.0)\n", + "fig, ax = plt.subplots(figsize=(7, 5))\n", + "sns.lineplot(data=df, x=\"noise_dim\", y=\"estimate\", hue=\"estimator\", ax=ax)\n", + "ax.set(\n", + " title=\"CMI of I(Y; Z | X)\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02d99c21-6874-48e8-8a51-ed923646f63a", + "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_supervised.ipynb b/experiments/plotting_cmi_supervised.ipynb new file mode 100644 index 000000000..f2f4690c6 --- /dev/null +++ b/experiments/plotting_cmi_supervised.ipynb @@ -0,0 +1,618 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "355a421f-926e-4ce6-ba03-b03893f47fed", + "metadata": {}, + "source": [ + "# Analysis of (Conditional) Mutual Information with Supervised Forests\n", + "\n", + "Compared to unsupervised forests, supervised forests are useful for computing direct estimates of entropy based on their abilities to predict probabilities. Moreover, supervised forests lend themselves well to computing (C)MI between discrete and continuous variables (i.e. when Y is categorical but X is continuous for (X, Y) input data).\n", + "\n", + "- MI: I(X;Y) = H(Y) - H(Y|X)\n", + "- CMI: I(X;Y | Z) = H(Y|Z) - H(Y | X, Z)\n", + "\n", + "Specifically, we will extend the separated Gaussian simulations of the uncertainty forest paper.\n", + "\n", + "$\n", + "\\begin{align}\n", + " I(X; Y | Z) = H(X | Z) - H(X | Y, Z) \\quad \\text{(Entropy identity)}\\\\\n", + " = (H(X, Z) - H(Z)) - (H(X, Z | Y) - H(Z | Y)) \\quad \\text{(By chain rule)} \\\\\n", + " = H(X, Z) - H(Z) - H(X, Z | Y) + H(Z | Y) \\quad \\text{(Simplify)}\n", + "\\end{align}\n", + "$\n", + "\n", + "Now each quantity is directly computable from $(X \\cup Z) \\sim$ multivariate Gaussian with Y being categorical.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "daa90182-c238-42dc-882b-0f1849461125", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The lab_black extension is already loaded. To reload it, use:\n", + " %reload_ext lab_black\n" + ] + } + ], + "source": [ + "%load_ext lab_black\n", + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "c482e3c5-b7f6-4b7a-a81e-e76a1c718206", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "import scipy\n", + "import scipy.spatial\n", + "import seaborn as sns\n", + "from sklearn.neighbors import NearestNeighbors\n", + "from sklearn.tree import DecisionTreeClassifier\n", + "from sklearn.utils.validation import check_is_fitted\n", + "from tqdm import tqdm\n", + "\n", + "import sktree\n", + "from sktree import (\n", + " HonestForestClassifier,\n", + " NearestNeighborsMetaEstimator,\n", + " ObliqueRandomForestClassifier,\n", + " ObliqueRandomForestRegressor,\n", + " UnsupervisedObliqueRandomForest,\n", + " UnsupervisedRandomForest,\n", + ")\n", + "from sktree.experimental import SupervisedInfoForest, mutual_info_ksg\n", + "from sktree.experimental.ksg import _compute_radius_nbrs, mutual_info_ksg_nn\n", + "from sktree.experimental.mutual_info import (\n", + " cmi_from_entropy,\n", + " cmi_gaussian,\n", + " entropy_gaussian,\n", + " entropy_weibull,\n", + " mi_from_entropy,\n", + " mi_gamma,\n", + " mi_gaussian,\n", + ")\n", + "from sktree.experimental.simulate import (\n", + " cmi_separated_gaussians,\n", + " embed_high_dims,\n", + " mi_separated_gaussians,\n", + " simulate_helix,\n", + " simulate_multivariate_gaussian,\n", + " simulate_separate_gaussians,\n", + " simulate_sphere,\n", + ")\n", + "from sktree.neighbors import forest_distance\n", + "from sktree.tree import ObliqueDecisionTreeClassifier, compute_forest_similarity_matrix" + ] + }, + { + "cell_type": "markdown", + "id": "9ed18c2d-7c0f-44a2-a240-fa7aa7fd9df5", + "metadata": {}, + "source": [ + "## Define Hyperparameters of the Simulation" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "037d0d4d-5c0d-4f0d-8318-bcf7af194cdf", + "metadata": {}, + "outputs": [], + "source": [ + "seed = 12345\n", + "rng = np.random.default_rng(seed)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "efddcb34-6007-4312-b7f4-e17a48f3614d", + "metadata": {}, + "outputs": [], + "source": [ + "n_jobs = -1\n", + "n_estimators = 100\n", + "feature_combinations = 2.0\n", + "n_nbrs = 5\n", + "\n", + "# hyperparameters of the simulation\n", + "n_samples = 1000\n", + "n_noise_dims = 20\n", + "alpha = 0.001\n", + "n_classes = 2\n", + "\n", + "# dimensionality of mvg\n", + "n_dims = 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": "e20f862e-4a93-4fba-8066-0606fe71b667", + "metadata": {}, + "source": [ + "## Setup 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": 8, + "id": "602422ed-7735-4396-8c28-f32a9fdf62a3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(1000, 3) (1000,)\n" + ] + } + ], + "source": [ + "# simulate separated Gaussians\n", + "# simulate multivariate Gaussian\n", + "X, y, means, sigmas, pi = simulate_separate_gaussians(\n", + " n_dims=n_dims, n_samples=n_samples, n_classes=n_classes, seed=seed\n", + ")\n", + "\n", + "print(X.shape, y.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "3de9593a-de44-4512-bfe8-f3c489eefd96", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2 2\n", + "(3,) [0.5 0.5]\n" + ] + } + ], + "source": [ + "print(len(means), len(sigmas))\n", + "print(means[0].shape, pi)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6d4b5d32-fce7-4198-95d1-ceb01400456b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[array([[1., 0., 0.],\n", + " [0., 1., 0.],\n", + " [0., 0., 1.]]), array([[1., 0., 0.],\n", + " [0., 1., 0.],\n", + " [0., 0., 1.]])]\n" + ] + } + ], + "source": [ + "print(sigmas)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "84c17ab8-6e7d-478c-8e9c-757bf1a2c527", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# compute ground-truth MI/CMI\n", + "# I(X;Y)\n", + "# I(X;Y |Z)\n", + "\n", + "# true MI and CMI\n", + "true_mi = mi_separated_gaussians(means, sigmas, pi, seed=seed)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "ee0fa67d-aac1-4e20-b153-0fb51d8b16b9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4.351104834412363 2.8469010556693393 4.2568155996140185 2.8378770664093453\n" + ] + } + ], + "source": [ + "true_cmi = cmi_separated_gaussians(means, sigmas, pi, condition_idx=[1, 2], seed=seed)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "dee49368-27f5-4f33-a9ce-525483e2abba", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[(2, 2), (2, 2)]\n" + ] + } + ], + "source": [ + "condition_idx = [1, 2]\n", + "z_sigmas = [sigma[np.ix_(condition_idx, condition_idx)] for sigma in sigmas]\n", + "print([sigma.shape for sigma in z_sigmas])" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "fb21dfe6-4cec-4aa2-ba1b-4e1d760a1f93", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.09428923479834417\n", + "0.08526524553835024\n" + ] + } + ], + "source": [ + "print(true_mi)\n", + "print(true_cmi)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "bf304453-a772-4148-a41a-f3e9de322b0a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(1000, 3) (1000,) (1000, 21) (1000, 21)\n" + ] + } + ], + "source": [ + "_X = embed_high_dims(X[:, [1]], n_dims=n_noise_dims, random_state=seed)\n", + "_Z = embed_high_dims(X[:, [2]], n_dims=n_noise_dims, random_state=seed)\n", + "highdim_X = np.hstack((_X, _Z))\n", + "\n", + "print(X.shape, y.shape, _X.shape, _Z.shape)" + ] + }, + { + "cell_type": "markdown", + "id": "b9a79406-10cb-4a72-a016-109a2a752eac", + "metadata": {}, + "source": [ + "# Setup SupervisedInfoForest\n", + "\n", + "The info forest object is just a meta-estimator that I wrote to expose the sklearn API for computing MI/CMI. It proceeds by fitting multiple forests." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "c6e3014d-ace3-41c2-9f30-cf2015737e4a", + "metadata": {}, + "outputs": [], + "source": [ + "tree_estimator = ObliqueDecisionTreeClassifier(\n", + " feature_combinations=feature_combinations,\n", + " random_state=seed,\n", + ")\n", + "tree_estimator = DecisionTreeClassifier(\n", + " # feature_combinations=feature_combinations,\n", + " random_state=seed,\n", + ")\n", + "\n", + "estimator = HonestForestClassifier(\n", + " n_estimators=n_estimators,\n", + " n_jobs=n_jobs,\n", + " random_state=seed,\n", + " tree_estimator=tree_estimator,\n", + ")\n", + "est = SupervisedInfoForest(\n", + " estimator=estimator, y_categorical=True, n_jobs=n_jobs, random_state=seed\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "ebee771a-c4cc-4fda-9e36-1987c1bdee25", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
SupervisedInfoForest(estimator=HonestForestClassifier(n_jobs=-1,\n",
+       "                                                      random_state=12345,\n",
+       "                                                      tree_estimator=DecisionTreeClassifier(random_state=12345)),\n",
+       "                     n_jobs=-1, random_state=12345, y_categorical=True)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + ], + "text/plain": [ + "SupervisedInfoForest(estimator=HonestForestClassifier(n_jobs=-1,\n", + " random_state=12345,\n", + " tree_estimator=DecisionTreeClassifier(random_state=12345)),\n", + " n_jobs=-1, random_state=12345, y_categorical=True)" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "est.fit(_X, y, _Z)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "4ca327d7-b1f6-49a1-ad2e-3d7346904c6e", + "metadata": {}, + "outputs": [], + "source": [ + "check_is_fitted(est)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "b0781b14-051e-4476-9bd4-44c6af841f8e", + "metadata": {}, + "outputs": [ + { + "ename": "NotFittedError", + "evalue": "This HonestTreeClassifier instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNotFittedError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[38], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[43mest\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mestimator_yxz_\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mapply\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[43m)\u001b[49m)\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/ensemble/_honest_forest.py:512\u001b[0m, in \u001b[0;36mHonestForestClassifier.apply\u001b[0;34m(self, X)\u001b[0m\n\u001b[1;32m 495\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mapply\u001b[39m(\u001b[38;5;28mself\u001b[39m, X):\n\u001b[1;32m 496\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 497\u001b[0m \u001b[38;5;124;03m Apply trees in the forest to X, return leaf indices.\u001b[39;00m\n\u001b[1;32m 498\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 510\u001b[0m \u001b[38;5;124;03m return the index of the leaf x ends up in.\u001b[39;00m\n\u001b[1;32m 511\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 512\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mestimator_\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mapply\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/_lib/./sklearn/tree/_classes.py:792\u001b[0m, in \u001b[0;36mBaseDecisionTree.apply\u001b[0;34m(self, X, check_input)\u001b[0m\n\u001b[1;32m 768\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mapply\u001b[39m(\u001b[38;5;28mself\u001b[39m, X, check_input\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m):\n\u001b[1;32m 769\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Return the index of the leaf that each sample is predicted as.\u001b[39;00m\n\u001b[1;32m 770\u001b[0m \n\u001b[1;32m 771\u001b[0m \u001b[38;5;124;03m .. versionadded:: 0.17\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 790\u001b[0m \u001b[38;5;124;03m numbering.\u001b[39;00m\n\u001b[1;32m 791\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 792\u001b[0m \u001b[43mcheck_is_fitted\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 793\u001b[0m X \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_validate_X_predict(X, check_input)\n\u001b[1;32m 794\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtree_\u001b[38;5;241m.\u001b[39mapply(X)\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/sklearn/utils/validation.py:1462\u001b[0m, in \u001b[0;36mcheck_is_fitted\u001b[0;34m(estimator, attributes, msg, all_or_any)\u001b[0m\n\u001b[1;32m 1459\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m is not an estimator instance.\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m%\u001b[39m (estimator))\n\u001b[1;32m 1461\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m _is_fitted(estimator, attributes, all_or_any):\n\u001b[0;32m-> 1462\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m NotFittedError(msg \u001b[38;5;241m%\u001b[39m {\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mname\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;28mtype\u001b[39m(estimator)\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m})\n", + "\u001b[0;31mNotFittedError\u001b[0m: This HonestTreeClassifier instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator." + ] + } + ], + "source": [ + "print(est.estimator_yxz_.apply(X))" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "fd03e849-854e-423c-93d7-214eb52558b3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "HonestForestClassifier(n_jobs=-1, random_state=12345,\n", + " tree_estimator=DecisionTreeClassifier(random_state=424562602))\n", + "None\n", + "None\n", + "None\n" + ] + } + ], + "source": [ + "print(est.estimator_yxz_)\n", + "print(check_is_fitted(est))\n", + "print(check_is_fitted(est.estimator_yxz_))\n", + "print(check_is_fitted(est.estimator_yz_))" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "ba9a8f70-555b-4f9e-9d79-0ad8f64f9ecc", + "metadata": {}, + "outputs": [ + { + "ename": "NotFittedError", + "evalue": "This HonestTreeClassifier instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNotFittedError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[27], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mest\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mestimator_yxz_\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mapply\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/ensemble/_honest_forest.py:512\u001b[0m, in \u001b[0;36mHonestForestClassifier.apply\u001b[0;34m(self, X)\u001b[0m\n\u001b[1;32m 495\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mapply\u001b[39m(\u001b[38;5;28mself\u001b[39m, X):\n\u001b[1;32m 496\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 497\u001b[0m \u001b[38;5;124;03m Apply trees in the forest to X, return leaf indices.\u001b[39;00m\n\u001b[1;32m 498\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 510\u001b[0m \u001b[38;5;124;03m return the index of the leaf x ends up in.\u001b[39;00m\n\u001b[1;32m 511\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 512\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mestimator_\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mapply\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/_lib/./sklearn/tree/_classes.py:792\u001b[0m, in \u001b[0;36mBaseDecisionTree.apply\u001b[0;34m(self, X, check_input)\u001b[0m\n\u001b[1;32m 768\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mapply\u001b[39m(\u001b[38;5;28mself\u001b[39m, X, check_input\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m):\n\u001b[1;32m 769\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Return the index of the leaf that each sample is predicted as.\u001b[39;00m\n\u001b[1;32m 770\u001b[0m \n\u001b[1;32m 771\u001b[0m \u001b[38;5;124;03m .. versionadded:: 0.17\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 790\u001b[0m \u001b[38;5;124;03m numbering.\u001b[39;00m\n\u001b[1;32m 791\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 792\u001b[0m \u001b[43mcheck_is_fitted\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 793\u001b[0m X \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_validate_X_predict(X, check_input)\n\u001b[1;32m 794\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtree_\u001b[38;5;241m.\u001b[39mapply(X)\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/sklearn/utils/validation.py:1462\u001b[0m, in \u001b[0;36mcheck_is_fitted\u001b[0;34m(estimator, attributes, msg, all_or_any)\u001b[0m\n\u001b[1;32m 1459\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m is not an estimator instance.\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m%\u001b[39m (estimator))\n\u001b[1;32m 1461\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m _is_fitted(estimator, attributes, all_or_any):\n\u001b[0;32m-> 1462\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m NotFittedError(msg \u001b[38;5;241m%\u001b[39m {\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mname\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;28mtype\u001b[39m(estimator)\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m})\n", + "\u001b[0;31mNotFittedError\u001b[0m: This HonestTreeClassifier instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator." + ] + } + ], + "source": [ + "est.estimator_yxz_.apply(X)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "e5e5d360-6007-4997-83dc-ff4bb425ab8e", + "metadata": {}, + "outputs": [ + { + "ename": "NotFittedError", + "evalue": "This HonestTreeClassifier instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNotFittedError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[20], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m pred_cmi \u001b[38;5;241m=\u001b[39m \u001b[43mest\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpredict_cmi\u001b[49m\u001b[43m(\u001b[49m\u001b[43m_X\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m_Z\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/experimental/forest.py:89\u001b[0m, in \u001b[0;36mSupervisedInfoForest.predict_cmi\u001b[0;34m(self, X, y, Z)\u001b[0m\n\u001b[1;32m 86\u001b[0m sample_indices \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39marange(\u001b[38;5;241m0\u001b[39m, X\u001b[38;5;241m.\u001b[39mshape[\u001b[38;5;241m0\u001b[39m])\n\u001b[1;32m 88\u001b[0m \u001b[38;5;66;03m# compute the entropy of H(Y | X, Z)\u001b[39;00m\n\u001b[0;32m---> 89\u001b[0m H_yxz \u001b[38;5;241m=\u001b[39m \u001b[43mcond_entropy\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 90\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mestimator_yxz_\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mXZ\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msample_indices\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkappa\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m3\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mseed\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrandom_state\u001b[49m\n\u001b[1;32m 91\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 93\u001b[0m \u001b[38;5;66;03m# compute the entropy of H(Y | Z)\u001b[39;00m\n\u001b[1;32m 94\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m Z \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/experimental/forest.py:160\u001b[0m, in \u001b[0;36mcond_entropy\u001b[0;34m(est, X, y, estimators_samples_, kappa, seed)\u001b[0m\n\u001b[1;32m 155\u001b[0m check_is_forest(est)\n\u001b[1;32m 156\u001b[0m \u001b[38;5;66;03m# if \"multioutput\" in est._get_tags():\u001b[39;00m\n\u001b[1;32m 157\u001b[0m \u001b[38;5;66;03m# raise ValueError(\"Multioutput is not supported.\")\u001b[39;00m\n\u001b[1;32m 158\u001b[0m \n\u001b[1;32m 159\u001b[0m \u001b[38;5;66;03m# get leaves of each tree for each sample (n_samples, n_estimators)\u001b[39;00m\n\u001b[0;32m--> 160\u001b[0m X_leaves \u001b[38;5;241m=\u001b[39m \u001b[43mest\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mapply\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 161\u001b[0m n_classes \u001b[38;5;241m=\u001b[39m est\u001b[38;5;241m.\u001b[39mn_classes_\n\u001b[1;32m 162\u001b[0m n_samples \u001b[38;5;241m=\u001b[39m X\u001b[38;5;241m.\u001b[39mshape[\u001b[38;5;241m0\u001b[39m]\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/ensemble/_honest_forest.py:512\u001b[0m, in \u001b[0;36mHonestForestClassifier.apply\u001b[0;34m(self, X)\u001b[0m\n\u001b[1;32m 495\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mapply\u001b[39m(\u001b[38;5;28mself\u001b[39m, X):\n\u001b[1;32m 496\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 497\u001b[0m \u001b[38;5;124;03m Apply trees in the forest to X, return leaf indices.\u001b[39;00m\n\u001b[1;32m 498\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 510\u001b[0m \u001b[38;5;124;03m return the index of the leaf x ends up in.\u001b[39;00m\n\u001b[1;32m 511\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 512\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mestimator_\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mapply\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/Documents/scikit-tree/sktree/_lib/./sklearn/tree/_classes.py:792\u001b[0m, in \u001b[0;36mBaseDecisionTree.apply\u001b[0;34m(self, X, check_input)\u001b[0m\n\u001b[1;32m 768\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mapply\u001b[39m(\u001b[38;5;28mself\u001b[39m, X, check_input\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m):\n\u001b[1;32m 769\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Return the index of the leaf that each sample is predicted as.\u001b[39;00m\n\u001b[1;32m 770\u001b[0m \n\u001b[1;32m 771\u001b[0m \u001b[38;5;124;03m .. versionadded:: 0.17\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 790\u001b[0m \u001b[38;5;124;03m numbering.\u001b[39;00m\n\u001b[1;32m 791\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 792\u001b[0m \u001b[43mcheck_is_fitted\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 793\u001b[0m X \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_validate_X_predict(X, check_input)\n\u001b[1;32m 794\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtree_\u001b[38;5;241m.\u001b[39mapply(X)\n", + "File \u001b[0;32m~/miniforge3/envs/sktree/lib/python3.9/site-packages/sklearn/utils/validation.py:1462\u001b[0m, in \u001b[0;36mcheck_is_fitted\u001b[0;34m(estimator, attributes, msg, all_or_any)\u001b[0m\n\u001b[1;32m 1459\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m is not an estimator instance.\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m%\u001b[39m (estimator))\n\u001b[1;32m 1461\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m _is_fitted(estimator, attributes, all_or_any):\n\u001b[0;32m-> 1462\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m NotFittedError(msg \u001b[38;5;241m%\u001b[39m {\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mname\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;28mtype\u001b[39m(estimator)\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m})\n", + "\u001b[0;31mNotFittedError\u001b[0m: This HonestTreeClassifier instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator." + ] + } + ], + "source": [ + "pred_cmi = est.predict_cmi(_X, y, _Z)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "726a83f2-5084-4958-9d01-71e2c3e86ebb", + "metadata": {}, + "outputs": [], + "source": [ + "pred_mi = est.predict_cmi(_X, y)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18b82f0f-5842-4f2d-8239-1a0e4067c282", + "metadata": {}, + "outputs": [], + "source": [ + "print(pred_cmi, pred_mi)" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "c299a578-1984-4030-a34d-eb3219edd4be", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-0.009832259764467277 -0.19976608189849127\n" + ] + } + ], + "source": [ + "print(pred_cmi, pred_mi)" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "7ab5065a-f606-4281-9631-15b1c460e048", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-0.006589316434129411 -0.19854781628986418\n" + ] + } + ], + "source": [ + "print(pred_cmi, pred_mi)" + ] + }, + { + "cell_type": "markdown", + "id": "63a9f1b5-fee5-416b-823b-c3781fa7a2d4", + "metadata": {}, + "source": [ + "# Analysis of dimensionality " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "842ccb8a-c25a-4d2d-93e4-ea70687a3bba", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e50fca6-a6ac-42ee-9b27-78e3b0552350", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d03d7125-9378-4058-957c-f79f59c38fc4", + "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/sktree/experimental/__init__.py b/sktree/experimental/__init__.py index cdf4b4295..888ecc9c9 100644 --- a/sktree/experimental/__init__.py +++ b/sktree/experimental/__init__.py @@ -1,4 +1,6 @@ from . import mutual_info, sdf, simulate +from .forest import SupervisedInfoForest +from .ksg import entropy_continuous, mutual_info_ksg from .mutual_info import ( cmi_from_entropy, cmi_gaussian, @@ -7,6 +9,5 @@ mi_from_entropy, mi_gamma, mi_gaussian, - mutual_info_ksg, ) from .sdf import StreamDecisionForest diff --git a/sktree/experimental/forest.py b/sktree/experimental/forest.py new file mode 100644 index 000000000..34df5f48a --- /dev/null +++ b/sktree/experimental/forest.py @@ -0,0 +1,271 @@ +from copy import deepcopy + +import numpy as np +from numpy.typing import ArrayLike +from scipy.stats import entropy +from sklearn.base import BaseEstimator, MetaEstimatorMixin +from sklearn.ensemble._forest import BaseForest + +from sktree.utils import check_is_forest + +from .ksg import entropy_continuous + + +class SupervisedInfoForest(BaseEstimator, MetaEstimatorMixin): + def __init__(self, estimator, y_categorical: bool = False, n_jobs=None, random_state=None): + """Meta estimator for mutual information. + + This supervised forest estimator uses two supervised forests to estimate + the (conditional) mutual information between X and Y given Z. + + Parameters + ---------- + estimator : _type_ + _description_ + y_categorical : bool, optional + _description_, by default False + n_jobs : _type_, optional + _description_, by default None + random_state : _type_, optional + _description_, by default None + """ + self.estimator = estimator + self.y_categorical = y_categorical + self.n_jobs = n_jobs + self.random_state = random_state + + def fit(self, X, y, Z=None): + X, y = self._validate_data(X, y, accept_sparse="csc") + check_is_forest(self.estimator, allow_tree=False, ensure_fitted=False) + + self.estimator_yxz_ = deepcopy(self.estimator) + self.estimator_yz_ = deepcopy(self.estimator) + # self.estimator_yxz_ = CalibratedClassifierCV( + # copy(self.estimator), method='isotonic', cv=5, + # ) + # self.estimator_yz_ = CalibratedClassifierCV( + # copy(self.estimator), method='isotonic', cv=5, + # ) + + if Z is not None: + XZ = np.hstack((X, Z)) + else: + XZ = X + + # compute the entropy of H(Y | X, Z) + self.estimator_yxz_.fit(XZ, y) + + # compute the entropy of H(Y | Z) + if Z is None: + # compute entropy using empirical distribution + if self.y_categorical: + _, self.counts_ = np.unique(y, return_counts=True) + else: + self.H_yz_ = entropy_continuous(y, k=5, n_jobs=self.n_jobs) + else: + self.estimator_yz_.fit(Z, y) + + return self + + def predict_cmi(self, X, y, Z=None): + if Z is not None: + X, y = self._validate_data(X, y, accept_sparse="csc") + Z = self._validate_data(Z, accept_sparse="csc") + else: + X = self._validate_data(X, accept_sparse="csc") + + if Z is not None: + XZ = np.hstack((X, Z)) + else: + XZ = X + + # if not fitted yet + if not hasattr(self, "estimator_yz_"): + self.fit(X, y, Z) + + sample_indices = np.arange(0, X.shape[0]) + + # compute the entropy of H(Y | X, Z) + H_yxz = cond_entropy( + self.estimator_yxz_, XZ, y, sample_indices, kappa=3, seed=self.random_state + ) + + # compute the entropy of H(Y | Z) + if Z is None: + if self.y_categorical: + # compute entropy using empirical distribution + H_yz = entropy(self.counts_, base=np.exp(1)) + else: + H_yz = self.H_yz_ + else: + H_yz = cond_entropy( + self.estimator_yz_, Z, y, sample_indices, kappa=3, seed=self.random_state + ) + + return H_yxz - H_yz + + +def approx_marginal(est: BaseForest, X: ArrayLike, weighted: bool = False): + """Compute the approximate marginal distribution. + + A fitted tree/forest can be used to compute the approximate marginal + by counting the number of training (weighted) training samples + that fall into each leaf node for samples in X. + + Parameters + ---------- + est : Forest + Fitted forest or tree estimator. + X : ArrayLike of shape (n_samples, n_features_in_) + Samples to compute the approximate marginal distribution for. + weighted : bool, default=False + Whether to use the weighted number of samples in each leaf node. + """ + check_is_forest(est) + n_samples = X.shape[0] + p_leaves = np.zeros(n_samples, est.n_estimators) + + # leaf indices (n_samples, n_estimators) + X_leaves = est.apply(X) + + for idx, tree_ in enumerate(est.estimators_.tree_): + # compute the empirical distribution of the leaf nodes + # (n_nodes,) array + if weighted: + n_node_samples = tree_.weighted_n_node_samples + else: + n_node_samples = tree_.n_node_samples + + # compute the total number of samples used in this tree + total_samples_in_tree = n_node_samples.sum() + + # compute the empirical distribution of the leaf nodes for each sample in X + tree_leaves = X_leaves[:, idx] + p_leaves[:, idx] = n_node_samples[tree_leaves] / total_samples_in_tree + + # return the average over all trees + return p_leaves.mean(axis=1) + + +def cond_entropy( + est: BaseForest, X: ArrayLike, y: ArrayLike, estimators_samples_: ArrayLike, kappa=3, seed=None +): + rng = np.random.default_rng(seed) + + check_is_forest(est) + # if "multioutput" in est._get_tags(): + # raise ValueError("Multioutput is not supported.") + + # get leaves of each tree for each sample (n_samples, n_estimators) + X_leaves = est.apply(X) + n_classes = est.n_classes_ + n_samples = X.shape[0] + + cond_entropy = 0.0 + for tree_idx, tree in enumerate(est.estimators_): + # (n_nodes,) array of number of samples + node_counts = tree.tree_.n_node_samples + n_nodes = len(node_counts) + + # (n_nodes, n_classes_) + class_counts = np.zeros((n_nodes, n_classes)) + + # Find the indices of the training set used for partition. + sampled_indices = estimators_samples_[tree_idx] + unsampled_indices = np.delete(np.arange(0, n_samples), sampled_indices) + + # Randomly split the rest into voting and evaluation. + total_unsampled = len(unsampled_indices) + rng.shuffle(unsampled_indices) + vote_indices = unsampled_indices[: total_unsampled // 2] + eval_indices = unsampled_indices[total_unsampled // 2 :] + + # true classes of evaluation points + true_classes = y[vote_indices] + + # estimated nodes + tree_vote_leaves = X_leaves[vote_indices, tree_idx] + n_vote_samples = len(tree_vote_leaves) + for i in range(n_vote_samples): + class_counts[tree_vote_leaves[i], true_classes[i]] += 1 + + # compute total number of samples in each leaf node + # then compute probabilities per class for each node + # (n_nodes, n_classes_) + n_node_leaves = class_counts.sum(axis=1) + class_probs = _finite_sample_correction(class_counts, n_node_leaves, n_classes, kappa=kappa) + + # Place evaluation points in their corresponding leaf node. + # Store evaluation posterior in a num_eval-by-num_class matrix. + tree_eval_leaves = tree.apply(X[eval_indices, :]) + eval_class_probs = class_probs[tree_eval_leaves, :] + + # compute the entropy per sample + eval_entropies = [entropy(posterior) for posterior in eval_class_probs] + cond_entropy += np.mean(eval_entropies) + return cond_entropy / est.n_estimators + + +def _finite_sample_correction(class_counts, n_samples_per_node, n_classes, kappa=3): + """Perform finite sample correction on class probabilities. + + This applies the following procedure: + + - if a leaf (i.e. node) is pure for a certain class, meaning there is 0 samples + for that class in the node, then the probability of that class is replaced + with ``1 / (kappa * n_classes)``, where `kappa` is a hyperparameter and + + + Parameters + ---------- + class_counts : array-like of shape (n_nodes, n_classes) + Class counts in each node. + n_samples_per_node : array-like of shape (n_nodes,) + Total number of samples in each node. + n_classes : int + Number of classes. + kappa : float, default=3 + Corrective factor hyperparameter. + """ + # Avoid divide by zero. + n_samples_per_node[n_samples_per_node == 0] = 1 + + class_probs = np.divide(class_counts, n_samples_per_node[:, np.newaxis]) + + # Make the nodes that have no estimation indices uniform. + # This includes non-leaf nodes, but that will not affect the estimate. + class_probs[np.argwhere(class_probs.sum(axis=1) == 0)] = 1.0 / n_classes + + # Apply finite sample correction and renormalize. + where_0 = np.argwhere(class_probs == 0) + for row, col in where_0: + class_probs[row, col] = 1.0 / (kappa * class_counts[row].sum()) + row_sums = class_probs.sum(axis=1) + class_probs = class_probs / row_sums[:, None] + + return class_probs + + +def approx_joint(est: BaseForest, X: ArrayLike, P_Y: ArrayLike): + """Compute the approximate joint distribution. + + A fitted tree/forest can be used to compute the approximate joint + by counting the number of training (weighted) training samples + that fall into each leaf node for samples in X. Then the conditional + distribution is computed as the number of Y samples in each for a + fitted tree or forest for P(Y | X). + + Parameters + ---------- + est : Forest + The fitted supervised forest. + X : ArrayLike of shape (n_samples, n_features_in_) + Input data. + P_Y : ArrayLike of shape (n_samples,) + The approximate marginal distribution of X. + """ + check_is_forest(est) + if est._get_tags().get("multioutput", False): + raise ValueError("Multioutput is not supported.") + + est.apply(X) diff --git a/sktree/experimental/ksg.py b/sktree/experimental/ksg.py new file mode 100644 index 000000000..2be021caa --- /dev/null +++ b/sktree/experimental/ksg.py @@ -0,0 +1,552 @@ +from typing import Optional + +import numpy as np +import scipy.linalg +import scipy.special +import scipy.stats +from numpy.typing import ArrayLike +from sklearn.base import BaseEstimator +from sklearn.neighbors import NearestNeighbors +from sklearn.preprocessing import StandardScaler + + +def entropy_continuous(X, k=1, norm="max", min_dist=0.0, n_jobs=-1): + """Estimates the entropy H of a continuous random variable. + + The approach uses the kth-nearest neighbour distances between sample points + from :footcite:`kozachenko1987sample`. See: + https://github.com/paulbrodersen/entropy_estimators/blob/master/ + + Parameters + ---------- + X : ArrayLike of shape (n_samples, n_features) + N samples from a d-dimensional multivariate distribution + + k : int (default 1) + kth nearest neighbour to use in density estimate; + imposes smoothness on the underlying probability distribution + + norm : 'euclidean' or 'max' + p-norm used when computing k-nearest neighbour distances + + min_dist : float (default 0.) + minimum distance between data points; + smaller distances will be capped using this value + + n_jobs : int (default 1) + number of workers to use for parallel processing in query; + -1 uses all CPU threads + + Returns + ------- + h: float + entropy H(X) + + References + ---------- + .. footbibliography:: + """ + if X.ndim == 1: + X = X[:, np.newaxis] + n, d = X.shape + + if norm == "max": # max norm: + p = np.inf + log_c_d = 0 # volume of the d-dimensional unit ball + elif norm == "euclidean": # euclidean norm + p = 2 + log_c_d = (d / 2.0) * np.log(np.pi) - np.log(scipy.special.gamma(d / 2.0 + 1)) + else: + raise NotImplementedError("Variable 'norm' either 'max' or 'euclidean'") + + kdtree = scipy.spatial.KDTree(X) + + # query all points -- k+1 as query point also in initial set + # distances, _ = kdtree.query(x, k + 1, eps=0, p=norm) + distances, _ = kdtree.query(X, k + 1, eps=0, p=p, workers=n_jobs) + distances = distances[:, -1] + + # enforce non-zero distances + distances[distances < min_dist] = min_dist + + sum_log_dist = np.sum(np.log(2.0 * distances)) # where did the 2 come from? radius -> diameter + h = ( + -scipy.special.digamma(k) + + scipy.special.digamma(n) + + log_c_d + + (d / float(n)) * sum_log_dist + ) + + return h + + +def mutual_info_ksg_nn( + X, + Y, + Z=None, + k: float = 0.2, + norm="max", + n_jobs: int = -1, + transform: str = "rank", + random_seed: int = None, + verbose: bool = False, +): + rng = np.random.default_rng(random_seed) + + data = np.hstack((X, Y)) + x_idx = np.arange(X.shape[1]) + y_idx = np.arange(Y.shape[1]) + X.shape[1] + z_idx = np.array([]) + if Z is not None: + z_idx = np.arange(Z.shape[1]) + data.shape[1] + data = np.hstack((data, Z)) + + data = _preprocess_data(data, transform, rng) + if verbose: + print(f"Data shape: {data.shape}") + print(f"X shape: {X.shape}, Y shape: {Y.shape}") + if Z is not None: + print(f"Z shape: {Z.shape}") + print("Preprocessing complete.") + n_samples = data.shape[0] + + # get the number of neighbors to use in estimating the CMI + if k < 1: + knn_here = max(1, int(k * n_samples)) + else: + knn_here = max(1, int(k)) + + if verbose: + print(f"Using {knn_here} neighbors to define D-dimensional volume.") + + if Z is not None: + val = _cmi_ksg_scipy(data, x_idx, y_idx, z_idx, knn_here, n_jobs=n_jobs) + else: + val = _mi_ksg_scipy(data, x_idx, y_idx, knn_here, n_jobs=n_jobs) + + if norm == "max": + norm_constant = np.log(1) + else: + norm_constant = np.log( + np.pi ** (data.shape[1] / 2) + / scipy.special.gamma(data.shape[1] / 2 + 1) + / 2 ** data.shape[1] + ) + + return val + norm_constant + + +def mutual_info_ksg( + X, + Y, + Z=None, + k: float = 0.2, + norm="max", + nn_estimator=None, + n_jobs: int = -1, + transform: str = "rank", + random_seed: int = None, + verbose: bool = False, +): + """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. + If a number less than 1, then the number of neighbors is computed as + ``k * n_samples``. + norm : str, optional, {'max', 'euclidean'} + The norm to use in computing the distance, by default 'max'. This is the norm + used for computing the distance between points in the covariate space and + affects the constant term in the KSG estimator. For 'max' norm, the constant + term is :math:`\\log(1) = 0`, and for 'euclidean' norm, the constant term is + :math:`\\pi^{d/2} / \\Gamma(d/2 + 1) / 2^d)`, where :math:`d` is the dimension. + nn_estimator : str, optional + The nearest neighbor estimator to use, by default None. If None will default + to using :class:`sklearn.neighbors.NearestNeighbors` with default parameters. + 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. + verbose : bool, optional + Whether to print verbose output, by default False. + + 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, the analytical solution is: + + .. math:: + + \\psi(k) - E[(\\psi(n_x) + \\psi(n_y))] + \\psi(n) + + 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 + 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. + + The hyperparamter ``k`` defines the number of points in the D-dimensional + ball with a specified radius. The larger the k, the higher the bias in + the estimate, but the lower the variance. The smaller the k, the lower + the bias, but the higher the variance. The default value of 0.2 is set + to allow scaling with the number of samples. + """ + rng = np.random.default_rng(random_seed) + + if nn_estimator is None: + nn_estimator = NearestNeighbors(n_jobs=n_jobs) + + data = np.hstack((X, Y)) + x_idx = np.arange(X.shape[1]) + y_idx = np.arange(Y.shape[1]) + X.shape[1] + z_idx = np.array([]) + if Z is not None: + z_idx = np.arange(Z.shape[1]) + data.shape[1] + data = np.hstack((data, Z)) + + data = _preprocess_data(data, transform, rng) + if verbose: + print(f"Data shape: {data.shape}") + print(f"X shape: {X.shape}, Y shape: {Y.shape}") + if Z is not None: + print(f"Z shape: {Z.shape}") + print("Preprocessing complete.") + n_samples = data.shape[0] + + # get the number of neighbors to use in estimating the CMI + if k < 1: + knn_here = max(1, int(k * n_samples)) + else: + knn_here = max(1, int(k)) + + if verbose: + print(f"Using {knn_here} neighbors to define D-dimensional volume.") + print(data.shape, x_idx, y_idx, z_idx) + + if Z is not None: + val = _cmi_ksg(data, x_idx, y_idx, z_idx, nn_estimator, knn_here) + else: + val = _mi_ksg(data, x_idx, y_idx, nn_estimator, knn_here) + + if norm == "max": + norm_constant = np.log(1) + else: + norm_constant = np.log( + np.pi ** (data.shape[1] / 2) + / scipy.special.gamma(data.shape[1] / 2 + 1) + / 2 ** data.shape[1] + ) + + return val + norm_constant + + +def _preprocess_data(data, transform, rng): + if transform not in ("rank", "standardize", "uniform", None): + raise ValueError( + f"Unknown transform {transform}. Must " + f"be one of 'rank', 'standardize', 'uniform', or None." + ) + + n_samples, n_features = data.shape + + # add minor noise to make sure there are no ties + random_noise = rng.random((n_samples, n_features)) + data += 1e-5 * random_noise @ np.std(data, axis=0).reshape(n_features, 1) + + # optionally transform the data + 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) + return data + + +def _cmi_ksg_scipy(data, x_idx, y_idx, z_idx, knn_here: int, n_jobs: int = -1) -> float: + tree_xyz = scipy.spatial.KDTree(data) + radius_per_sample = tree_xyz.query(data, k=[knn_here + 1], p=np.inf, eps=0.0, workers=n_jobs)[ + 0 + ][:, 0].astype(np.float64) + + # To search neighbors < eps + radius_per_sample = np.multiply(radius_per_sample, 0.99999) + + # compute on the subspace of X, Z + xz_idx = np.concatenate((x_idx, z_idx)).squeeze() + tree_xz = scipy.spatial.KDTree(data[:, xz_idx]) + num_nn_xz = tree_xz.query_ball_point( + data[:, xz_idx], r=radius_per_sample, eps=0.0, p=np.inf, workers=n_jobs, return_length=True + ) + + # compute on the subspace of Y, Z + yz_idx = np.concatenate((y_idx, z_idx)).squeeze() + tree_yz = scipy.spatial.KDTree(data[:, yz_idx]) + num_nn_yz = tree_yz.query_ball_point( + data[:, yz_idx], r=radius_per_sample, eps=0.0, p=np.inf, workers=n_jobs, return_length=True + ) + + tree_z = scipy.spatial.KDTree(data[:, z_idx]) + num_nn_z = tree_z.query_ball_point( + data[:, z_idx], r=radius_per_sample, eps=0.0, p=np.inf, workers=n_jobs, return_length=True + ) + + # 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 _mi_ksg_scipy(data, x_idx, y_idx, knn_here: int, n_jobs: int = -1) -> float: + n_samples = data.shape[0] + + tree_xyz = scipy.spatial.KDTree(data) + radius_per_sample = tree_xyz.query(data, k=[knn_here + 1], p=np.inf, eps=0.0, workers=n_jobs)[ + 0 + ][:, 0].astype(np.float64) + + # To search neighbors < eps + radius_per_sample = np.multiply(radius_per_sample, 0.99999) + + # compute on the subspace of X + tree_x = scipy.spatial.KDTree(data[:, x_idx]) + num_nn_x = tree_x.query_ball_point( + data[:, x_idx], r=radius_per_sample, eps=0.0, p=np.inf, workers=n_jobs, return_length=True + ) + + # compute on the subspace of Y + tree_y = scipy.spatial.KDTree(data[:, y_idx]) + num_nn_y = tree_y.query_ball_point( + data[:, x_idx], r=radius_per_sample, eps=0.0, p=np.inf, workers=n_jobs, return_length=True + ) + + # 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 _mi_ksg( + data, x_idx, y_idx, nn_estimator: BaseEstimator, knn_here: int, verbose: bool = False +) -> float: + """Compute KSG estimate of MI. + + Parameters + ---------- + data : ArrayLike + Stacked data of X and Y. + x_idx : ArrayLike + Indices for the X data stored as a 1D array. + y_idx : ArrayLike + Indices for the Y data stored as a 1D array. + nn_estimator : BaseEstimator + Nearest neighbor estimator. + knn_here : int + Number of nearest neighbors used in nn_estimator to estimate the volume + of the joint distribution. + + Returns + ------- + val : float + Estimated MI value. + """ + n_samples = data.shape[0] + + if verbose: + print(f"Fitting nearest neighbors estimator with {data.shape} data.") + + # estimate distance to the kth NN in XYZ subspace for each sample + neigh = nn_estimator.fit(data) + dists, _ = neigh.kneighbors(n_neighbors=knn_here) + + if verbose: + print(f"Computing radii for {knn_here} nn and got dists {dists.shape}.") + + # - get the radius we want to use per sample as the distance to the kth neighbor + # in the joint distribution space + radius_per_sample = dists[:, -1] + + # compute on the subspace of X + if verbose: + print(f"Computing radius neighbors for X with {x_idx.shape} indices.") + num_nn_x = _compute_radius_nbrs(data, radius_per_sample, nn_estimator, col_idx=x_idx) + + # compute on the subspace of Y + if verbose: + print(f"Computing radius neighbors for Y with {y_idx.shape} indices.") + num_nn_y = _compute_radius_nbrs(data, radius_per_sample, nn_estimator, col_idx=y_idx) + + # 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: ArrayLike, x_idx, y_idx, z_idx, nn_estimator: BaseEstimator, knn_here: int +) -> float: + """Compute KSG estimate of CMI. + + Parameters + ---------- + data : ArrayLike + Stacked data of X, Y and Z. + x_idx : ArrayLike + Indices for the X data stored as a 1D array. + y_idx : ArrayLike + Indices for the Y data stored as a 1D array. + z_idx : ArrayLike + Indices for the Z data stored as a 1D array. + nn_estimator : BaseEstimator + Nearest neighbor estimator. + knn_here : int + Number of nearest neighbors used in nn_estimator to estimate the volume + of the joint distribution. + + Returns + ------- + val : float + Estimated CMI value. + """ + # estimate distance to the kth NN in XYZ subspace for each sample + nn_estimator = nn_estimator.fit(data) + dists, _ = nn_estimator.kneighbors(n_neighbors=knn_here) + + # get the radius we want to use per sample as the distance to the kth neighbor + # in the joint distribution space + radius_per_sample = dists[:, -1] + + # compute on the subspace of XZ + xz_idx = np.concatenate((x_idx, z_idx)).squeeze() + num_nn_xz = _compute_radius_nbrs( + data, + radius_per_sample, + nn_estimator, + col_idx=xz_idx, + ) + + # compute on the subspace of YZ + yz_idx = np.concatenate((y_idx, z_idx)).squeeze() + num_nn_yz = _compute_radius_nbrs( + data, + radius_per_sample, + nn_estimator, + col_idx=yz_idx, + ) + + # compute on the subspace of XZ + num_nn_z = _compute_radius_nbrs( + data, + radius_per_sample, + nn_estimator, + col_idx=z_idx, + ) + + # 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: ArrayLike, + radius_per_sample: ArrayLike, + nn_estimator: BaseEstimator, + col_idx: Optional[ArrayLike] = None, +): + # compute distances in the subspace defined by data + nn_estimator.fit(data[:, col_idx]) + + # compute the radius neighbors for each sample + if getattr(nn_estimator, "_supports_multi_radii", False): + nn = nn_estimator.radius_neighbors( + X=data[:, col_idx], radius=radius_per_sample, return_distance=False + ) + else: + nn = [] + for idx, radius in enumerate(radius_per_sample): + nn_ = nn_estimator.radius_neighbors( + X=np.atleast_2d(data[:, col_idx]), radius=radius, return_distance=False + ) + nn.append(nn_[idx]) + + num_nn_data = np.array([len(nn_) for nn_ in nn]) + return num_nn_data + + +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/meson.build b/sktree/experimental/meson.build index 2be7ae9e8..dfed10b82 100644 --- a/sktree/experimental/meson.build +++ b/sktree/experimental/meson.build @@ -2,6 +2,9 @@ python_sources = [ '__init__.py', 'mutual_info.py', 'simulate.py', + 'ksg.py', + 'forest.py', + 'monte_carlo.py', 'sdf.py', ] diff --git a/sktree/experimental/monte_carlo.py b/sktree/experimental/monte_carlo.py new file mode 100644 index 000000000..0c412a782 --- /dev/null +++ b/sktree/experimental/monte_carlo.py @@ -0,0 +1,238 @@ +from typing import Optional + +import numpy as np +from numpy.typing import ArrayLike +from scipy.sparse import issparse +from sklearn.neighbors import NearestNeighbors +from sklearn.utils import _approximate_mode, _safe_indexing, check_array, check_consistent_length + + +def _conditional_shuffle(nbrs: ArrayLike, replace: bool = False, rng=None) -> ArrayLike: + """Compute a permutation of neighbors with restrictions. + + Parameters + ---------- + nbrs : ArrayLike of shape (n_samples, k) + The k-nearest-neighbors for each sample index. Each row corresponds to the + original sample. Each element corresponds to another sample index that is deemed + as the k-nearest neighbors with respect to the original sample. + replace : bool, optional + Whether or not to allow replacement of samples, by default False. + random_seed : int, optional + Random seed, by default None. + + Returns + ------- + restricted_perm : ArrayLike of shape (n_samples) + The final permutation order of the sample indices. There may be + repeating samples. See Notes for details. + + Notes + ----- + Restricted permutation goes through random samples and looks at the k-nearest + neighbors (columns of ``nbrs``) and shuffles the closest neighbor index only + if it has not been used to permute another sample. If it has been, then the + algorithm looks at the next nearest-neighbor and so on. If all k-nearest + neighbors of a sample has been checked, then a random neighbor is chosen. In this + manner, the algorithm tries to perform permutation without replacement, but + if necessary, will choose a repeating neighbor sample. + """ + n_samples, k_dims = nbrs.shape + rng = np.random.default_rng(seed=rng) + + # initialize the final permutation order + restricted_perm = np.zeros((n_samples,), dtype=np.intp) + + # generate a random order of samples to go through + random_order = rng.permutation(n_samples) + + # keep track of values we have already used + used = set() + + # go through the random order + for idx in random_order: + if replace: + restricted_perm[idx] = rng.choice(nbrs[idx, :], size=1) + else: + m = 0 + use_idx = nbrs[idx, m] + + # if the current nbr is already used, continue incrementing + # until we have either found a new sample to use, or if + # we have reach the maximum number of shuffles to consider + while (use_idx in used) and (m < k_dims - 1): + m += 1 + use_idx = nbrs[idx, m] + + # check whether or not we have exhaustively checked all kNN + if use_idx in used and m == k_dims: + # XXX: Note this step is not in the original paper + # choose a random neighbor to permute + restricted_perm[idx] = rng.choice(nbrs[idx, :], size=1) + else: + # permute with the existing neighbor + restricted_perm[idx] = use_idx + used.add(use_idx) + return restricted_perm + + +def conditional_resample( + conditional_array, + *arrays, + nn_estimator=None, + replace=True, + replace_nbrs=True, + n_samples=None, + random_state=None, + stratify=None, +): + """Conditionally resample arrays or sparse matrices in a consistent way. + + The default strategy implements one step of the bootstrapping + procedure. Conditional resampling is a modification of the bootstrap + technique that preserves the conditional distribution of the data. This + is done by fitting a nearest neighbors estimator on the conditional array + and then resampling the nearest neighbors of each sample. + + Parameters + ---------- + conditional_array : array-like of shape (n_samples, n_features) + The array, which we preserve the conditional distribution of. + + *arrays : sequence of array-like of shape (n_samples,) or \ + (n_samples, n_outputs) + Indexable data-structures can be arrays, lists, dataframes or scipy + sparse matrices with consistent first dimension. + + nn_estimator : estimator object, default=None + The nearest neighbors estimator to use. If None, then a + :class:`sklearn.neighbors.NearestNeighbors` instance is used. + + replace : bool, default=True + Implements resampling with replacement. If False, this will implement + (sliced) random permutations. The replacement will take place at the level + of the sample index. + + replace_nbrs : bool, default=True + Implements resampling with replacement at the level of the nearest neighbors. + + n_samples : int, default=None + Number of samples to generate. If left to None this is + automatically set to the first dimension of the arrays. + If replace is False it should not be larger than the length of + arrays. + + random_state : int, RandomState instance or None, default=None + Determines random number generation for shuffling + the data. + Pass an int for reproducible results across multiple function calls. + See :term:`Glossary `. + + stratify : array-like of shape (n_samples,) or (n_samples, n_outputs), \ + default=None + If not None, data is split in a stratified fashion, using this as + the class labels. + + Returns + ------- + resampled_arrays : sequence of array-like of shape (n_samples,) or \ + (n_samples, n_outputs) + Sequence of resampled copies of the collections. The original arrays + are not impacted. + """ + max_n_samples = n_samples + rng = np.random.default_rng(random_state) + + if len(arrays) == 0: + return None + + first = arrays[0] + n_samples = first.shape[0] if hasattr(first, "shape") else len(first) + + if max_n_samples is None: + max_n_samples = n_samples + elif (max_n_samples > n_samples) and (not replace): + raise ValueError( + "Cannot sample %d out of arrays with dim %d when replace is False" + % (max_n_samples, n_samples) + ) + + check_consistent_length(conditional_array, *arrays) + + # fit nearest neighbors onto the conditional array + if nn_estimator is None: + nn_estimator = NearestNeighbors() + nn_estimator.fit(conditional_array) + + if stratify is None: + if replace: + indices = rng.integers(0, n_samples, size=(max_n_samples,)) + else: + indices = np.arange(n_samples) + rng.shuffle(indices) + indices = indices[:max_n_samples] + else: + # Code adapted from StratifiedShuffleSplit() + y = check_array(stratify, ensure_2d=False, dtype=None) + if y.ndim == 2: + # for multi-label y, map each distinct row to a string repr + # using join because str(row) uses an ellipsis if len(row) > 1000 + y = np.array([" ".join(row.astype("str")) for row in y]) + + classes, y_indices = np.unique(y, return_inverse=True) + n_classes = classes.shape[0] + + class_counts = np.bincount(y_indices) + + # Find the sorted list of instances for each class: + # (np.unique above performs a sort, so code is O(n logn) already) + class_indices = np.split( + np.argsort(y_indices, kind="mergesort"), np.cumsum(class_counts)[:-1] + ) + + n_i = _approximate_mode(class_counts, max_n_samples, random_state) + + indices = [] + + for i in range(n_classes): + indices_i = rng.choice(class_indices[i], n_i[i], replace=replace) + indices.extend(indices_i) + + indices = rng.permutation(indices) + + # now get the kNN indices for each sample (n_samples, n_neighbors) + sample_nbrs = nn_estimator.kneighbors(X=conditional_array[indices, :], return_distance=False) + + # actually sample the indices using a conditional permutation + indices = _conditional_shuffle(sample_nbrs, replace=replace_nbrs, rng=rng) + + # convert sparse matrices to CSR for row-based indexing + arrays = [a.tocsr() if issparse(a) else a for a in arrays] + resampled_arrays = [_safe_indexing(a, indices) for a in arrays] + if len(resampled_arrays) == 1: + # syntactic sugar for the unit argument case + return resampled_arrays[0] + else: + return resampled_arrays + + +def conditional_shuffle( + conditional_array, *arrays, random_state: Optional[int] = None, n_samples: Optional[int] = None +): + """_summary_ + + Parameters + ---------- + conditional_array : ArrayLike of shape (n_samples, k) + The array, which we preserve the conditional distribution of. + *arrays : sequence of ArrayLike + Indexable data-structures can be arrays, lists, dataframes or scipy sparse matrices + with consistent first dimension. + random_state : int, optional + Random seed, by default None. + n_samples : int, optional + Number of samples to generate, by default None. If left to None this is + automatically set to the first dimension of the arrays. It should not be larger + than the length of arrays. + """ + pass diff --git a/sktree/experimental/mutual_info.py b/sktree/experimental/mutual_info.py index a1820e715..ac5384329 100644 --- a/sktree/experimental/mutual_info.py +++ b/sktree/experimental/mutual_info.py @@ -1,15 +1,5 @@ -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): @@ -24,14 +14,8 @@ def entropy_gaussian(cov): 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)) + true_entropy : float + The true entropy of the generated multivariate Gaussian distribution. References ---------- @@ -43,13 +27,17 @@ def entropy_gaussian(cov): return true_entropy -def mi_gaussian(cov): +def mi_gaussian(cov, x_index, y_index): """Compute mutual information of a multivariate Gaussian. 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. Returns ------- @@ -62,12 +50,20 @@ def mi_gaussian(cov): H(X) = \\frac{d}{2} (1 + log(2\\pi)) + \\frac{1}{2} log(det(C)) """ + x_index = np.atleast_1d(x_index) + y_index = np.atleast_1d(y_index) + # computes the true MI - true_mi = -0.5 * np.log(np.linalg.det(cov)) + det_x = np.linalg.det(cov[np.ix_(x_index, x_index)]) + det_y = np.linalg.det(cov[np.ix_(y_index, y_index)]) + det_xy = np.linalg.det( + cov[np.ix_(np.concatenate((x_index, y_index)), np.concatenate((x_index, y_index)))] + ) + true_mi = 0.5 * np.log((det_x * det_y) / det_xy) return true_mi -def cmi_gaussian(cov, x_index, y_index, z_index): +def cmi_gaussian(cov, x_index, y_index, z_index=None): """Compute the analytical CMI for a multivariate Gaussian distribution. Parameters @@ -95,22 +91,28 @@ def cmi_gaussian(cov, x_index, y_index, z_index): 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] - + x_index = np.atleast_1d(x_index).astype(int) + y_index = np.atleast_1d(y_index).astype(int) + if z_index is None: + z_index = [] + z_index = np.atleast_1d(z_index).astype(int) + + xz_index = np.concatenate((x_index, z_index)) + yz_index = np.concatenate((y_index, z_index)) + + cov_xz = cov[np.ix_(xz_index, xz_index)] + cov_yz = cov[np.ix_(yz_index, yz_index)] + cov_z = cov[np.ix_(z_index, z_index)] + cov_xyz = cov[ + np.ix_( + np.concatenate((x_index, y_index, z_index)), np.concatenate((x_index, y_index, z_index)) + ) + ] cmi = ( entropy_gaussian(cov_xz) + entropy_gaussian(cov_yz) - entropy_gaussian(cov_z) - - entropy_gaussian(cov) + - entropy_gaussian(cov_xyz) ) return cmi @@ -136,312 +138,3 @@ def mi_from_entropy(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:`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 - 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, the analytical solution is: - - .. math:: - - \\psi(k) - E[(\\psi(n_x) + \\psi(n_y))] + \\psi(n) - - 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 - 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) - - data = np.hstack((X, Y)) - if Z is not None: - 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: - 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)) - 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) - return data - - -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 - # - 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) - 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): - 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 -) -> NearestNeighbors: - """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:`sktree.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) - - # 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 index 17b56145f..6a3f904f7 100644 --- a/sktree/experimental/simulate.py +++ b/sktree/experimental/simulate.py @@ -2,6 +2,8 @@ import scipy.linalg import scipy.special import scipy.stats +from scipy.integrate import nquad +from scipy.stats import multivariate_normal def simulate_helix( @@ -221,3 +223,246 @@ def simulate_multivariate_gaussian(mean=None, cov=None, d=2, n_samples=1000, see data = rng.multivariate_normal(mean=mean, cov=cov, size=(n_samples)) return data, mean, cov + + +def embed_high_dims(data, n_dims=50, random_state=None): + rng = np.random.default_rng(random_state) + + new_data = np.zeros((data.shape[0], n_dims + data.shape[1])) + new_data[:, : data.shape[1]] = data + + for idim in range(n_dims): + new_col = rng.standard_normal(size=(data.shape[0],)) + new_data[:, data.shape[1] + idim] = new_col + + return new_data + + +def simulate_separate_gaussians( + n_dims=2, + n_samples=1000, + n_classes=2, + mean1: float = None, + var1: float = None, + pi=None, + seed=None, +): + """Simulate data from separate multivariate Gaussians. + + Parameters + ---------- + n_dims : int + The dimensionality of the data. The default is 2. + n_samples : int + The number of samples to generate. The default is 1000. + n_classes : int + The number of classes to generate. The default is 2. + mean1 : float + The mean of the first dimension of the first class. If None (default), then a random + standard normal vector is drawn. + var1 : float + The covariance matrix of the first class. If None (default), then a random standard + normal 2D array is drawn. It is then converted to a PD matrix. + pi : array-like of shape (n_classes,) + The class probabilities. If None (default), then uniform class probabilities are used. + seed : int + The random seed to feed to :func:`numpy.random.default_rng`. The default is None. + + Returns + ------- + data : array-like of shape (n_samples, n_dims) + The generated data. + y : array-like of shape (n_samples,) + The class labels. + means : list of array-like of shape (n_dims,) + The means of the Gaussians from each class. + sigmas : list of array-like of shape (n_dims, n_dims) + The covariance matrices of the Gaussians from each class. + pi : array-like of shape (n_classes,) + The class probabilities. + + Notes + ----- + This simulates data from separate multivariate Gaussians, where each class has its own + multivariate Gaussian distribution. The class labels are sampled from a multinomial distribution + with probabilities `pi`. + + The ground-truth computation of the MI depends on + """ + rng = np.random.default_rng(seed) + + if pi is None: + pi = np.ones((n_classes,)) / n_classes + else: + if len(pi) != n_classes: + raise RuntimeError(f"pi should be of length {n_classes}") + + # first sample the class labels according to class probabilities + counts = rng.multinomial(n_samples, pi, size=1)[0] + + # now sample the multivariate Gaussian for each class + means = [np.zeros((n_dims,))] + sigmas = [np.eye(n_dims)] + + if mean1 is not None: + means[0][0] = mean1 + if var1 is not None: + sigmas[0][0, 0] = var1 + + # sample additional classes if not binary + for _ in range(1, n_classes): + mean = rng.standard_normal(size=(n_dims,)) + sigma = np.eye(n_dims) + + means.append(mean) + sigmas.append(sigma) + + # now sample the data + X_data = [] + y_data = [] + for k in range(n_classes): + X_data.append(rng.multivariate_normal(means[k], sigmas[k], counts[k])) + y_data.append(np.repeat(k, counts[k])) + X = np.concatenate(tuple(X_data)) + y = np.concatenate(tuple(y_data)) + + return X, y, means, sigmas, pi + + +def mi_separated_gaussians(means, sigmas, pi, seed=None): + """Compute the ground-truth mutual information between the class labels and the data. + + Parameters + ---------- + means : list of array-like of shape (n_dims,) + The means of the Gaussians from each class. The list has length ``n_classes``. + sigmas : list of array-like of shape (n_dims, n_dims) + The covariance matrices of the Gaussians from each class. + The list has length ``n_classes``. + pi : array-like of shape (n_classes,) + The class probabilities. + seed : int + The random seed to feed to :func:`numpy.random.default_rng`. The default is None. + + Returns + ------- + I_XY : float + The ground-truth mutual information between the class labels and the data. + """ + n_dims = means[0].shape[0] + n_classes = len(sigmas) + + # compute ground-truth MI + base = np.exp(1) + # H_Y = entropy(pi, base=base) + + def func(*args): + # points at which to evaluate the multivariate-Gaussian + x_points = np.array(args) + + # compute the probability of the multivariate-Gaussian at various points in the + # d-dimensional space + p = 0.0 + for k in range(n_classes): + p += pi[k] * multivariate_normal.pdf(x_points, mean=means[k], cov=sigmas[k]) + + # compute the log-probability + return -p * np.log(p) / np.log(base) + + # limits over each dimension of the multivariate-Gaussian + lims = [[-10, 10]] * n_dims + H_X, _ = nquad(func, ranges=lims) + + # now compute H(X|Y) + H_XY = _conditional_entropy_separated_gaussians(sigmas, pi, base) + + I_XY = H_X - H_XY + return I_XY + + +def _conditional_entropy_separated_gaussians(sigmas, pi, base): + """Conditional entropy. + + Computes H(X | Y), where X can be multivariate and is assumed to be multivariate-Gaussian. + The determinant of the covariance matrix of X is used to compute the entropy. + + Y is assumed to be discrete. + X is some multivariate-Gaussian, where the covariance matrix is given by `sigmas` + and provides the entropy of X for each class. + """ + n_classes = len(sigmas) + n_dims = sigmas[0].shape[0] + + # now compute H(Y|X) = H(X, Y) - H(X) + H_XY = 0.0 + for k in range(n_classes): + # [d * log(2 * pi) + log(det(sigma)) + d] / (2 * log(base)) + H_XY += ( + pi[k] + * (n_dims * np.log(2 * np.pi) + np.log(np.linalg.det(sigmas[k])) + n_dims) + / (2.0 * np.log(base)) + ) + return H_XY + + +def cmi_separated_gaussians(means, sigmas, pi, condition_idx, seed=None): + """Compute the ground-truth conditional mutual information. + + This computes the CMI between the class labels and the data. + """ + n_classes = len(means) + + x_idx = np.ones((means[0].shape[0],), dtype=np.bool_) + x_idx[condition_idx] = False + # Z_sigmas = [sigma[condition_idx, condition_idx] for sigma in sigmas] + # X_sigmas = [sigma[x_idx, x_idx] for sigma in sigmas] + base = np.exp(1) + + def func(*args): + # points at which to evaluate the multivariate-Gaussian + x_points = np.array(args) + + # compute the probability of the multivariate-Gaussian at various points in the + # d-dimensional space + p = 0.0 + for k in range(n_classes): + p += pi[k] * multivariate_normal.pdf(x_points, mean=means[k], cov=sigmas[k]) + + # compute the log-probability + return -p * np.log(p) / np.log(base) + + # integrate to get approximate H(X, Z) + n_dims = means[0].shape[0] + lims = [[-10, 10]] * n_dims + H_XZ, _ = nquad(func, ranges=lims) + + def func(*args): + # points at which to evaluate the multivariate-Gaussian + x_points = np.array(args) + + # compute the probability of the multivariate-Gaussian at various points in the + # d-dimensional space + p = 0.0 + for k in range(n_classes): + p += pi[k] * multivariate_normal.pdf(x_points, mean=z_means[k], cov=z_sigmas[k]) + + # compute the log-probability + return -p * np.log(p) / np.log(base) + + # get approximate H(Z) + z_means = [mean[condition_idx] for mean in means] + z_sigmas = [sigma[np.ix_(condition_idx, condition_idx)] for sigma in sigmas] + n_dims = z_means[0].shape[0] + lims = [[-10, 10]] * n_dims + H_Z, _ = nquad(func, ranges=lims) + + # now compute H(X, Z|Y) + H_XZY = _conditional_entropy_separated_gaussians(sigmas, pi, base) + + # lastly compute H(Z |Y) + H_ZY = _conditional_entropy_separated_gaussians(z_sigmas, pi, base) + + # now compute H(X|Y,Z) + print(H_XZ, H_Z, H_XZY, H_ZY) + I_XYZ = H_XZ - H_Z - H_XZY + H_ZY + return I_XYZ diff --git a/sktree/experimental/tests/meson.build b/sktree/experimental/tests/meson.build index b5d1ef79c..b1c0880c4 100644 --- a/sktree/experimental/tests/meson.build +++ b/sktree/experimental/tests/meson.build @@ -2,6 +2,8 @@ python_sources = [ '__init__.py', 'test_mutual_info.py', 'test_simulate.py', + 'test_ksg.py', + 'test_monte_carlo.py', 'test_sdf.py', ] diff --git a/sktree/experimental/tests/test_ksg.py b/sktree/experimental/tests/test_ksg.py new file mode 100644 index 000000000..c2be62114 --- /dev/null +++ b/sktree/experimental/tests/test_ksg.py @@ -0,0 +1,51 @@ +import itertools + +import numpy as np + +from sktree.experimental.ksg import entropy_continuous +from sktree.experimental.mutual_info import entropy_gaussian + +seed = 12345 + +rng = np.random.default_rng(seed) + + +def get_mvn_data(total_rvs, dimensionality=2, scale_sigma_offdiagonal_by=1.0, total_samples=1000): + data_space_size = total_rvs * dimensionality + + # initialise distribution + mu = rng.standard_normal(data_space_size) + sigma = rng.rand(data_space_size, data_space_size) + + # ensures that sigma is positive semi-definite + sigma = np.dot(sigma.transpose(), sigma) + + # scale off-diagonal entries -- might want to change that to block diagonal entries + # diag = np.diag(sigma).copy() + # sigma *= scale_sigma_offdiagonal_by + # sigma[np.diag_indices(len(diag))] = diag + + # scale off-block diagonal entries + d = dimensionality + for ii, jj in itertools.product(list(range(total_rvs)), repeat=2): + if ii != jj: + sigma[d * ii : d * (ii + 1), d * jj : d * (jj + 1)] *= scale_sigma_offdiagonal_by + + # get samples + samples = rng.multivariate_normal(mu, sigma, size=total_samples) + + return [samples[:, ii * d : (ii + 1) * d] for ii in range(total_rvs)] + + +def test_get_h_1d(k=5, norm="max"): + X = rng.standard_normal(size=(1000, 1)) + cov_X = np.atleast_2d(np.cov(X.T)) + + analytic = entropy_gaussian(cov_X) + kozachenko = entropy_continuous(X, k=k, norm=norm) + + print("analytic result: {:.5f}".format(analytic)) + print("K-L estimator: {:.5f}".format(kozachenko)) + assert np.isclose( + analytic, kozachenko, rtol=0.1, atol=0.1 + ), "K-L estimate strongly differs from analytic expectation!" diff --git a/sktree/experimental/tests/test_monte_carlo.py b/sktree/experimental/tests/test_monte_carlo.py new file mode 100644 index 000000000..9a24868d1 --- /dev/null +++ b/sktree/experimental/tests/test_monte_carlo.py @@ -0,0 +1,51 @@ +import numpy as np +from scipy.sparse import csr_matrix +from sklearn.datasets import make_classification +from sklearn.neighbors import NearestNeighbors + +from sktree.experimental.monte_carlo import conditional_resample + + +def test_conditional_resample(): + # Generate synthetic data + X, y = make_classification(n_samples=100, n_features=5, random_state=42) + + # Convert X to sparse matrix + X_sparse = csr_matrix(X) + + # Create conditional array + nn = NearestNeighbors(n_neighbors=5) + nn.fit(X) + conditional_array = nn.kneighbors_graph(X).toarray() + + # Perform conditional resampling + resampled_X = conditional_resample(conditional_array, X, replace=False, replace_nbrs=False) + resampled_X_sparse = conditional_resample( + conditional_array, X_sparse, replace=False, replace_nbrs=False + ) + + # Check that the resampled arrays have the correct shape + assert resampled_X.shape == X.shape + assert resampled_X_sparse.shape == X_sparse.shape + + # Check that the resampled arrays have the correct number of unique samples + assert len(np.unique(resampled_X, axis=0)) == X.shape[0] + assert len(np.unique(resampled_X_sparse.toarray(), axis=0)) == X_sparse.shape[0] + + # Check that the conditional distribution is preserved + for i in range(X.shape[1]): + unique_values, counts = np.unique(resampled_X[:, i], return_counts=True) + original_values, original_counts = np.unique(X[:, i], return_counts=True) + + assert np.all(unique_values == original_values) + assert np.all(counts == original_counts) + + unique_values_sparse, counts_sparse = np.unique( + resampled_X_sparse[:, i].toarray(), return_counts=True + ) + original_values_sparse, original_counts_sparse = np.unique( + X_sparse[:, i].toarray(), return_counts=True + ) + + assert np.all(unique_values_sparse == original_values_sparse) + assert np.all(counts_sparse == original_counts_sparse) diff --git a/sktree/meson.build b/sktree/meson.build index 8608052b8..82900ef42 100644 --- a/sktree/meson.build +++ b/sktree/meson.build @@ -54,6 +54,7 @@ cython_c_args += numpy_nodepr_api python_sources = [ '__init__.py', 'neighbors.py', + 'utils.py', 'conftest.py', ] diff --git a/sktree/neighbors.py b/sktree/neighbors.py index 1d6e1ed84..2137b92cd 100644 --- a/sktree/neighbors.py +++ b/sktree/neighbors.py @@ -2,6 +2,7 @@ from copy import copy import numpy as np +from joblib import Parallel, delayed from sklearn.base import BaseEstimator, MetaEstimatorMixin from sklearn.exceptions import NotFittedError from sklearn.neighbors import NearestNeighbors @@ -10,11 +11,32 @@ from sktree.tree._neighbors import _compute_distance_matrix, compute_forest_similarity_matrix +def forest_distance(clf, X, Y) -> float: + """Compute a valid distance metric between two samples using a decision-tree or forest model.""" + X = np.atleast_2d(X) + Y = np.atleast_2d(Y) + XY = np.concatenate((X, Y), axis=0) + aff_matrix = compute_forest_similarity_matrix(clf, XY) + + # dists should be (2, 2) + dists = _compute_distance_matrix(aff_matrix) + if dists.shape != (2, 2): + raise RuntimeError("This shouldn't happen") + + return dists[0, 1] + + 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. + and then uses the sklearn's nearest-neighbors API to compute neighbors. Thus, + this meta-estimator is a two-stage process: + + 1. Fit a forest on X (n_samples, n_features) to compute a distance matrix + (n_samples, n_samples). + 2. Fit an instance of `sklearn.neighbors.NearestNeighbors` on the distance matrix to compute + nearest neighbors. Parameters ---------- @@ -29,14 +51,31 @@ class NearestNeighborsMetaEstimator(BaseEstimator, MetaEstimatorMixin): See :class:`sklearn.neighbors.NearestNeighbors` for details. n_jobs : int, optional The number of parallel jobs to run for neighbors, by default None. + force_fit : bool, optional + If True, the estimator will be fit even if it is already fitted, by default False. + verbose : bool, optional + If True, print out additional information, by default False. """ - def __init__(self, estimator, n_neighbors=5, radius=1.0, algorithm="auto", n_jobs=None): + _supports_multi_radii: bool = True + + def __init__( + self, + estimator, + n_neighbors=5, + radius=1.0, + algorithm="auto", + n_jobs=None, + force_fit=False, + verbose: bool = False, + ): self.estimator = estimator self.n_neighbors = n_neighbors self.algorithm = algorithm self.radius = radius self.n_jobs = n_jobs + self.force_fit = force_fit + self.verbose = verbose def fit(self, X, y=None): """Fit the nearest neighbors estimator from the training dataset. @@ -56,15 +95,31 @@ def fit(self, X, y=None): self : object Fitted estimator. """ - X, y = self._validate_data(X, y, accept_sparse="csc") + if y is not None: + X, y = self._validate_data(X, y, accept_sparse="csc") + else: + X = self._validate_data(X, accept_sparse="csc") self.estimator_ = copy(self.estimator) - try: - check_is_fitted(self.estimator_) - except NotFittedError: + if self.force_fit: self.estimator_.fit(X, y) + else: + try: + check_is_fitted(self.estimator_) + except NotFittedError: + self.estimator_.fit(X, y) + + if self.verbose: + print(f"Finished fitting estimator: {self.estimator_}") + + # get the number of neighbors to use in estimating the CMI + n_samples = X.shape[0] + if self.n_neighbors < 1: + knn_here = max(1, int(self.n_neighbors * n_samples)) + else: + knn_here = max(1, int(self.n_neighbors)) - self._fit(X, self.n_neighbors) + self._fit(X, knn_here) return self def _fit(self, X, n_neighbors): @@ -76,12 +131,20 @@ def _fit(self, X, n_neighbors): ) # compute the distance matrix - aff_matrix = compute_forest_similarity_matrix(self.estimator_, X) - dists = _compute_distance_matrix(aff_matrix) + dists = self._compute_distance_matrix(X) + + if self.verbose: + print(f"Finished computing distance matrix: {dists.shape}") # fit the nearest-neighbors estimator self.neigh_est_.fit(dists) + def _compute_distance_matrix(self, X): + # compute the distance matrix + aff_matrix = compute_forest_similarity_matrix(self.estimator_, X) + dists = _compute_distance_matrix(aff_matrix) + return dists + def kneighbors(self, X=None, n_neighbors=None, return_distance=True): """Find the K-neighbors of a point. @@ -182,26 +245,60 @@ def radius_neighbors(self, X=None, radius=None, return_distance=True, sort_resul if X is not None: n_samples = X.shape[0] + X = self._compute_distance_matrix(X) + + if self.verbose: + print(f"Finished computing distance matrix: {X.shape}") 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 self.verbose: + print(f"Computing radius neighbors for {n_samples} samples") + + if isinstance(radius, numbers.Number): + # return only neighbors within one fixed radius across all samples + return self.neigh_est_.radius_neighbors( + X=X, radius=radius, return_distance=return_distance, sort_results=sort_results + ) + else: + # forest-based nearest neighbors needs to support all samples to get pairwise + # distances before querying the radius neighbors API + if X is None: + raise RuntimeError("Must provide X if radius is an array of numbers") + + if len(radius) != n_samples: + raise RuntimeError(f"Expected {n_samples} radius values, got {len(radius)}") + + nn_inds_arr = np.zeros((n_samples,), dtype=object) + nn_dist_arr = np.zeros((n_samples,), dtype=object) + + # compute radius neighbors for each sample in parallel + if self.verbose: + print("Computing radius neighbors in parallel...") + + result = Parallel(n_jobs=self.n_jobs)( + delayed(_parallel_radius_nbrs)( + self.neigh_est_.radius_neighbors, + np.atleast_2d(X[idx, :]), + radius[idx], + return_distance, + ) + for idx in range(n_samples) ) + for idx, nn in enumerate(result): + if return_distance: + nn_inds_arr[idx] = nn[0][0] + nn_dist_arr[idx] = nn[1][0] + else: + nn_inds_arr[idx] = nn[0] + if return_distance: - nn_ind_data[idx] = nn[0][idx] - nn_dist_data[idx] = nn[1][idx] + return nn_dist_arr, nn_inds_arr else: - nn_ind_data[idx] = nn + return nn_inds_arr + - if return_distance: - return nn_dist_data, nn_ind_data - return nn_ind_data +def _parallel_radius_nbrs(radius_nbr_func, X, radius, return_distance): + return radius_nbr_func(X, radius, return_distance) diff --git a/sktree/tests/test_unsupervised_forest.py b/sktree/tests/test_unsupervised_forest.py index 7bd1e0116..4218f86d2 100644 --- a/sktree/tests/test_unsupervised_forest.py +++ b/sktree/tests/test_unsupervised_forest.py @@ -53,7 +53,7 @@ def test_check_simulation(name, forest, criterion): n_features = 5 if criterion == "twomeans": expected_score = 0.05 - elif criterion == "fastbic": + elif criterion in ("fastbic", "fasterbic"): expected_score = 0.35 else: n_features = 20 @@ -96,7 +96,7 @@ def test_check_iris(name, forest, criterion): expected_score = 0.21 else: expected_score = 0.2 - elif criterion == "fastbic": + elif criterion in ("fastbic", "fasterbic"): if "oblique" in name.lower(): expected_score = 0.55 else: diff --git a/sktree/tree/_classes.py b/sktree/tree/_classes.py index 4763bb53d..36297ac71 100644 --- a/sktree/tree/_classes.py +++ b/sktree/tree/_classes.py @@ -60,7 +60,11 @@ "best": _morf_splitter.BestPatchSplitter, } -UNSUPERVISED_CRITERIA = {"twomeans": _unsup_criterion.TwoMeans, "fastbic": _unsup_criterion.FastBIC} +UNSUPERVISED_CRITERIA = { + "twomeans": _unsup_criterion.TwoMeans, + "fastbic": _unsup_criterion.FastBIC, + "fasterbic": _unsup_criterion.FasterBIC, +} UNSUPERVISED_SPLITTERS = { "best": _unsup_splitter.BestUnsupervisedSplitter, } @@ -73,7 +77,7 @@ class UnsupervisedDecisionTree(SimMatrixMixin, TransformerMixin, ClusterMixin, B Parameters ---------- - criterion : {"twomeans", "fastbic"}, default="twomeans" + criterion : {"twomeans", "fastbic", "fasterbic"}, default="twomeans" The function to measure the quality of a split. Supported criteria are "twomeans" for the variance impurity and "fastbic" for the BIC criterion. If ``UnsupervisedCriterion`` instance is passed in, then @@ -169,7 +173,7 @@ class UnsupervisedDecisionTree(SimMatrixMixin, TransformerMixin, ClusterMixin, B Notes ----- - The "faster" BIC criterion enablescomputation of the split point evaluations + The "faster" BIC criterion enables computation of the split point evaluations in O(n) time given that the samples are sorted. This algorithm is described in :footcite:`marx2022estimating` and :footcite:`terzi2006efficient` and enables fast variance computations for the twomeans and fastbic criteria. diff --git a/sktree/tree/_oblique_splitter.pyx b/sktree/tree/_oblique_splitter.pyx index 1dfb256c3..488978294 100644 --- a/sktree/tree/_oblique_splitter.pyx +++ b/sktree/tree/_oblique_splitter.pyx @@ -112,6 +112,8 @@ cdef class BaseObliqueSplitter(Splitter): cdef intp_t col_idx cdef float32_t col_weight + # XXX: this should be able to be improved, if we can flip the order of the for-loops + # XXX: we can add a hashmap here to keep track of the min/max of each feature # Compute linear combination of features and then # sort samples according to the feature values. for jdx in range(0, proj_vec_indices.size()): @@ -433,6 +435,7 @@ cdef class BestObliqueSplitter(ObliqueSplitter): deref(oblique_split).impurity_right = best_split.impurity_right return 0 + cdef class RandomObliqueSplitter(ObliqueSplitter): def __reduce__(self): """Enable pickling the splitter.""" diff --git a/sktree/tree/manifold/_morf_splitter.pyx b/sktree/tree/manifold/_morf_splitter.pyx index 79be42a47..c7f8a0185 100644 --- a/sktree/tree/manifold/_morf_splitter.pyx +++ b/sktree/tree/manifold/_morf_splitter.pyx @@ -452,7 +452,7 @@ cdef class BestPatchSplitterTester(BestPatchSplitter): patch_dims = np.array(self.patch_dims_buff, dtype=np.intp) return top_left_patch_seed, patch_size, patch_dims - cpdef sample_projection_vector( + cpdef sample_projection_vector_py( self, intp_t proj_i, intp_t patch_size, diff --git a/sktree/tree/tests/test_unsupervised_tree.py b/sktree/tree/tests/test_unsupervised_tree.py index 2dba0fab8..645c2e34d 100644 --- a/sktree/tree/tests/test_unsupervised_tree.py +++ b/sktree/tree/tests/test_unsupervised_tree.py @@ -133,7 +133,7 @@ def test_check_simulation(name, Tree, criterion): expected_score = 0.02 else: expected_score = 0.3 - elif criterion == "fastbic": + elif criterion in ("fastbic", "fasterbic"): if "oblique" in name.lower(): expected_score = 0.005 else: @@ -172,7 +172,7 @@ def test_check_rotated_blobs(name, Tree, criterion): expected_score = 0.02 else: expected_score = 0.3 - elif criterion == "fastbic": + elif criterion in ("fastbic", "fasterbic"): if "oblique" in name.lower(): expected_score = 0.005 else: @@ -196,7 +196,10 @@ def test_check_rotated_blobs(name, Tree, criterion): def test_check_iris(name, Tree, criterion): # Check consistency on dataset iris. n_classes = len(np.unique(iris.target)) - est = Tree(criterion=criterion, random_state=123) + est = Tree( + criterion=criterion, min_samples_split=np.sqrt(len(iris)).astype(int), random_state=12345 + ) + est.fit(iris.data, iris.target) sim_mat = est.compute_similarity_matrix(iris.data) @@ -206,7 +209,7 @@ def test_check_iris(name, Tree, criterion): expected_score = 0.12 else: expected_score = 0.01 - elif criterion == "fastbic": + elif criterion in ("fastbic", "fasterbic"): if "oblique" in name.lower(): expected_score = 0.005 else: @@ -220,3 +223,28 @@ def test_check_iris(name, Tree, criterion): assert score > expected_score, "Iris failed with {0}, criterion = {1} and score = {2}".format( name, criterion, score ) + + +@pytest.mark.skip() +def test_fasterbic_vs_fastbic_on_iris(): + fastbic_tree = UnsupervisedDecisionTree(criterion="fastbic", random_state=1) + fasterbic_tree = UnsupervisedDecisionTree(criterion="fasterbic", random_state=0) + + n_classes = len(np.unique(iris.target)) + fastbic_tree.fit(iris.data, iris.target) + fasterbic_tree.fit(iris.data, iris.target) + + sim_mat = fastbic_tree.compute_similarity_matrix(iris.data) + cluster = AgglomerativeClustering(n_clusters=n_classes).fit(sim_mat) + predict_labels = cluster.fit_predict(sim_mat) + fastbic_score = adjusted_rand_score(iris.target, predict_labels) + + sim_mat = fasterbic_tree.compute_similarity_matrix(iris.data) + cluster = AgglomerativeClustering(n_clusters=n_classes).fit(sim_mat) + predict_labels = cluster.fit_predict(sim_mat) + fasterbic_score = adjusted_rand_score(iris.target, predict_labels) + + print(fastbic_score, fasterbic_score) + assert False + # assert_array_equal(fastbic_tree.compute_similarity_matrix(iris.data), + # fasterbic_tree.compute_similarity_matrix(iris.data)) diff --git a/sktree/tree/unsupervised/_unsup_criterion.pyx b/sktree/tree/unsupervised/_unsup_criterion.pyx index 58f21f348..794277ab6 100644 --- a/sktree/tree/unsupervised/_unsup_criterion.pyx +++ b/sktree/tree/unsupervised/_unsup_criterion.pyx @@ -8,6 +8,7 @@ cimport numpy as cnp import numpy as np from libc.math cimport log +from libcpp.unordered_map cimport unordered_map cnp.import_array() @@ -506,3 +507,234 @@ cdef class FastBIC(TwoMeans): else: impurity_left[0] = -BIC_same_var_left impurity_right[0] = -BIC_same_var_right + + +cdef class FasterBIC(UnsupervisedCriterion): + r"""Faster-BIC split criterion + + This utilizes a trick from [2]_ to improve the computation for the variance. + Since we have an arbitrary segment $X_i, ..., X_j$ with $1 \le i \le j \le n_samples$, + we can compute the variance in O(1) time. + + Reference: + [1] https://arxiv.org/pdf/2110.13883.pdf + [2] E. Terzi, Problems and algorithms for sequence segmen- tations. Helsingin yliopisto, 2006. + """ + cdef unordered_map[SIZE_t, DTYPE_t] cumsum_of_squares_map + cdef unordered_map[SIZE_t, DTYPE_t] cumsum_map + cdef unordered_map[SIZE_t, DTYPE_t] cumsum_weights_map + + cdef double bic_cluster(self, SIZE_t n_samples, double variance) noexcept nogil: + """Help compute the BIC from assigning to a specific cluster. + + Parameters + ---------- + n_samples : SIZE_t + The number of samples assigned cluster. + variance : double + The plug-in variance for assigning to specific cluster. + + Notes + ----- + Computes the following: + + :math:`-2 * (n_i log(w_i) - n_i/2 log(2 \\pi \\sigma_i^2)) + + where :math:`n_i` is the number of samples assigned to cluster i, + :math:`w_i` is the probability of choosing cluster i at random (or also known + as the prior) and :math:`\\sigma_i^2` is the variance estimate for cluster i. + + Note that :math:`\\sigma_i^2` in the Fast-BIC derivation may be the + variance of the cluster itself, or the estimated combined variance + from both clusters. + """ + cdef SIZE_t n_node_samples = self.n_node_samples + + # chances of choosing the cluster based on how many samples are hard-assigned to cluster + # i.e. the prior + # cast to double, so we do not round to integers + cdef double w_cluster = (n_samples + 0.0) / n_node_samples + + # add to prevent taking log of 0 when there is a degenerate cluster (i.e. single sample, or no variance) + return -2. * (n_samples * log(w_cluster) + 0.5 * n_samples * log(2. * PI * variance + 1.e-7)) + + cdef double node_impurity( + self + ) noexcept nogil: + """Evaluate the impurity of the current node. + + Evaluate the FastBIC criterion impurity as estimated maximum log likelihood. + This is the maximum likelihood given prior, mean, and variance at s number of samples + Namely, this is the maximum likelihood of Xf[sample_indices[start:end]]. + The smaller the impurity the better. + """ + cdef double variance + cdef double impurity + cdef SIZE_t n_node_samples = self.n_node_samples + + # then compute the variance of the cluster + # see Section 5.2 in reference. + variance = self.fast_total_variance(self.weighted_n_node_samples, self.end) + + # Compute the BIC of the current set of samples + # Note: we do not compute the BIC_diff_var and BIC_same_var because + # they are equivalent in the single cluster setting + impurity = self.bic_cluster(n_node_samples, variance) + return impurity + + cdef inline DTYPE_t fast_total_variance(self, double weighted_n_node_samples, SIZE_t j) noexcept nogil: + """Computes variance in O(1). + + Computes: + + \\sigma_{i,j}^2 = \\frac{1}{j-i+1} ((css_j - css_{i-1}) - \\frac{1}{j-i+1} (cs_j - cs_{i-1})^2) + """ + cdef double normalizer = 1. / weighted_n_node_samples + cdef SIZE_t s_idx = self.sample_indices[j] + return normalizer * \ + ( + (self.cumsum_of_squares_map[s_idx]) - + (normalizer * (self.cumsum_map[s_idx]) * (self.cumsum_map[s_idx])) + ) + + cdef inline DTYPE_t fast_variance(self, double weighted_n_node_samples, SIZE_t j, SIZE_t i) noexcept nogil: + """Computes variance in O(1). + + Computes: + + \\sigma_{i,j}^2 = \\frac{1}{j-i+1} ((css_j - css_{i-1}) - \\frac{1}{j-i+1} (cs_j - cs_{i-1})^2) + """ + cdef double normalizer = 1. / weighted_n_node_samples + cdef SIZE_t sj_idx = self.sample_indices[j] + cdef SIZE_t si_idx = self.sample_indices[i - 1] + + return normalizer * \ + ( + (self.cumsum_of_squares_map[sj_idx] - self.cumsum_of_squares_map[si_idx]) - + (normalizer * (self.cumsum_map[sj_idx] - self.cumsum_map[si_idx]) * (self.cumsum_map[sj_idx] - self.cumsum_map[si_idx])) + ) + + cdef void children_impurity( + self, + double* impurity_left, + double* impurity_right + ) noexcept nogil: + cdef SIZE_t pos = self.pos + cdef SIZE_t start = self.start + cdef SIZE_t end = self.end + cdef SIZE_t n_samples_left, n_samples_right + + cdef double variance_left, variance_right, variance_comb + cdef double BIC_diff_var_left, BIC_diff_var_right + cdef double BIC_same_var_left, BIC_same_var_right + cdef double BIC_same_var, BIC_diff_var + + # number of samples of left and right + n_samples_left = pos - start + n_samples_right = end - pos + + # compute the variance of the node, left, and right child + variance_left = self.fast_total_variance(self.weighted_n_left, pos) + variance_right = self.fast_variance(self.weighted_n_right, end, pos) + variance_comb = self.fast_total_variance(self.weighted_n_node_samples, end) + + # Compute the BIC using different variances for left and right + BIC_diff_var_left = self.bic_cluster(n_samples_left, variance_left) + BIC_diff_var_right = self.bic_cluster(n_samples_right, variance_right) + + # Compute the BIC using different variances for left and right + BIC_same_var_left = self.bic_cluster(n_samples_left, variance_comb) + BIC_same_var_right = self.bic_cluster(n_samples_right, variance_comb) + BIC_same_var = BIC_same_var_left - BIC_same_var_right + BIC_diff_var = BIC_diff_var_left - BIC_diff_var_right + + # choose the BIC formulation that gives us the smallest values + # (i.e. min of (BIC_diff, BIC_same) in the paper) and then + # assign the left and right child BIC values by reference + if BIC_diff_var < BIC_same_var: + impurity_left[0] = -BIC_diff_var_left + impurity_right[0] = -BIC_diff_var_right + else: + impurity_left[0] = -BIC_same_var_left + impurity_right[0] = -BIC_same_var_right + + cdef void init_feature_vec( + self, + ) noexcept nogil: + """Compute sufficient statistics from the feature vector at this node. + + This function iterates over the set of samples at this node and computes + the cumulative sum and cumulative sum-of-suqares, which enables O(1) computation + of the variance. + + When calling `update` to compute the sum_left and sum_right. It will be fairly straightforward. + """ + # also compute the sum total + self.sum_total = 0.0 + self.weighted_n_node_samples = 0.0 + cdef SIZE_t s_idx + cdef SIZE_t p_idx + + cdef DOUBLE_t w = 1.0 + + cdef SIZE_t prev_s_idx + # = self.sample_indices[self.start] - 1 + # self.cumsum_of_squares_map[self.sample_indices[self.start] - 1] = 0.0 + # self.cumsum_map[self.sample_indices[self.start] - 1] = 0.0 + # self.cumsum_weights_map[self.sample_indices[self.start] - 1] = 0.0 + + cdef unordered_map[SIZE_t, DTYPE_t] cumsum_map + cdef unordered_map[SIZE_t, DTYPE_t] cumsum_of_squares_map + cdef unordered_map[SIZE_t, DTYPE_t] cumsum_weights_map + self.cumsum_map = cumsum_map + self.cumsum_of_squares_map = cumsum_of_squares_map + self.cumsum_weights_map = cumsum_weights_map + + for p_idx in range(self.start, self.end): + s_idx = self.sample_indices[p_idx] + + # w is originally set to be 1.0, meaning that if no sample weights + # are given, the default weight of each sample is 1.0. + if self.sample_weight is not None: + w = self.sample_weight[s_idx] + + self.sum_total += self.feature_values[s_idx] * w + self.weighted_n_node_samples += w + + if p_idx != self.start: + self.cumsum_of_squares_map[s_idx] = self.cumsum_of_squares_map[prev_s_idx] + (self.feature_values[s_idx] * self.feature_values[s_idx] * w * w) + self.cumsum_map[s_idx] = self.cumsum_map[prev_s_idx] + (self.feature_values[s_idx] * w) + self.cumsum_weights_map[s_idx] = self.cumsum_weights_map[prev_s_idx] + w + else: + self.cumsum_of_squares_map[s_idx] = 0.0 + self.cumsum_map[s_idx] = 0.0 + self.cumsum_weights_map[s_idx] = 0.0 + prev_s_idx = s_idx + + # Reset to pos=start + self.reset() + + cdef int update( + self, + SIZE_t new_pos + ) except -1 nogil: + """Updated statistics by moving sample_indices[pos:new_pos] to the left child. + + Update the split point. + + Parameters + ---------- + new_pos : SIZE_t + The new ending position for which to move sample_indices from the right + child to the left child. + """ + # infer left-child statistics + self.weighted_n_left = self.cumsum_weights_map[self.sample_indices[new_pos]] + self.sum_left = self.cumsum_map[self.sample_indices[new_pos]] + + # Update right part statistics as a result + self.weighted_n_right = (self.weighted_n_node_samples - self.weighted_n_left) + self.sum_right = self.sum_total - self.sum_left + + self.pos = new_pos + return 0 diff --git a/sktree/tree/unsupervised/_unsup_oblique_tree.pyx b/sktree/tree/unsupervised/_unsup_oblique_tree.pyx index 51f6e6202..0c555d9ab 100644 --- a/sktree/tree/unsupervised/_unsup_oblique_tree.pyx +++ b/sktree/tree/unsupervised/_unsup_oblique_tree.pyx @@ -216,7 +216,6 @@ cdef class UnsupervisedObliqueTree(UnsupervisedTree): # For reference, see: # https://www.codementor.io/@arpitbhayani/powering-inheritance-in-c-using-structure-composition-176sygr724 cdef ObliqueSplitRecord* oblique_split_node = (split_node) - node_id = self.node_count node.feature = deref(oblique_split_node).feature node.threshold = deref(oblique_split_node).threshold diff --git a/sktree/tree/unsupervised/_unsup_splitter.pyx b/sktree/tree/unsupervised/_unsup_splitter.pyx index df48394a0..06e2b3cf3 100644 --- a/sktree/tree/unsupervised/_unsup_splitter.pyx +++ b/sktree/tree/unsupervised/_unsup_splitter.pyx @@ -126,7 +126,7 @@ cdef class UnsupervisedSplitter(BaseSplitter): self.feature_values, self.sample_weight, self.weighted_n_samples, - self.samples + self.samples, ) # set sample pointers in criterion diff --git a/sktree/utils.py b/sktree/utils.py new file mode 100644 index 000000000..1c3378f77 --- /dev/null +++ b/sktree/utils.py @@ -0,0 +1,27 @@ +from sklearn.utils.validation import check_is_fitted + + +def check_is_forest( + est, + allow_tree=False, + ensure_fitted: bool = True, +): + """Check if an estimator is a tree or forest. + + Parameters + ---------- + est : Estimator + Given estimator. + allow_tree : bool, optional + Whether to allow the estimator to be tree, by default False. + ensure_fitted : bool, optional + Whether to check if the estimator is fitted or not, by default True. + """ + if ensure_fitted: + check_is_fitted(est) + + if not hasattr(est, "apply"): + raise ValueError(f"estimator {est} must be a tree or forest") + + if not allow_tree and not hasattr(est, "estimator"): + raise ValueError("estimator must be a forest, not a tree")