From 3c1c80a6a22abadea313694aeb58c5e5b5b1d468 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Mon, 14 Oct 2024 21:42:59 -0500 Subject: [PATCH 1/8] auto generate docs from doc-strings --- README.md | 1 + artlib/biclustering/BARTMAP.py | 22 ++++- artlib/cvi/iCVIs/__init__.py | 0 artlib/elementary/ART1.py | 16 +++- artlib/elementary/ART2.py | 15 +++- artlib/elementary/BayesianART.py | 14 +++- artlib/elementary/DualVigilanceART.py | 24 +++++- artlib/elementary/EllipsoidART.py | 21 ++++- artlib/elementary/FuzzyART.py | 14 +++- artlib/elementary/GaussianART.py | 17 +++- artlib/elementary/HypersphereART.py | 18 +++- artlib/elementary/QuadraticNeuronART.py | 22 ++++- artlib/fusion/FusionART.py | 38 +++++++-- artlib/hierarchical/DeepARTMAP.py | 15 ++++ artlib/hierarchical/SMART.py | 26 +++++- artlib/reinforcement/FALCON.py | 107 +++++++++--------------- artlib/reinforcement/TDFALCON.py | 99 ++++++++++++++++++++++ artlib/reinforcement/__init__.py | 0 artlib/supervised/ARTMAP.py | 18 ++++ artlib/supervised/SimpleARTMAP.py | 29 ++++++- artlib/topological/TopoART.py | 28 +++++++ docs/source/conf.py | 9 +- docs/source/index.rst | 2 + pyproject.toml | 1 + 24 files changed, 458 insertions(+), 98 deletions(-) create mode 100644 artlib/cvi/iCVIs/__init__.py create mode 100644 artlib/reinforcement/TDFALCON.py create mode 100644 artlib/reinforcement/__init__.py diff --git a/README.md b/README.md index 7a011be..228a2e3 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ AdaptiveResonanceLib includes implementations for the following ART models: - Fusion ART - #### Reinforcement Learning - FALCON + - TD-FALCON - #### Biclustering - Biclustering ARTMAP diff --git a/artlib/biclustering/BARTMAP.py b/artlib/biclustering/BARTMAP.py index e6f03c8..fb210a8 100644 --- a/artlib/biclustering/BARTMAP.py +++ b/artlib/biclustering/BARTMAP.py @@ -19,6 +19,24 @@ from scipy.stats import pearsonr class BARTMAP(BaseEstimator, BiclusterMixin): + """BARTMAP for Biclustering + + This module implements BARTMAP as first published in + Xu, R., & Wunsch II, D. C. (2011). + BARTMAP: A viable structure for biclustering. + Neural Networks, 24, 709–716. doi:10.1016/j.neunet.2011.03.020. + BARTMAP accepts two instantiated ART modules module_a and module_b which will cluster the rows (samples) and + columns (features) respectively. The features are clustered independently but the samples are clustered by + considering samples already within a row cluster as well as the candidate sample and enforcing a minimum correlation + within the subset of features belonging to at least one of the feature clusters. + + + Parameters: + module_a: The instantiated ART module used for clustering the rows (samples) + module_b: The instantiated ART module used for clustering the columns (features) + eta: float the minimum pearson correlation + + """ rows_: np.ndarray #bool columns_: np.ndarray #bool @@ -26,8 +44,8 @@ def __init__(self, module_a: BaseART, module_b: BaseART, eta: float): """ Parameters: - - module_a: a-side ART module - - module_b: b-side ART module + - module_a: The instantiated ART module used for clustering the rows (samples) + - module_b: The instantiated ART module used for clustering the columns (features) - eta: minimum correlation """ diff --git a/artlib/cvi/iCVIs/__init__.py b/artlib/cvi/iCVIs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index 2a04404..cbf257d 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -11,7 +11,21 @@ class ART1(BaseART): - # implementation of ART 1 + """ART1 for Clustering + + This module implements ART1 as first published in + Carpenter, G. A., & Grossberg, S. (1987a). + A massively parallel architecture for a self-organizing neural pattern recognition machine. + Computer Vision, Graphics, and Image Processing, 37, 54 – 115. doi:10. 1016/S0734-189X(87)80014-2. + ART1 is intended for binary data clustering only. + + + Parameters: + rho: float [0,1] for the vigilance parameter. + beta: float [0,1] learning parameters. beta = 1 is fast learning and the recommended value. + L: float [0,1] the uncommitted node bias + + """ def __init__(self, rho: float, beta: float, L: float): """ Parameters: diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index 497e0a0..2f75ebf 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -24,7 +24,20 @@ class ART2A(BaseART): - # implementation of ART 2-A + """ART2-A for Clustering + + This module implements ART2-A as first published in Carpenter, G. A., Grossberg, S., & Rosen, D. B. (1991b). + ART 2-A: An adaptive resonance algorithm for rapid category learning and recognition. + Neural Networks, 4, 493 – 504. doi:10.1016/0893-6080(91) 90045-7. ART2-A is similar to ART1 but designed for + analog data. This method is implemented for historical purposes and is not recommended for use. + + + Parameters: + rho: float [0,1] for the vigilance parameter. + alpha: float choice parameter. 1e-7 recommended value. + beta: float [0,1] learning parameters. beta = 1 is fast learning and the recommended value. + + """ def __init__(self, rho: float, alpha: float, beta: float): """ Parameters: diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index d092384..c2638ba 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -12,7 +12,19 @@ class BayesianART(BaseART): - # implementation of Bayesian ART + """Bayesian ART for Clustering + + This module implements Bayesian ART as first published in Vigdor, B., & Lerner, B. (2007). + The Bayesian ARTMAP. IEEE Transactions on Neural Networks, 18, 1628–1644. doi:10.1109/TNN.2007.900234. + Bayesian ART clusters data in Bayesian Distributions (Hyper-ellipsoids) and is similar to Gaussian ART but differs + in that it allows arbitrary rotation of the hyper-ellipsoid. + + + Parameters: + rho: float [0,1] for the vigilance parameter. + cov_init: np.ndarray the initial estimate of the covariance matrix for each cluster. + + """ pi2 = np.pi * 2 def __init__(self, rho: float, cov_init: np.ndarray): """ diff --git a/artlib/elementary/DualVigilanceART.py b/artlib/elementary/DualVigilanceART.py index cfb912a..9052882 100644 --- a/artlib/elementary/DualVigilanceART.py +++ b/artlib/elementary/DualVigilanceART.py @@ -12,9 +12,31 @@ class DualVigilanceART(BaseART): - # implementation of Dual Vigilance ART + """Dual Vigilance ART for Clustering + + This module implements Dual Vigilance ART as first published in + Brito da Silva, L. E., Elnabarawy, I., & Wunsch II, D. C. (2019). + Dual vigilance fuzzy adaptive resonance theory. Neural Networks, 109, 1–5. doi:10.1016/j.neunet.2018.09.015. + Dual Vigilance ART allows a base ART module to cluster with both an upper and lower vigilance value. + The upper-vigilance value allows the base ART module to cluster normally, however, data is simultaneously clustered + using the lower vigilance level to combine multiple base ART categories into a single abstracted category. This + permits clusters to be combined to form arbitrary shapes. For example if the base ART module is fuzzy ART, a + Dual Vigilance Fuzzy ART clustering result would look like a series of hyper-boxes forming an arbitrary geometry. + + + Parameters: + base_module: BaseART the instantiated ART module that wil serve as the base for dual vigilance + rho_lower_bound: float the lower vigilance value that will "merge" the base_module clusters + + """ def __init__(self, base_module: BaseART, rho_lower_bound: float): + """ + Parameters: + - base_module: BaseART the instantiated ART module that wil serve as the base for dual vigilance + - rho_lower_bound: float the lower vigilance value that will "merge" the base_module clusters + + """ assert isinstance(base_module, BaseART) if hasattr(base_module, "base_module"): warn( diff --git a/artlib/elementary/EllipsoidART.py b/artlib/elementary/EllipsoidART.py index fd0ccd2..16a271b 100644 --- a/artlib/elementary/EllipsoidART.py +++ b/artlib/elementary/EllipsoidART.py @@ -16,14 +16,31 @@ from artlib.common.utils import l2norm2 class EllipsoidART(BaseART): - # implementation of EllipsoidART + """Ellipsoid ART for Clustering + + This module implements Ellipsoid ART as first published in Anagnostopoulos, G. C., & Georgiopoulos, M. (2001a). + Ellipsoid ART and ARTMAP for incremental clustering and classification. + In Proc. IEEE International Joint Conference on Neural Networks (IJCNN) + (pp. 1221–1226). volume 2. doi:10.1109/IJCNN.2001.939535. + Ellipsoid ART clusters data in Hyper-ellipsoids. It is highly sensitive to sample presentation order as the second + sample will determine the orientation of the principal axes. + + + Parameters: + rho: float [0,1] for the vigilance parameter. + alpha: float choice parameter. 1e-7 recommended value. + beta: float [0,1] learning parameters. beta = 1 is fast learning and the recommended value. + mu: float ratio between the major and minor axes + r_hat: float radius bias parameter + + """ def __init__(self, rho: float, alpha: float, beta: float, mu: float, r_hat: float): """ Parameters: - rho: vigilance parameter - alpha: choice parameter - beta: learning rate - - mu: ratio between major and minor axis + - mu: ratio between major and minor axess - r_hat: radius bias parameter """ diff --git a/artlib/elementary/FuzzyART.py b/artlib/elementary/FuzzyART.py index 3a14044..5e1dafe 100644 --- a/artlib/elementary/FuzzyART.py +++ b/artlib/elementary/FuzzyART.py @@ -40,7 +40,19 @@ def get_bounding_box(w: np.ndarray, n: Optional[int] = None) -> tuple[list[int], class FuzzyART(BaseART): - # implementation of FuzzyART + """Fuzzy ART for Clustering + + This module implements Fuzzy ART as first published in Carpenter, G. A., Grossberg, S., & Rosen, D. B. (1991c). + Fuzzy ART: Fast stable learning and categorization of analog patterns by an adaptive resonance system. + Neural Networks, 4, 759 – 771. doi:10.1016/0893-6080(91)90056-B. Fuzzy ART is a hyper-box based clustering method. + + + Parameters: + rho: float [0,1] for the vigilance parameter. + alpha: float choice parameter. 1e-7 recommended value. + beta: float [0,1] learning parameters. beta = 1 is fast learning and the recommended value. + + """ def __init__(self, rho: float, alpha: float, beta: float): """ Parameters: diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index ccf2b9f..db77800 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -13,7 +13,22 @@ class GaussianART(BaseART): - # implementation of GaussianART + """Gaussian ART for Clustering + + This module implements Gaussian ART as first published in Williamson, J. R. (1996). + Gaussian ARTMAP: A Neural Network for Fast Incremental Learning of Noisy Multidimensional Maps. + Neural Networks, 9, 881 – 897. doi:10.1016/0893-6080(95)00115-8. + Guassian ART clusters data in Gaussian Distributions (Hyper-ellipsoids) and is similar to Bayesian ART but differs + in that the hyper-ellipsoid always have their principal axes square to the coordinate frame. + It is also faster than Bayesian ART. + + + Parameters: + rho: float [0,1] for the vigilance parameter. + sigma_init: np.ndarray the initial estimate of the variance of each dimension for each cluster. + alpha: float an arbitrarily small parameter used to prevent division-by-zero errors. 1e-10 is recommended + + """ def __init__(self, rho: float, sigma_init: np.ndarray, alpha: float = 1e-10): """ Parameters: diff --git a/artlib/elementary/HypersphereART.py b/artlib/elementary/HypersphereART.py index 5716c38..c1e33ae 100644 --- a/artlib/elementary/HypersphereART.py +++ b/artlib/elementary/HypersphereART.py @@ -11,7 +11,23 @@ from artlib.common.utils import l2norm2 class HypersphereART(BaseART): - # implementation of HypersphereART + """Hypersphere ART for Clustering + + This module implements Ellipsoid ART as first published in Anagnostopoulos, G. C., & Georgiopulos, M. (2000). + Hypersphere ART and ARTMAP for unsupervised and supervised, incremental learning. + In Proc. IEEE International Joint Conference on Neural Networks (IJCNN) + (pp. 59–64). volume 6. doi:10.1109/IJCNN.2000.859373. + Hyperpshere ART clusters data in Hyper-spheres similar to k-means with a dynamic k. + + + Parameters: + rho: float [0,1] for the vigilance parameter. + alpha: float choice parameter. 1e-7 recommended value. + beta: float [0,1] learning parameters. beta = 1 is fast learning and the recommended value. + mu: float ratio between the major and minor axes + r_hat: float maximum possible radius + + """ def __init__(self, rho: float, alpha: float, beta: float, r_hat: float): """ Parameters: diff --git a/artlib/elementary/QuadraticNeuronART.py b/artlib/elementary/QuadraticNeuronART.py index 04b42ca..9d61e9e 100644 --- a/artlib/elementary/QuadraticNeuronART.py +++ b/artlib/elementary/QuadraticNeuronART.py @@ -17,15 +17,31 @@ class QuadraticNeuronART(BaseART): - # implementation of QuadraticNeuronART + """Quadratic Neuron ART for Clustering + + This module implements Quadratic Neuron ART as first published in Su, M.-C., & Liu, Y.-C. (2005). + A new approach to clustering data with arbitrary shapes. + Pattern Recognition, 38, 1887 – 1901. doi:10.1016/j.patcog.2005.04.010. + Quadratic Neuron ART clusters data in Hyper-ellipsoid by utilizing a quadratic neural network for activation + and resonance. + + + Parameters: + rho: float [0,1] for the vigilance parameter. + s_init: float initial quadratic term + lr_b: float the bias learning rate + lr_w: float the weight matrix learning rate + lr_s: the learning rate for the quadratic term + + """ def __init__(self, rho: float, s_init: float, lr_b: float, lr_w: float, lr_s: float): """ Parameters: - rho: vigilance parameter - - s_init: initial linear activation parameter + - s_init: initial quadratic term - lr_b: learning rate for cluster mean - lr_w: learning rate for cluster weights - - lr_s: learning rate for cluster activation parameter + - lr_s: learning rate for the quadratic term """ params = { diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index 0749fb8..00b2d06 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -7,7 +7,7 @@ doi:10.1007/ 978-3-540-72383-7_128. """ import numpy as np -from typing import Optional, Union, Callable, List, Literal, Tuple +from typing import Optional, Union, Callable, List, Literal from copy import deepcopy from artlib.common.BaseART import BaseART from sklearn.utils.validation import check_is_fitted @@ -23,14 +23,42 @@ def get_channel_position_tuples(channel_dims: list[int]) -> list[tuple[int, int] return positions class FusionART(BaseART): - # implementation of FusionART + """Fusion ART for Data Fusion and Regression + + This module implements Fusion ART as first described in + Tan, A.-H., Carpenter, G. A., & Grossberg, S. (2007). + Intelligence Through Interaction: Towards a Unified Theory for Learning. + In D. Liu, S. Fei, Z.-G. Hou, H. Zhang, & C. Sun (Eds.), + Advances in Neural Networks – ISNN 2007 (pp. 1094–1103). + Berlin, Heidelberg: Springer Berlin Heidelberg. + doi:10.1007/ 978-3-540-72383-7_128. + Fusion ART accepts an arbitrary number of ART modules, each assigned a different data channel. The activation + and match functions for all ART modules are then fused such that all modules must be simultaneously active and + resonant in order for a match to occur. This provides fine-grain control when clustering multi-channel or + molti-modal data and allows for different geometries of clusters to be used for each channel. + Fusion ART also allows for fitting regression models and specific functions have been implemented to allow this. + + + Parameters: + modules: List[BaseART] a list of instantiated ART modules to use for each channel + gamma_values: Union[List[float], np.ndarray] the activation ratio for each channel + channel_dims: Union[List[int], np.ndarray] the dimension of each channel + + """ def __init__( self, - modules: list[BaseART], - gamma_values: Union[list[float], np.ndarray], - channel_dims: Union[list[int], np.ndarray] + modules: List[BaseART], + gamma_values: Union[List[float], np.ndarray], + channel_dims: Union[List[int], np.ndarray] ): + + """ + Parameters: + - modules: List[BaseART] a list of instantiated ART modules to use for each channel + - gamma_values: Union[List[float], np.ndarray] the activation ratio for each channel + - channel_dims: Union[List[int], np.ndarray] the dimension of each channel + """ assert len(modules) == len(gamma_values) == len(channel_dims) params = {"gamma_values": gamma_values} super().__init__(params) diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index de99323..58a51fc 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -14,6 +14,21 @@ class DeepARTMAP(BaseEstimator, ClassifierMixin, ClusterMixin): + """DeepARTMAP for Hierachical Supervised and Unsupervised Learning + + This module implements DeepARTMAP, a generalization of the ARTMAP class that allows an arbitrary number of + data channels to be divisively clustered. DeepARTMAP support both supervised and unsupervised modes. + If only two ART modules are provided, DeepARTMAP reverts to standard ARTMAP where the first module is the A module + and the second module is the B module. + DeepARTMAP does not currently have a direct citation and is an original creation of this library. + + + Parameters: + modules: An list of instatiated BaseART classes to use as layers. e.g. [FuzzyART(), HyperpshereART()]. + + + """ + def __init__(self, modules: list[BaseART]): """ diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index b3f4e39..1385597 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -12,18 +12,38 @@ from artlib.hierarchical.DeepARTMAP import DeepARTMAP class SMART(DeepARTMAP): + """SMART for Hierachical Clustering + + This module implements SMART as first published in + Bartfai, G. (1994). + Hierarchical clustering with ART neural networks. + In Proc. IEEE International Conference on Neural Networks (ICNN) + (pp. 940–944). volume 2. doi:10.1109/ICNN.1994.374307. + SMART accepts an uninstatiated ART class and hierarchically clusters data in a divisive fashion by using a set of + vigilance values that monotonically increase in their restrictiveness. SMART is a special case of DeepARTMAP, + which forms the backbone of this class, where all channels receive the same data. + + + Parameters: + base_ART_class: An uninstatiated BaseART class. e.g. FuzzyART + rho_values: Union[list[float], np.ndarray] a set of monotonically increasing vigilance values + base_params: all other params used to instantiate the base ART (will be identical across all layers) + + """ def __init__(self, base_ART_class: Type, rho_values: Union[list[float], np.ndarray], base_params: dict, **kwargs): """ Parameters: - - base_ART: some ART class + - base_ART_class: some ART class - rho_values: rho parameters for each sub-module - base_params: base param dict for each sub-module """ - - assert all(np.diff(rho_values) > 0), "rho_values must be monotonically increasing" + if base_ART_class.__name__ != "BayesianART": + assert all(np.diff(rho_values) > 0), "rho_values must be monotonically increasing" + else: + assert all(np.diff(rho_values) < 0), "rho_values must be monotonically decreasing for BayesianART" self.rho_values = rho_values layer_params = [dict(base_params, **{"rho": rho}) for rho in self.rho_values] diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index 84e9dd4..0fb94f1 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -1,17 +1,49 @@ +""" +Tan, A.-H. (2004). FALCON: a fusion architecture for learning, cognition, and navigation. In Proc. IEEE +International Joint Conference on Neural Networks (IJCNN) (pp. 3297–3302). volume 4. doi:10.1109/ +IJCNN.2004.1381208 +""" import numpy as np -from typing import Optional, Literal, Tuple -from artlib import FusionART, BaseART, compliment_code, de_compliment_code +from typing import Optional, Literal, Tuple, Union, List +from artlib.common.BaseART import BaseART +from artlib.fusion.FusionART import FusionART class FALCON: + """FALCON for Reinforcement Learning + + This module implements the reactive FALCON as first described in + Tan, A.-H. (2004). FALCON: a fusion architecture for learning, cognition, and navigation. In Proc. IEEE + International Joint Conference on Neural Networks (IJCNN) (pp. 3297–3302). volume 4. doi:10.1109/ + IJCNN.2004.1381208. + FALCON is based on a Fusion-ART backbone but only accepts 3 channels: State, Action, and Reward. Specific + functions are implemented for getting optimal reward and action predictions. + + + Parameters: + state_art: BaseART the instantiated ART module that wil cluster the state-space + action_art: BaseART the instantiated ART module that wil cluster the action-space + reward_art: BaseART the instantiated ART module that wil cluster the reward-space + gamma_values: Union[List[float], np.ndarray] the activation ratio for each channel + channel_dims: Union[List[int], np.ndarray] the dimension of each channel + + """ def __init__( self, state_art: BaseART, action_art: BaseART, reward_art: BaseART, - gamma_values: list[float] = np.array([0.33, 0.33, 0.34]), - channel_dims = list[int] + gamma_values: Union[List[float], np.ndarray] = np.array([0.33, 0.33, 0.34]), + channel_dims: Union[List[int], np.ndarray] = list[int] ): + """ + Parameters: + - state_art: BaseART the instantiated ART module that wil cluster the state-space + - action_art: BaseART the instantiated ART module that wil cluster the action-space + - reward_art: BaseART the instantiated ART module that wil cluster the reward-space + - gamma_values: Union[List[float], np.ndarray] the activation ratio for each channel + - channel_dims: Union[List[int], np.ndarray] the dimension of each channel + """ self.fusion_art = FusionART( modules=[state_art, action_art, reward_art], gamma_values=gamma_values, @@ -52,7 +84,7 @@ def partial_fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarr self.fusion_art = self.fusion_art.partial_fit(data) return self - def get_actions_and_rewards(self, state: np.ndarray, action_space: Optional[np.ndarray] = None) -> np.ndarray: + def get_actions_and_rewards(self, state: np.ndarray, action_space: Optional[np.ndarray] = None) -> Tuple[np.ndarray, np.ndarray]: reward_centers = self.fusion_art.get_channel_centers(2) if action_space is None: action_space = self.fusion_art.get_channel_centers(1) @@ -67,6 +99,8 @@ def get_actions_and_rewards(self, state: np.ndarray, action_space: Optional[np.n rewards = [reward_centers[c] for c in viable_clusters] return action_space, np.array(rewards) + + def get_action(self, state: np.ndarray, action_space: Optional[np.ndarray] = None, optimality: Literal["min", "max"] = "max") -> np.ndarray: action_space, rewards = self.get_actions_and_rewards(state, action_space) if optimality == "max": @@ -100,66 +134,3 @@ def get_rewards(self, states: np.ndarray, actions: np.ndarray) -> np.ndarray: return np.array([reward_centers[c] for c in C]) - - -class TD_FALCON(FALCON): - # implements SARSA learning - - def __init__( - self, - state_art: BaseART, - action_art: BaseART, - reward_art: BaseART, - gamma_values: list[float] = np.array([0.33, 0.33, 0.34]), - channel_dims = list[int], - td_alpha: float = 1.0, - td_lambda: float = 1.0, - ): - self.td_alpha = td_alpha - self.td_lambda = td_lambda - super(TD_FALCON, self).__init__(state_art, action_art, reward_art, gamma_values, channel_dims) - - def fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray): - raise NotImplementedError("TD-FALCON can only be trained with partial fit") - - def calculate_SARSA(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray, single_sample_reward: Optional[float] = None): - # calculate SARSA values - rewards_dcc = de_compliment_code(rewards) - if len(states) > 1: - - if hasattr(self.fusion_art.modules[0], "W"): - # if FALCON has been trained get predicted rewards - Q = self.get_rewards(states, actions) - else: - # otherwise set predicted rewards to 0 - Q = np.zeros_like(rewards_dcc) - # SARSA equation - sarsa_rewards = Q[:-1] + self.td_alpha * (rewards_dcc[:-1] + self.td_lambda * Q[1:] - Q[:-1]) - # ensure SARSA values are between 0 and 1 - sarsa_rewards = np.maximum(np.minimum(sarsa_rewards, 1.0), 0.0) - # compliment code rewards - sarsa_rewards_fit = compliment_code(sarsa_rewards) - # we cant train on the final state because no rewards are generated after it - states_fit = states[:-1, :] - actions_fit = actions[:-1, :] - else: - # if we only have a single sample, we cant learn from future samples - if single_sample_reward is None: - sarsa_rewards_fit = rewards - else: - sarsa_rewards_fit = compliment_code(np.array([[single_sample_reward]])) - states_fit = states - actions_fit = actions - - return states_fit, actions_fit, sarsa_rewards_fit - - def partial_fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray, single_sample_reward: Optional[float] = None): - - states_fit, actions_fit, sarsa_rewards_fit = self.calculate_SARSA(states, actions, rewards, single_sample_reward) - data = self.fusion_art.join_channel_data([states_fit, actions_fit, sarsa_rewards_fit]) - self.fusion_art = self.fusion_art.partial_fit(data) - return self - - - - diff --git a/artlib/reinforcement/TDFALCON.py b/artlib/reinforcement/TDFALCON.py new file mode 100644 index 0000000..666c773 --- /dev/null +++ b/artlib/reinforcement/TDFALCON.py @@ -0,0 +1,99 @@ +""" +Tan, A.-H., Lu, N., & Xiao, D. (2008). Integrating Temporal Difference Methods and Self-Organizing Neural +Networks for Reinforcement Learning With Delayed Evaluative Feedback. IEEE Transactions on Neural +Networks, 19 , 230–244. doi:10.1109/TNN.2007.905839 +""" + +import numpy as np +from typing import Optional, List, Union +from artlib.common.BaseART import BaseART +from artlib.common.utils import compliment_code, de_compliment_code +from artlib.reinforcement.FALCON import FALCON + +class TD_FALCON(FALCON): + """TD-FALCON for Reinforcement Learning + + This module implements TD-FALCON as first described in + Tan, A.-H., Lu, N., & Xiao, D. (2008). Integrating Temporal Difference Methods and Self-Organizing Neural + Networks for Reinforcement Learning With Delayed Evaluative Feedback. IEEE Transactions on Neural + Networks, 19 , 230–244. doi:10.1109/TNN.2007.905839. + TD-FALCON is based on a FALCON backbone but includes specific function for temporal-difference learning. + Currently, only SARSA is implemented and only Fuzzy ART base modules are supported. + + + Parameters: + state_art: BaseART the instantiated ART module that wil cluster the state-space + action_art: BaseART the instantiated ART module that wil cluster the action-space + reward_art: BaseART the instantiated ART module that wil cluster the reward-space + gamma_values: Union[List[float], np.ndarray] the activation ratio for each channel + channel_dims: Union[List[int], np.ndarray] the dimension of each channel + td_alpha: float the learning rate for the temporal difference estimator + td_lambda: float the future-cost factor + + """ + + def __init__( + self, + state_art: BaseART, + action_art: BaseART, + reward_art: BaseART, + gamma_values: Union[List[float], np.ndarray] = np.array([0.33, 0.33, 0.34]), + channel_dims: Union[List[int], np.ndarray] = list[int], + td_alpha: float = 1.0, + td_lambda: float = 1.0, + ): + """ + Parameters: + - state_art: BaseART the instantiated ART module that wil cluster the state-space + - action_art: BaseART the instantiated ART module that wil cluster the action-space + - reward_art: BaseART the instantiated ART module that wil cluster the reward-space + - gamma_values: Union[List[float], np.ndarray] the activation ratio for each channel + - channel_dims: Union[List[int], np.ndarray] the dimension of each channel + - td_alpha: float the learning rate for the temporal difference estimator + - td_lambda: float the future-cost factor + """ + self.td_alpha = td_alpha + self.td_lambda = td_lambda + super(TD_FALCON, self).__init__(state_art, action_art, reward_art, gamma_values, channel_dims) + + def fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray): + raise NotImplementedError("TD-FALCON can only be trained with partial fit") + + def calculate_SARSA(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray, single_sample_reward: Optional[float] = None): + # calculate SARSA values + rewards_dcc = de_compliment_code(rewards) + if len(states) > 1: + + if hasattr(self.fusion_art.modules[0], "W"): + # if FALCON has been trained get predicted rewards + Q = self.get_rewards(states, actions) + else: + # otherwise set predicted rewards to 0 + Q = np.zeros_like(rewards_dcc) + # SARSA equation + sarsa_rewards = Q[:-1] + self.td_alpha * (rewards_dcc[:-1] + self.td_lambda * Q[1:] - Q[:-1]) + # ensure SARSA values are between 0 and 1 + sarsa_rewards = np.maximum(np.minimum(sarsa_rewards, 1.0), 0.0) + # compliment code rewards + sarsa_rewards_fit = compliment_code(sarsa_rewards) + # we cant train on the final state because no rewards are generated after it + states_fit = states[:-1, :] + actions_fit = actions[:-1, :] + else: + # if we only have a single sample, we cant learn from future samples + if single_sample_reward is None: + sarsa_rewards_fit = rewards + else: + sarsa_rewards_fit = compliment_code(np.array([[single_sample_reward]])) + states_fit = states + actions_fit = actions + + return states_fit, actions_fit, sarsa_rewards_fit + + def partial_fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray, single_sample_reward: Optional[float] = None): + + states_fit, actions_fit, sarsa_rewards_fit = self.calculate_SARSA(states, actions, rewards, single_sample_reward) + data = self.fusion_art.join_channel_data([states_fit, actions_fit, sarsa_rewards_fit]) + self.fusion_art = self.fusion_art.partial_fit(data) + return self + diff --git a/artlib/reinforcement/__init__.py b/artlib/reinforcement/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index 066b209..e397a1a 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -11,6 +11,24 @@ class ARTMAP(SimpleARTMAP): + """ARTMAP for Classification and Regression + + This module implements ARTMAP as first published in + Carpenter, G. A., Grossberg, S., & Reynolds, J. H. (1991a). + ARTMAP: Supervised real-time learning and classification of nonstationary data by a self-organizing neural network. + Neural Networks, 4, 565 – 588. doi:10.1016/0893-6080(91)90012-T. + + ARTMAP joins accepts two ART modules A and B which cluster the dependent channel (samples) and the independent + channel (labels) respectively while linking them with a many-to-one mapping. + If your labels are integers, use SimpleARTMAP for a faster and more direct implementation. + ARTMAP also provides the ability to fit a regression model to data and specific functions have been implemented to + allow this. However, FusionART provides substantially better fit for regression problems which are not monotonic. + + Parameters: + module_a: The instantiated ART module used for clustering the independent channel + module_b: The instantiated ART module used for clustering the dependent channel + + """ def __init__(self, module_a: BaseART, module_b: BaseART): """ diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index b0bf4d9..1216899 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -13,6 +13,31 @@ class SimpleARTMAP(BaseARTMAP): + """SimpleARTMAP for Classification + + This module implements SimpleARTMAP as first published in + Serrano-Gotarredona, T., Linares-Barranco, B., & Andreou, A. G. (1998). + Adaptive Resonance Theory Microchips: Circuit Design Techniques. + Norwell, MA, USA: Kluwer Academic Publishers. + + SimpleARTMAP allows the clustering of data samples while enforcing a many-to-one mapping from sample clusters to + labels. It accepts an instantiated ART module and dynamically adapts the vigilance function to prevent resonance + when the many-to-one mapping is violated. This enables SimpleARTMAP to identify discrete clusters belonging to + each category label. + + Parameters: + module_a: The instantiated ART module used for clustering the independent channel + + """ + + def __init__(self, module_a: BaseART): + """ + Parameters: + - module_a: The instantiated ART module used for clustering the independent channel + """ + self.module_a = module_a + super().__init__() + def match_reset_func( self, @@ -43,10 +68,6 @@ def match_reset_func( return False return True - def __init__(self, module_a: BaseART): - self.module_a = module_a - super().__init__() - def get_params(self, deep: bool = True) -> dict: """ diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index 461156e..cc3dded 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -18,8 +18,36 @@ class TopoART(BaseART): + """Topo ART Clustering + + This module implements Topo ART as first published in + Tscherepanow, M. (2010). + TopoART: A Topology Learning Hierarchical ART Network. + In K. Diamantaras, W. Duch, & L. S. Iliadis (Eds.), + Artificial Neural Networks – ICANN 2010 (pp. 157–167). + Berlin, Heidelberg: Springer Berlin Heidelberg. + doi:10.1007/978-3-642-15825-4_21. + Topo ART clusters accepts an instatiated base ART module and generates a topological clustering by recording + the first and second resonant cluster relationships in an adjacency matrix. Further, it updates the second + resonant cluster with a lower learning rate than the first, providing for a distributed learning model. + + + Parameters: + base_module: an instantiated ART module + beta_lower: the learning rate for the second resonant cluster + tau: number of samples after which we prune + phi: minimum number of samples a cluster must have association with to be kept + + """ def __init__(self, base_module: BaseART, beta_lower: float, tau: int, phi: int): + """ + Parameters: + - base_module: an instantiated ART module + - beta_lower: the learning rate for the second resonant cluster + - tau: number of samples after which we prune + - phi: minimum number of samples a cluster must have association with to be kept + """ assert isinstance(base_module, BaseART) if hasattr(base_module, "base_module"): warn( diff --git a/docs/source/conf.py b/docs/source/conf.py index 4f58160..9ac14cd 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,17 +14,18 @@ # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = ['sphinx.ext.autodoc'] +extensions = ['sphinx.ext.autodoc', 'autoapi.extension', 'sphinx.ext.napoleon'] templates_path = ['_templates'] -exclude_patterns = [] - +exclude_patterns = ['artlib/experimental/*'] +autoapi_type = 'python' +autoapi_dirs = ['../../artlib'] # Adjust this to point to your source code directory # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'alabaster' +html_theme = 'classic' html_static_path = ['_static'] diff --git a/docs/source/index.rst b/docs/source/index.rst index eec7a6b..8550f17 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,3 +15,5 @@ documentation for details. :maxdepth: 2 :caption: Contents: + artlib + diff --git a/pyproject.toml b/pyproject.toml index c723f00..c17fe78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ matplotlib = ">=3.3.3" pytest = "^6.2.2" sphinx = "^5.0" sphinx-rtd-theme = "^1.0.0" # If you're using the Read the Docs theme +sphinx-autoapi = ">=1.8.1" [build-system] requires = ["poetry-core>=1.0.0"] From edcab0cdc60718024954680151004fa0f1598e8d Mon Sep 17 00:00:00 2001 From: niklas melton Date: Mon, 14 Oct 2024 21:43:36 -0500 Subject: [PATCH 2/8] auto generate docs from doc-strings --- artlib/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/artlib/__init__.py b/artlib/__init__.py index 2dfdd95..5e2d787 100644 --- a/artlib/__init__.py +++ b/artlib/__init__.py @@ -23,7 +23,8 @@ from artlib.fusion.FusionART import FusionART -from artlib.reinforcement.FALCON import FALCON, TD_FALCON +from artlib.reinforcement.FALCON import FALCON +from artlib.reinforcement.TDFALCON import TD_FALCON from artlib.biclustering.BARTMAP import BARTMAP @@ -51,5 +52,7 @@ "FusionART", "BARTMAP", "iCVIFuzzyART", - "CVIART" + "CVIART", + "FALCON", + "TD_FALCON" ] \ No newline at end of file From 1cebc3c2648afb705bb4f98734d7d51e05eff943 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Tue, 15 Oct 2024 15:28:08 -0500 Subject: [PATCH 3/8] docstrings to numpy format --- artlib/biclustering/BARTMAP.py | 241 +++++++++----- artlib/cvi/CVIART.py | 237 ++++++++++---- artlib/cvi/iCVIFuzzyArt.py | 83 +++-- artlib/cvi/iCVIs/CalinkskiHarabasz.py | 146 ++++++--- artlib/elementary/ART1.py | 134 +++++--- artlib/elementary/ART2.py | 134 +++++--- artlib/elementary/BayesianART.py | 209 ++++++++---- artlib/elementary/DualVigilanceART.py | 205 ++++++++---- artlib/elementary/EllipsoidART.py | 196 +++++++---- artlib/elementary/FuzzyART.py | 253 ++++++++++----- artlib/elementary/GaussianART.py | 140 +++++--- artlib/elementary/HypersphereART.py | 179 +++++++---- artlib/elementary/QuadraticNeuronART.py | 148 +++++---- artlib/experimental/ConvexHullART.py | 230 +++++++++---- artlib/experimental/SeqART.py | 247 +++++++++----- artlib/experimental/merging.py | 55 +++- artlib/fusion/FusionART.py | 411 ++++++++++++++++++------ artlib/hierarchical/DeepARTMAP.py | 242 +++++++++----- artlib/hierarchical/SMART.py | 154 ++++++--- artlib/reinforcement/FALCON.py | 172 ++++++++-- artlib/reinforcement/TDFALCON.py | 81 +++-- artlib/supervised/ARTMAP.py | 227 ++++++++----- artlib/supervised/SimpleARTMAP.py | 345 +++++++++++++------- artlib/topological/TopoART.py | 364 ++++++++++++++------- 24 files changed, 3320 insertions(+), 1513 deletions(-) diff --git a/artlib/biclustering/BARTMAP.py b/artlib/biclustering/BARTMAP.py index fb210a8..bf27e46 100644 --- a/artlib/biclustering/BARTMAP.py +++ b/artlib/biclustering/BARTMAP.py @@ -19,22 +19,20 @@ from scipy.stats import pearsonr class BARTMAP(BaseEstimator, BiclusterMixin): - """BARTMAP for Biclustering + """ + BARTMAP for Biclustering - This module implements BARTMAP as first published in + This class implements BARTMAP as first published in: Xu, R., & Wunsch II, D. C. (2011). BARTMAP: A viable structure for biclustering. Neural Networks, 24, 709–716. doi:10.1016/j.neunet.2011.03.020. - BARTMAP accepts two instantiated ART modules module_a and module_b which will cluster the rows (samples) and - columns (features) respectively. The features are clustered independently but the samples are clustered by - considering samples already within a row cluster as well as the candidate sample and enforcing a minimum correlation - within the subset of features belonging to at least one of the feature clusters. - - Parameters: - module_a: The instantiated ART module used for clustering the rows (samples) - module_b: The instantiated ART module used for clustering the columns (features) - eta: float the minimum pearson correlation + BARTMAP accepts two instantiated ART modules `module_a` and `module_b` which + cluster the rows (samples) and columns (features) respectively. The features + are clustered independently, but the samples are clustered by considering + samples already within a row cluster as well as the candidate sample and + enforcing a minimum correlation within the subset of features belonging to + at least one of the feature clusters. """ rows_: np.ndarray #bool @@ -42,11 +40,16 @@ class BARTMAP(BaseEstimator, BiclusterMixin): def __init__(self, module_a: BaseART, module_b: BaseART, eta: float): """ + Initialize the BARTMAP model. - Parameters: - - module_a: The instantiated ART module used for clustering the rows (samples) - - module_b: The instantiated ART module used for clustering the columns (features) - - eta: minimum correlation + Parameters + ---------- + module_a : BaseART + The instantiated ART module used for clustering the rows (samples). + module_b : BaseART + The instantiated ART module used for clustering the columns (features). + eta : float + The minimum Pearson correlation required for row clustering. """ params: dict = {"eta": eta} @@ -72,12 +75,18 @@ def __setattr__(self, key, value): def get_params(self, deep: bool = True) -> dict: """ + Get parameters for this estimator. - Parameters: - - deep: If True, will return the parameters for this class and contained subobjects that are estimators. + Parameters + ---------- + deep : bool, default=True + If True, return the parameters for this estimator and contained subobjects + that are estimators. - Returns: - Parameter names mapped to their values. + Returns + ------- + dict + Dictionary of parameter names mapped to their values. """ out = self.params @@ -92,15 +101,21 @@ def get_params(self, deep: bool = True) -> dict: return out def set_params(self, **params): - """Set the parameters of this estimator. + """ + Set the parameters of this estimator. - Specific redefinition of sklearn.BaseEstimator.set_params for ART classes + Specific redefinition of `sklearn.BaseEstimator.set_params` for ART classes. - Parameters: - - **params : Estimator parameters. + Parameters + ---------- + **params : dict + Estimator parameters as keyword arguments. + + Returns + ------- + self : object + The estimator instance. - Returns: - - self : estimator instance """ if not params: @@ -132,12 +147,14 @@ def set_params(self, **params): return self @staticmethod - def validate_params(params): + def validate_params(params: dict): """ - validate clustering parameters + Validate clustering parameters. - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ assert "eta" in params @@ -161,30 +178,41 @@ def n_column_clusters(self): def _get_x_cb(self, x: np.ndarray, c_b: int): """ - get the components of a vector belonging to a b-side cluster + Get the components of a vector belonging to a b-side cluster. - Parameters: - - x: a sample vector - - c_b: b-side cluster label + Parameters + ---------- + x : np.ndarray + A sample vector. + c_b : int + The b-side cluster label. - Returns: - x filtered to features belonging to the b-side cluster c_b + Returns + ------- + np.ndarray + The sample vector `x` filtered to include only features belonging to + the b-side cluster `c_b`. """ b_components = self.module_b.labels_ == c_b return x[b_components] @staticmethod - def _pearsonr(a: np.ndarray, b: np.ndarray): + def _pearsonr(a: np.ndarray, b: np.ndarray) -> float: """ - get the correlation between two vectors + Get the Pearson correlation between two vectors. - Parameters: - - a: some vector - - b: some vector + Parameters + ---------- + a : np.ndarray + A vector. + b : np.ndarray + Another vector. - Returns: - Pearson correlation + Returns + ------- + float + The Pearson correlation between the two vectors `a` and `b`. """ r, _ = pearsonr(a, b) @@ -192,12 +220,22 @@ def _pearsonr(a: np.ndarray, b: np.ndarray): def _average_pearson_corr(self, X: np.ndarray, k: int, c_b: int) -> float: """ - get the average correlation between for a sample for all features in cluster b - - Parameters: - - X: data set A - - k: sample index - - c_b: b-side cluster to check + Get the average Pearson correlation for a sample across all features in cluster b. + + Parameters + ---------- + X : np.ndarray + The dataset A. + k : int + The sample index. + c_b : int + The b-side cluster to check. + + Returns + ------- + float + The average Pearson correlation for the sample at index `k` across all + features in cluster `c_b`. """ X_a = X[self.column_labels_ == c_b, :] @@ -215,11 +253,14 @@ def _average_pearson_corr(self, X: np.ndarray, k: int, c_b: int) -> float: def validate_data(self, X_a: np.ndarray, X_b: np.ndarray): """ - validates the data prior to clustering + Validate the data prior to clustering. - Parameters: - - X: data set A - - y: data set B + Parameters + ---------- + X_a : np.ndarray + Dataset A, containing the samples. + X_b : np.ndarray + Dataset B, containing the features. """ self.module_a.validate_data(X_a) @@ -227,16 +268,23 @@ def validate_data(self, X_a: np.ndarray, X_b: np.ndarray): def match_criterion_bin(self, X: np.ndarray, k: int, c_b: int, params: dict) -> bool: """ - get the binary match criterion of the cluster - - Parameters: - - X: data set - - k: sample index - - c_b: b-side cluster to check - - params: dict containing parameters for the algorithm - - Returns: - cluster match criterion binary + Get the binary match criterion of the cluster. + + Parameters + ---------- + X : np.ndarray + The dataset. + k : int + The sample index. + c_b : int + The b-side cluster to check. + params : dict + Dictionary containing parameters for the algorithm. + + Returns + ------- + bool + Binary value indicating whether the cluster match criterion is met. """ M = self._average_pearson_corr(X, k, c_b) @@ -252,18 +300,27 @@ def match_reset_func( cache: Optional[dict] = None ) -> bool: """ - Permits external factors to influence cluster creation. - - Parameters: - - i: data sample - - w: cluster weight / info - - cluster_a: a-side cluster label - - params: dict containing parameters for the algorithm - - extra: additional parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - true if match is permitted + Permit external factors to influence cluster creation. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + cluster_a : int + A-side cluster label. + params : dict + Dictionary containing parameters for the algorithm. + extra : dict + Additional parameters for the algorithm. + cache : dict, optional + Dictionary containing values cached from previous calculations. + + Returns + ------- + bool + True if the match is permitted, otherwise False. """ k = extra["k"] @@ -274,14 +331,19 @@ def match_reset_func( def step_fit(self, X: np.ndarray, k: int) -> int: """ - fit the model to a single sample + Fit the model to a single sample. - Parameters: - - X: data set - - k: sample index + Parameters + ---------- + X : np.ndarray + The dataset. + k : int + The sample index. - Returns: - cluster label of the input sample + Returns + ------- + int + The cluster label of the input sample. """ match_reset_func = lambda i, w, cluster, params, cache: self.match_reset_func( @@ -292,11 +354,14 @@ def step_fit(self, X: np.ndarray, k: int) -> int: def fit(self, X: np.ndarray, max_iter=1): """ - Fit the model to the data + Fit the model to the data. - Parameters: - - X: data set - - max_iter: number of iterations to fit the model on the same data set + Parameters + ---------- + X : np.ndarray + The dataset to fit the model on. + max_iter : int + The number of iterations to fit the model on the same dataset. """ # Check that X and y have correct shape @@ -343,10 +408,12 @@ def visualize( cmap: Optional[Colormap] = None ): """ - Visualize the clustering of the data + Visualize the clustering of the data. - Parameters: - - cmap: some colormap + Parameters + ---------- + cmap : matplotlib.colors.Colormap or str + The colormap to use for visualization. """ import matplotlib.pyplot as plt diff --git a/artlib/cvi/CVIART.py b/artlib/cvi/CVIART.py index 62cfc81..fc0a3a6 100644 --- a/artlib/cvi/CVIART.py +++ b/artlib/cvi/CVIART.py @@ -16,26 +16,37 @@ class CVIART(BaseART): Note, the default step_fit function in base ART evaluates the matching function even if the other criteria has failed. This means it could run slower then it would otherwise. - - Parameters: - rho: float [0,1] for the vigilance parameter. - alpha: float choice parameter. 1e-7 recommended value. - beta: float [0,1] learning parameters. beta = 1 is fast learning recommended value. - validity: int the cluster validity index being used. - W: list of weights, top down. - labels: class labels for data set. """ CALINSKIHARABASZ = 1 DAVIESBOULDIN = 2 SILHOUETTE = 3 # PBM = 4 + def __init__(self, base_module: BaseART, validity: int): + """ + Initialize the CVIART model. + + Parameters + ---------- + base_module : BaseART + Base ART module for clustering. + validity : int + Validity index used for cluster evaluation. + + """ + self.base_module = base_module + params = dict(base_module.params, **{"validity": validity}) + super().__init__(params) + print(self.params) + def validate_params(self, params: dict): """ - validate clustering parameters + Validate clustering parameters. - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ self.base_module.validate_params(params) @@ -45,25 +56,35 @@ def validate_params(self, params: dict): def prepare_data(self, X: np.ndarray) -> np.ndarray: """ - prepare data for clustering + Prepare data for clustering. + + Parameters + ---------- + X : np.ndarray + Dataset to be normalized. - Parameters: - - X: data set + Returns + ------- + np.ndarray + Normalized data. - Returns: - normalized data """ return self.base_module.prepare_data(X) def restore_data(self, X: np.ndarray) -> np.ndarray: """ - restore data to state prior to preparation + Restore data to state prior to preparation. + + Parameters + ---------- + X : np.ndarray + Dataset to be restored. - Parameters: - - X: data set + Returns + ------- + np.ndarray + Restored data. - Returns: - restored data """ return self.base_module.restore_data(X) @@ -84,14 +105,31 @@ def labels_(self, new_labels_): self.base_module.labels_ = new_labels_ - def __init__(self, base_module: BaseART, validity: int): - self.base_module = base_module - params = dict(base_module.params, **{"validity": validity}) - super().__init__(params) - print(self.params) - - def CVI_match(self, x, w, c_, params, extra, cache): + """ + Evaluate the cluster validity index (CVI) for a match. + + Parameters + ---------- + x : np.ndarray + Data sample. + w : np.ndarray + Cluster weight information. + c_ : int + Cluster index. + params : dict + Parameters for the algorithm. + extra : dict + Extra information including index and validity type. + cache : dict + Cache containing values from previous calculations. + + Returns + ------- + bool + True if the new validity score improves the clustering, False otherwise. + + """ if len(self.W) < 2: return True @@ -112,7 +150,29 @@ def CVI_match(self, x, w, c_, params, extra, cache): return new_VI > old_VI else: return new_VI < old_VI + + def _match_tracking(self, cache: dict, epsilon: float, params: dict, method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"]) -> bool: + """ + Adjust the vigilance parameter (rho) based on the match tracking method. + + Parameters + ---------- + cache : dict + Cache containing match criterion. + epsilon : float + Epsilon value for adjusting the vigilance parameter. + params : dict + Parameters for the algorithm. + method : {"MT+", "MT-", "MT0", "MT1", "MT~"} + Method for resetting match criterion. + + Returns + ------- + bool + True if further matching is required, False otherwise. + + """ M = cache["match_criterion"] if method == "MT+": self.base_module.params["rho"] = M+epsilon @@ -140,21 +200,23 @@ def _deep_copy_params(self) -> dict: def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, match_reset_func: Optional[Callable] = None, max_iter=1, match_reset_method:Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0): """ - Fit the model to the data - - Parameters: - - X: data set - - y: not used. For compatibility. - - match_reset_func: a callable accepting the data sample, a cluster weight, the params dict, and the cache dict - Permits external factors to influence cluster creation. - Returns True if the cluster is valid for the sample, False otherwise - - max_iter: number of iterations to fit the model on the same data set - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho + Fit the model to the data. + + Parameters + ---------- + X : np.ndarray + The dataset. + y : np.ndarray, optional + Not used. For compatibility. + match_reset_func : callable, optional + A callable accepting the data sample, a cluster weight, the params dict, and the cache dict. + Returns True if the cluster is valid for the sample, False otherwise. + max_iter : int, optional + Number of iterations to fit the model on the same dataset, by default 1. + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, optional + Method for resetting match criterion. + epsilon : float, optional + Epsilon value used for adjusting match criterion, by default 0.0. """ self.data = X @@ -177,40 +239,95 @@ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, match_reset_func: O def pre_step_fit(self, X: np.ndarray): + """ + Preprocessing step before fitting each sample. + + Parameters + ---------- + X : np.ndarray + The dataset. + + """ return self.base_module.pre_step_fit(X) def post_step_fit(self, X: np.ndarray): + """ + Postprocessing step after fitting each sample. + + Parameters + ---------- + X : np.ndarray + The dataset. + + """ return self.base_module.post_step_fit(X) def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0) -> int: """ - fit the model to a single sample + Fit the model to a single sample. + + Parameters + ---------- + x : np.ndarray + Data sample. + match_reset_func : callable, optional + A callable accepting the data sample, a cluster weight, the params dict, and the cache dict. + Returns True if the cluster is valid for the sample, False otherwise. + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, optional + Method for resetting match criterion. + epsilon : float, optional + Epsilon value used for adjusting match criterion, by default 0.0. + + Returns + ------- + int + Cluster label of the input sample. - Parameters: - - x: data sample - - match_reset_func: a callable accepting the data sample, a cluster weight, the params dict, and the cache dict - Permits external factors to influence cluster creation. - Returns True if the cluster is valid for the sample, False otherwise - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho + """ + raise NotImplementedError + def step_pred(self, x: np.ndarray) -> int: + """ + Predict the label for a single sample. - Returns: - cluster label of the input sample + Parameters + ---------- + x : np.ndarray + Data sample. - """ - raise NotImplementedError + Returns + ------- + int + Cluster label of the input sample. - def step_pred(self, x) -> int: + """ return self.base_module.step_pred(x) def get_cluster_centers(self) -> List[np.ndarray]: + """ + Get the centers of the clusters. + + Returns + ------- + list of np.ndarray + Cluster centroids. + + """ return self.base_module.get_cluster_centers() def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): + """ + Plot the boundaries of each cluster. + + Parameters + ---------- + ax : matplotlib.axes.Axes + Figure axes. + colors : iterable + Colors to use for each cluster. + linewidth : int, optional + Width of boundary line, by default 1. + + """ return self.base_module.plot_cluster_bounds(ax, colors,linewidth) diff --git a/artlib/cvi/iCVIFuzzyArt.py b/artlib/cvi/iCVIFuzzyArt.py index c54aed1..2252e08 100644 --- a/artlib/cvi/iCVIFuzzyArt.py +++ b/artlib/cvi/iCVIFuzzyArt.py @@ -13,26 +13,59 @@ class iCVIFuzzyART(FuzzyART): - """iCVI Fuzzy Art Classification - - Parameters: - rho: float [0,1] for the vigilance parameter. - alpha: float choice parameter. 1e-7 recommended value. - beta: float [0,1] learning parameters. beta = 1 is fast learning recommended value. - validity: int the cluster validity index being used. - W: list of weights, top down. - labels: class labels for data set. + """iCVI Fuzzy Art For Clustering + """ CALINSKIHARABASZ = 1 def __init__(self, rho: float, alpha: float, beta: float, validity: int, offline: bool = True): + """ + Initialize the iCVIFuzzyART model. + + Parameters + ---------- + rho : float + Vigilance parameter in the range [0, 1]. + alpha : float + Choice parameter. A value of 1e-7 is recommended. + beta : float + Learning parameter in the range [0, 1]. A value of 1 is recommended for fast learning. + validity : int + The cluster validity index being used. + offline : bool, optional + Whether to use offline mode for iCVI updates, by default True. + + """ super().__init__(rho, alpha, beta) self.params['validity'] = validity # Currently not used. Waiting for more algorithms. self.offline = offline assert 'validity' in self.params # Because Fuzzy art doesn't accept validity, and makes the params the way it does, validations have to be done after init. assert isinstance(self.params['validity'], int) + def iCVI_match(self, x, w, c_, params, cache): + """ + Apply iCVI (incremental Cluster Validity Index) matching criteria. + + Parameters + ---------- + x : np.ndarray + Data sample. + w : np.ndarray + Cluster weight. + c_ : int + Cluster index. + params : dict + Dictionary containing algorithm parameters. + cache : dict + Cache used for storing intermediate results. + + Returns + ------- + bool + True if the new criterion value is better than the previous one, False otherwise. + + """ if self.offline: new = self.iCVI.switch_label(x, self.labels_[self.index], c_) else: @@ -44,21 +77,23 @@ def iCVI_match(self, x, w, c_, params, cache): # Could add max epochs back in, but only if offline is true, or do something special... def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, match_reset_func: Optional[Callable] = None, max_iter=1, match_reset_method:Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0): """ - Fit the model to the data - - Parameters: - - X: data set - - y: not used. For compatibility. - - match_reset_func: a callable accepting the data sample, a cluster weight, the params dict, and the cache dict - Permits external factors to influence cluster creation. - Returns True if the cluster is valid for the sample, False otherwise - - max_iter: number of iterations to fit the model on the same data set - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho + Fit the model to the data. + + Parameters + ---------- + X : np.ndarray + The dataset. + y : np.ndarray, optional + Not used. For compatibility. + match_reset_func : callable, optional + A callable accepting the data sample, a cluster weight, the params dict, and the cache dict. + Returns True if the cluster is valid for the sample, False otherwise. + max_iter : int, optional + Number of iterations to fit the model on the same dataset, by default 1. + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, optional + Method for resetting match criterion. + epsilon : float, optional + Epsilon value used for adjusting match criterion, by default 0.0. """ self.validate_data(X) diff --git a/artlib/cvi/iCVIs/CalinkskiHarabasz.py b/artlib/cvi/iCVIs/CalinkskiHarabasz.py index 6f85989..1021f9c 100644 --- a/artlib/cvi/iCVIs/CalinkskiHarabasz.py +++ b/artlib/cvi/iCVIs/CalinkskiHarabasz.py @@ -12,13 +12,47 @@ import numpy as np -def delta_add_sample_to_average(average, sample, total_samples): - """Calculate the new average if sample is added""" +def delta_add_sample_to_average(average: float, sample: float, total_samples: int) -> float: + """ + Calculate the new average if a sample is added. + + Parameters + ---------- + average : float + Current average. + sample : float + New sample to be added. + total_samples : int + Total number of samples including the new one. + + Returns + ------- + float + Updated average after adding the sample. + + """ return (sample - average) / total_samples -def delta_remove_sample_from_average(average, sample, total_samples): - """Calculate the new average if sample is removed""" +def delta_remove_sample_from_average(average: float, sample: float, total_samples: int) -> float: + """ + Calculate the new average if a sample is removed. + + Parameters + ---------- + average : float + Current average. + sample : float + Sample to be removed. + total_samples : int + Total number of samples before removal. + + Returns + ------- + float + Updated average after removing the sample. + + """ return (average - sample) / (total_samples - 1) @@ -40,25 +74,17 @@ class iCVI_CH(): samples in the dataset. For the Calinski Harabasz validity Index, larger values represent better clusters. - - Parameters: - dim: an int storing the dimensionality of the input data. - n_samples: an int with the total number of samples. - mu: a numpy array representing the average of all data points. - CD: ClusterData... a dict holding all the data for each cluster, with the parameters - n: the number of samples belonging to each cluster/label. - v: the prototypes/centriods of each cluster. - CP: the compactness of each cluster. Not really needed since WGSS can be calculated incrementally. - G: the vector g of each cluster. - WGSS: Within Groupd sum of squares. - criterion_value: the calculated CH score. """ def __init__(self, x: np.ndarray) -> None: - """Create the iCVI object. + """ + Create the iCVI_CH object. + + Parameters + ---------- + x : np.ndarray + A sample from the dataset used for recording data dimensionality. - Args: - x: a sample from the dataset used for recording data dimensionality. """ self.dim = x.shape[0] # Dimension of the input data self.n_samples: int = 0 # number of samples encountered @@ -67,20 +93,22 @@ def __init__(self, x: np.ndarray) -> None: self.WGSS = 0 # within group sum of squares self.criterion_value = 0 # calcualted CH index - def add_sample(self, x, label) -> dict: - """Calculate the result of adding a new sample with a given label. - - Create a dictionary containing the updated values after assigning a label to a given sample. - To accept the outcome of the sample being added, pass the returned parameter dict to update. + def add_sample(self, x: np.ndarray, label: int) -> dict: + """ + Calculate the result of adding a new sample with a given label. - In general, if newP['criterion_value'] > obj.criterion_value, the clustering has been improved. + Parameters + ---------- + x : np.ndarray + The sample to add to the current validity index calculation. + label : int + The sample category/cluster. - Args: - x: The sample to add to the current validity index calculation - label: an int representing the sample category/cluster + Returns + ------- + dict + A dictionary containing the updated values after the sample is added. - Returns: - newP: a dictionary contained the values after the sample is added, to be passed to update call. """ newP = {'x': x, 'label': label} # New Parameters newP['n_samples'] = self.n_samples + 1 @@ -136,15 +164,19 @@ def add_sample(self, x, label) -> dict: newP['criterion_value'] = (BGSS / WGSS) * (newP['n_samples'] - n_clusters) / (n_clusters - 1) return newP - def update(self, params) -> None: - """Update the parameters of the object. + def update(self, params: dict) -> None: + """ + Update the parameters of the object. Takes the updated params from adding/removing a sample or switching its label, and updates the object. Switching a label needs more updates, so those dicts have an extra set of things to update, signified with the 'label2' key existing - Args: - params: dict containing the parameters to update. + Parameters + ---------- + params : dict + Dictionary containing the updated parameters to be applied. + """ self.n_samples = params['n_samples'] self.mu = params['mu'] @@ -155,8 +187,9 @@ def update(self, params) -> None: self.CD[params['label2']] = params['CD2'] self.WGSS += params['CP_diff2'] - def switch_label(self, x, label_old, label_new): - """Calculates the parameters if a sample has its label changed. + def switch_label(self, x: np.ndarray, label_old: int, label_new: int) -> dict: + """ + Calculate the parameters when a sample has its label changed. This essentially removes a sample with the old label from the clusters, then adds it back with the new sample. There are a few optimizations, such as keeping mu the same since adding and removing it doesn't affect any calculations @@ -165,13 +198,21 @@ def switch_label(self, x, label_old, label_new): Otherwise it should work the same as removing a sample and updating, then adding the sample back and updating, without the need to create a deep copy of the object if just testing the operation. - Args: - x: The sample to switch the label of for the current validity index calculation - label_old: an int representing the sample category/cluster the sample belongs to - label_new: an int representing the sample category/cluster the sample will be assigned to + Parameters + ---------- + x : np.ndarray + The sample whose label is being changed. + label_old : int + The old label of the sample. + label_new : int + The new label of the sample. + + Returns + ------- + dict + A dictionary containing the updated values after switching the label. - Returns: - newP: a dictionary contained the values after the sample is added, to be passed to update call.""" + """ if label_new == label_old: return {'n_samples': self.n_samples, 'mu': self.mu, @@ -230,17 +271,22 @@ def switch_label(self, x, label_old, label_new): newP['criterion_value'] = (BGSS / WGSS) * (newP['n_samples'] - n_clusters) / (n_clusters - 1) return newP - def remove_sample(self, x, label): # This is left here mostly as an extra, and not really meant to be used. - """Remove a sample from the clusters + def remove_sample(self, x: np.ndarray, label: int) -> dict: # This is left here mostly as an extra, and not really meant to be used. + """ + Remove a sample from the clusters. - Calculates parameters after removing a sample from the clusters, or the opposite of an add operation. + Parameters + ---------- + x : np.ndarray + The sample to remove from the current validity index calculation. + label : int + The sample category/cluster. - Args: - x: The sample to remove from the current validity index calculation - label: an int representing the sample category/cluster + Returns + ------- + dict + A dictionary containing the updated values after the sample is removed. - Returns: - newP: a dictionary contained the values after the sample is remove, to be passed to update call. """ Data = self.CD[label] if Data['n'] <= 1: diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index cbf257d..fc3afe7 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -19,19 +19,19 @@ class ART1(BaseART): Computer Vision, Graphics, and Image Processing, 37, 54 – 115. doi:10. 1016/S0734-189X(87)80014-2. ART1 is intended for binary data clustering only. - - Parameters: - rho: float [0,1] for the vigilance parameter. - beta: float [0,1] learning parameters. beta = 1 is fast learning and the recommended value. - L: float [0,1] the uncommitted node bias - """ def __init__(self, rho: float, beta: float, L: float): """ - Parameters: - - rho: vigilance parameter - - beta: learning rate - - L: uncommitted node bias + Initialize the ART1 model. + + Parameters + ---------- + rho : float + Vigilance parameter in the range [0, 1]. + beta : float + Learning parameter in the range [0, 1]. A value of 1 is recommended for fast learning. + L : float + Uncommitted node bias, a value greater than or equal to 1. """ params = { @@ -44,10 +44,12 @@ def __init__(self, rho: float, beta: float, L: float): @staticmethod def validate_params(params: dict): """ - validate clustering parameters + Validate clustering parameters. - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ assert "rho" in params @@ -62,10 +64,12 @@ def validate_params(params: dict): def validate_data(self, X: np.ndarray): """ - validates the data prior to clustering + Validate the data prior to clustering. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset. """ assert np.array_equal(X, X.astype(bool)), "ART1 only supports binary data" @@ -73,15 +77,23 @@ def validate_data(self, X: np.ndarray): def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: """ - get the activation of the cluster + Get the activation of the cluster. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - cluster activation, cache used for later processing + Returns + ------- + float + Cluster activation. + dict, optional + Cache used for later processing. """ w_bu = w[:self.dim_] @@ -89,16 +101,25 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: """ - get the match criterion of the cluster + Get the match criterion of the cluster. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. - Returns: - cluster match criterion, cache used for later processing + Returns + ------- + float + Cluster match criterion. + dict + Cache used for later processing. """ w_td = w[self.dim_:] @@ -107,16 +128,23 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: """ - get the updated cluster weight + Get the updated cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. - Returns: - updated cluster weight, cache used for later processing + Returns + ------- + np.ndarray + Updated cluster weight. """ w_td = w[self.dim_:] @@ -128,15 +156,19 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ - generate a new cluster weight + Generate a new cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - updated cluster weight + Returns + ------- + np.ndarray + Updated cluster weight. """ w_td_new = i @@ -145,8 +177,12 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: def get_cluster_centers(self) -> List[np.ndarray]: """ - function for getting centers of each cluster. Used for regression - Returns: - cluster centroid + Get the centers of each cluster, used for regression. + + Returns + ------- + list of np.ndarray + Cluster centroids. + """ return [w[self.dim_:] for w in self.W] diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index 2f75ebf..cbd2fff 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -31,19 +31,19 @@ class ART2A(BaseART): Neural Networks, 4, 493 – 504. doi:10.1016/0893-6080(91) 90045-7. ART2-A is similar to ART1 but designed for analog data. This method is implemented for historical purposes and is not recommended for use. - - Parameters: - rho: float [0,1] for the vigilance parameter. - alpha: float choice parameter. 1e-7 recommended value. - beta: float [0,1] learning parameters. beta = 1 is fast learning and the recommended value. - """ def __init__(self, rho: float, alpha: float, beta: float): """ - Parameters: - - rho: vigilance parameter - - alpha: choice parameter - - beta: learning rate + Initialize the ART2-A model. + + Parameters + ---------- + rho : float + Vigilance parameter in the range [0, 1]. + alpha : float + Choice parameter, recommended value is 1e-7. + beta : float + Learning parameter in the range [0, 1]. A value of 1 is recommended for fast learning. """ warn("Do Not Use ART2. It does not work. This module is provided for completeness only") @@ -58,10 +58,12 @@ def __init__(self, rho: float, alpha: float, beta: float): @staticmethod def validate_params(params: dict): """ - validate clustering parameters + Validate clustering parameters. - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ assert "rho" in params @@ -76,10 +78,12 @@ def validate_params(params: dict): def check_dimensions(self, X: np.ndarray): """ - check the data has the correct dimensions + Check that the data has the correct dimensions. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset. """ if not hasattr(self, "dim_"): @@ -90,15 +94,23 @@ def check_dimensions(self, X: np.ndarray): def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: """ - get the activation of the cluster + Get the activation of the cluster. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - cluster activation, cache used for later processing + Returns + ------- + float + Cluster activation. + dict, optional + Cache used for later processing. """ activation = float(np.dot(i, w)) @@ -107,16 +119,25 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: """ - get the match criterion of the cluster + Get the match criterion of the cluster. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. - Returns: - cluster match criterion, cache used for later processing + Returns + ------- + float + Cluster match criterion. + dict + Cache used for later processing. """ if cache is None: @@ -133,39 +154,54 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: """ - get the updated cluster weight + Get the updated cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. - Returns: - updated cluster weight, cache used for later processing + Returns + ------- + np.ndarray + Updated cluster weight. """ return params["beta"]*i + (1-params["beta"])*w def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ - generate a new cluster weight + Generate a new cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - updated cluster weight + Returns + ------- + np.ndarray + Updated cluster weight. """ return i def get_cluster_centers(self) -> List[np.ndarray]: """ - function for getting centers of each cluster. Used for regression - Returns: - cluster centroid + Get the centers of each cluster, used for regression. + + Returns + ------- + list of np.ndarray + Cluster centroids. + """ return self.W \ No newline at end of file diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index c2638ba..76d4385 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -19,18 +19,18 @@ class BayesianART(BaseART): Bayesian ART clusters data in Bayesian Distributions (Hyper-ellipsoids) and is similar to Gaussian ART but differs in that it allows arbitrary rotation of the hyper-ellipsoid. - - Parameters: - rho: float [0,1] for the vigilance parameter. - cov_init: np.ndarray the initial estimate of the covariance matrix for each cluster. - """ pi2 = np.pi * 2 def __init__(self, rho: float, cov_init: np.ndarray): """ - Parameters: - - rho: vigilance parameter - - cov_init: initial estimate of covariance matrix + Initialize the Bayesian ART model. + + Parameters + ---------- + rho : float + Vigilance parameter in the range [0, 1]. + cov_init : np.ndarray + Initial estimate of the covariance matrix for each cluster. """ params = { @@ -42,10 +42,12 @@ def __init__(self, rho: float, cov_init: np.ndarray): @staticmethod def validate_params(params: dict): """ - validate clustering parameters + Validate clustering parameters. - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ assert "rho" in params @@ -56,10 +58,12 @@ def validate_params(params: dict): def check_dimensions(self, X: np.ndarray): """ - check the data has the correct dimensions + Check that the data has the correct dimensions. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset. """ if not hasattr(self, "dim_"): @@ -71,15 +75,23 @@ def check_dimensions(self, X: np.ndarray): def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: """ - get the activation of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - Returns: - cluster activation, cache used for later processing + Get the activation of the cluster. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + + Returns + ------- + float + Cluster activation. + dict, optional + Cache used for later processing. """ mean = w[:self.dim_] @@ -104,16 +116,25 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: """ - get the match criterion of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - cluster match criterion, cache used for later processing + Get the match criterion of the cluster. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. + + Returns + ------- + float + Cluster match criterion. + dict + Cache used for later processing. """ # the original paper uses the det(cov_old) for match criterion @@ -128,16 +149,27 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None, op: Callable = operator.ge) -> tuple[bool, dict]: """ - get the binary match criterion of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - cluster match criterion binary, cache used for later processing + Get the binary match criterion of the cluster. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. + op : callable, optional + Operator for comparison, by default operator.ge. + + Returns + ------- + bool + Binary match criterion. + dict + Cache used for later processing. """ M, cache = self.match_criterion(i, w, params=params, cache=cache) @@ -150,6 +182,26 @@ def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: return M_bin, cache def _match_tracking(self, cache: dict, epsilon: float, params: dict, method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"]) -> bool: + """ + Adjust match tracking based on the method and epsilon value. + + Parameters + ---------- + cache : dict + Cache containing intermediate results, including the match criterion. + epsilon : float + Adjustment factor for the match criterion. + params : dict + Dictionary containing algorithm parameters. + method : {"MT+", "MT-", "MT0", "MT1", "MT~"} + Match tracking method to use. + + Returns + ------- + bool + True if match tracking continues, False otherwise. + + """ M = cache["match_criterion"] # we have to reverse some signs becayse bayesianART has an inverted vigilence check if method == "MT+": @@ -171,16 +223,23 @@ def _match_tracking(self, cache: dict, epsilon: float, params: dict, method: Lit def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: """ - get the updated cluster weight - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - updated cluster weight, cache used for later processing + Get the updated cluster weight. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. + + Returns + ------- + np.ndarray + Updated cluster weight. """ if cache is None: @@ -205,35 +264,47 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ - generate a new cluster weight + Generate a new cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - updated cluster weight + Returns + ------- + np.ndarray + Updated cluster weight. """ return np.concatenate([i, params["cov_init"].flatten(), [1]]) def get_cluster_centers(self) -> List[np.ndarray]: """ - function for getting centers of each cluster. Used for regression - Returns: - cluster centroid + Get the centers of each cluster, used for regression. + + Returns + ------- + list of np.ndarray + Cluster centroids. + """ return [w[:self.dim_] for w in self.W] def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ - undefined function for visualizing the bounds of each cluster - - Parameters: - - ax: figure axes - - colors: colors to use for each cluster - - linewidth: width of boundary line + Visualize the bounds of each cluster. + + Parameters + ---------- + ax : matplotlib.axes.Axes + Figure axes. + colors : iterable + Colors to use for each cluster. + linewidth : int, optional + Width of boundary line, by default 1. """ for w, col in zip(self.W, colors): diff --git a/artlib/elementary/DualVigilanceART.py b/artlib/elementary/DualVigilanceART.py index 9052882..11448d5 100644 --- a/artlib/elementary/DualVigilanceART.py +++ b/artlib/elementary/DualVigilanceART.py @@ -23,18 +23,18 @@ class DualVigilanceART(BaseART): permits clusters to be combined to form arbitrary shapes. For example if the base ART module is fuzzy ART, a Dual Vigilance Fuzzy ART clustering result would look like a series of hyper-boxes forming an arbitrary geometry. - - Parameters: - base_module: BaseART the instantiated ART module that wil serve as the base for dual vigilance - rho_lower_bound: float the lower vigilance value that will "merge" the base_module clusters - """ def __init__(self, base_module: BaseART, rho_lower_bound: float): """ - Parameters: - - base_module: BaseART the instantiated ART module that wil serve as the base for dual vigilance - - rho_lower_bound: float the lower vigilance value that will "merge" the base_module clusters + Initialize the Dual Vigilance ART model. + + Parameters + ---------- + base_module : BaseART + The instantiated ART module that will serve as the base for dual vigilance. + rho_lower_bound : float + The lower vigilance value that will "merge" the base_module clusters. """ assert isinstance(base_module, BaseART) @@ -55,35 +55,50 @@ def __init__(self, base_module: BaseART, rho_lower_bound: float): def prepare_data(self, X: np.ndarray) -> np.ndarray: """ - prepare data for clustering + Prepare data for clustering. + + Parameters + ---------- + X : np.ndarray + The dataset. - Parameters: - - X: data set + Returns + ------- + np.ndarray + Prepared data from the base module. - Returns: - base modules prepare_data """ return self.base_module.prepare_data(X) def restore_data(self, X: np.ndarray) -> np.ndarray: """ - restore data to state prior to preparation + Restore data to its state prior to preparation. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset. + + Returns + ------- + np.ndarray + Restored data from the base module. - Returns: - restored data """ return self.base_module.restore_data(X) def get_params(self, deep: bool = True) -> dict: """ + Get the parameters of the estimator. - Parameters: - - deep: If True, will return the parameters for this class and contained subobjects that are estimators. + Parameters + ---------- + deep : bool, optional + If True, return the parameters for this class and contained subobjects that are estimators, by default True. - Returns: + Returns + ------- + dict Parameter names mapped to their values. """ @@ -100,15 +115,27 @@ def get_params(self, deep: bool = True) -> dict: @property def n_clusters(self) -> int: """ - get the current number of clusters + Get the current number of clusters. + + Returns + ------- + int + The number of clusters. - Returns: - the number of clusters """ return len(set(c for c in self.map.values())) @property def dim_(self): + """ + Get the dimensionality of the data from the base module. + + Returns + ------- + int + Dimensionality of the data. + + """ return self.base_module.dim_ @dim_.setter @@ -117,6 +144,15 @@ def dim_(self, new_dim): @property def labels_(self): + """ + Get the labels from the base module. + + Returns + ------- + np.ndarray + Labels for the data. + + """ return self.base_module.labels_ @labels_.setter @@ -129,24 +165,37 @@ def W(self): @W.setter def W(self, new_W: list[np.ndarray]): + """ + Get the weights from the base module. + + Returns + ------- + list of np.ndarray + Weights of the clusters. + + """ self.base_module.W = new_W def check_dimensions(self, X: np.ndarray): """ - check the data has the correct dimensions + Check that the data has the correct dimensions. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset. """ self.base_module.check_dimensions(X) def validate_data(self, X: np.ndarray): """ - validates the data prior to clustering + Validate the data prior to clustering. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset. """ self.base_module.validate_data(X) @@ -154,10 +203,12 @@ def validate_data(self, X: np.ndarray): def validate_params(self, params: dict): """ - validate clustering parameters + Validate clustering parameters. - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ @@ -167,6 +218,26 @@ def validate_params(self, params: dict): assert isinstance(params["rho_lower_bound"], float) def _match_tracking(self, cache: dict, epsilon: float, params: dict, method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"]) -> bool: + """ + Adjust match tracking based on the method and epsilon value. + + Parameters + ---------- + cache : dict + Cache containing intermediate results, including the match criterion. + epsilon : float + Adjustment factor for the match criterion. + params : dict + Dictionary containing algorithm parameters. + method : {"MT+", "MT-", "MT0", "MT1", "MT~"} + Match tracking method to use. + + Returns + ------- + bool + True if match tracking continues, False otherwise. + + """ M = cache["match_criterion"] if method == "MT+": self.base_module.params["rho"] = M+epsilon @@ -193,16 +264,24 @@ def _deep_copy_params(self) -> dict: def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None,match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0) -> int: """ - fit the model to a single sample - - Parameters: - - x: data sample - - match_reset_func: a callable accepting the data sample, a cluster weight, the params dict, and the cache dict - Permits external factors to influence cluster creation. - Returns True if the cluster is valid for the sample, False otherwise - - Returns: - cluster label of the input sample + Fit the model to a single sample. + + Parameters + ---------- + x : np.ndarray + Data sample. + match_reset_func : callable, optional + A callable accepting the data sample, a cluster weight, the params dict, and the cache dict. + Returns True if the cluster is valid for the sample, False otherwise. + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, optional + Method for resetting match criterion, by default "MT+". + epsilon : float, optional + Epsilon value used for adjusting match criterion, by default 0.0. + + Returns + ------- + int + Cluster label of the input sample. """ base_params = self._deep_copy_params() @@ -262,13 +341,17 @@ def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None,ma def step_pred(self, x) -> int: """ - predict the label for a single sample + Predict the label for a single sample. - Parameters: - - x: data sample + Parameters + ---------- + x : np.ndarray + Data sample. - Returns: - cluster label of the input sample + Returns + ------- + int + Cluster label of the input sample. """ assert len(self.base_module.W) >= 0, "ART module is not fit." @@ -283,20 +366,28 @@ def step_pred(self, x) -> int: def get_cluster_centers(self) -> List[np.ndarray]: """ - function for getting centers of each cluster. Used for regression - Returns: - cluster centroid + Get the centers of each cluster, used for regression. + + Returns + ------- + list of np.ndarray + Cluster centroids. + """ return self.base_module.get_cluster_centers() def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ - function for visualizing the bounds of each cluster - - Parameters: - - ax: figure axes - - colors: colors to use for each cluster - - linewidth: width of boundary line + Visualize the bounds of each cluster. + + Parameters + ---------- + ax : matplotlib.axes.Axes + Figure axes. + colors : iterable + Colors to use for each cluster. + linewidth : int, optional + Width of boundary line, by default 1. """ colors_base = [] diff --git a/artlib/elementary/EllipsoidART.py b/artlib/elementary/EllipsoidART.py index 16a271b..e0ada65 100644 --- a/artlib/elementary/EllipsoidART.py +++ b/artlib/elementary/EllipsoidART.py @@ -25,23 +25,23 @@ class EllipsoidART(BaseART): Ellipsoid ART clusters data in Hyper-ellipsoids. It is highly sensitive to sample presentation order as the second sample will determine the orientation of the principal axes. - - Parameters: - rho: float [0,1] for the vigilance parameter. - alpha: float choice parameter. 1e-7 recommended value. - beta: float [0,1] learning parameters. beta = 1 is fast learning and the recommended value. - mu: float ratio between the major and minor axes - r_hat: float radius bias parameter - """ def __init__(self, rho: float, alpha: float, beta: float, mu: float, r_hat: float): """ - Parameters: - - rho: vigilance parameter - - alpha: choice parameter - - beta: learning rate - - mu: ratio between major and minor axess - - r_hat: radius bias parameter + Initialize the Ellipsoid ART model. + + Parameters + ---------- + rho : float + Vigilance parameter in the range [0, 1]. + alpha : float + Choice parameter, recommended value is 1e-7. + beta : float + Learning parameter in the range [0, 1]. A value of 1 is recommended for fast learning. + mu : float + Ratio between major and minor axes. + r_hat : float + Radius bias parameter. """ params = { @@ -56,10 +56,12 @@ def __init__(self, rho: float, alpha: float, beta: float, mu: float, r_hat: floa @staticmethod def validate_params(params: dict): """ - validate clustering parameters + Validate clustering parameters. - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ assert "rho" in params @@ -78,7 +80,27 @@ def validate_params(params: dict): assert isinstance(params["r_hat"], float) @staticmethod - def category_distance(i: np.ndarray, centroid: np.ndarray, major_axis: np.ndarray, params): + def category_distance(i: np.ndarray, centroid: np.ndarray, major_axis: np.ndarray, params: dict) -> float: + """ + Calculate the distance between a sample and the cluster centroid. + + Parameters + ---------- + i : np.ndarray + Data sample. + centroid : np.ndarray + Centroid of the cluster. + major_axis : np.ndarray + Major axis of the cluster. + params : dict + Dictionary containing parameters for the algorithm. + + Returns + ------- + float + Distance between the sample and the cluster centroid. + + """ ic_dist = (i - centroid) if major_axis.any(): @@ -90,15 +112,23 @@ def category_distance(i: np.ndarray, centroid: np.ndarray, major_axis: np.ndarra def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: """ - get the activation of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - Returns: - cluster activation, cache used for later processing + Get the activation of the cluster. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + + Returns + ------- + float + Cluster activation. + dict, optional + Cache used for later processing. """ centroid = w[:self.dim_] @@ -115,16 +145,25 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: """ - get the match criterion of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - cluster match criterion, cache used for later processing + Get the match criterion of the cluster. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. + + Returns + ------- + float + Cluster match criterion. + dict + Cache used for later processing. """ radius = w[-1] @@ -137,16 +176,23 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: """ - get the updated cluster weight - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - updated cluster weight, cache used for later processing + Get the updated cluster weight. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. + + Returns + ------- + np.ndarray + Updated cluster weight. """ centroid = w[:self.dim_] @@ -169,20 +215,33 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ - generate a new cluster weight + Generate a new cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - updated cluster weight + Returns + ------- + np.ndarray + New cluster weight. """ return np.concatenate([i, np.zeros_like(i), [0.]]) def get_2d_ellipsoids(self) -> list[tuple]: + """ + Get the 2D ellipsoids for visualization. + + Returns + ------- + list of tuple + Each tuple contains the centroid, width, height, and angle of an ellipsoid. + + """ ellipsoids = [] for w in self.W: centroid = w[:2] @@ -199,20 +258,28 @@ def get_2d_ellipsoids(self) -> list[tuple]: def get_cluster_centers(self) -> List[np.ndarray]: """ - function for getting centers of each cluster. Used for regression - Returns: - cluster centroid + Get the centers of each cluster, used for regression. + + Returns + ------- + list of np.ndarray + Cluster centroids. + """ return [w[:self.dim_] for w in self.W] def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ - undefined function for visualizing the bounds of each cluster + Visualize the bounds of each cluster. - Parameters: - - ax: figure axes - - colors: colors to use for each cluster - - linewidth: width of boundary line + Parameters + ---------- + ax : matplotlib.axes.Axes + Figure axes. + colors : iterable + Colors to use for each cluster. + linewidth : int, optional + Width of boundary line, by default 1. """ from matplotlib.patches import Ellipse @@ -230,8 +297,3 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): ) ax.add_patch(ellip) - - - - - diff --git a/artlib/elementary/FuzzyART.py b/artlib/elementary/FuzzyART.py index 5e1dafe..fe24eb6 100644 --- a/artlib/elementary/FuzzyART.py +++ b/artlib/elementary/FuzzyART.py @@ -12,14 +12,20 @@ def get_bounding_box(w: np.ndarray, n: Optional[int] = None) -> tuple[list[int], list[int]]: """ - extract the bounding boxes from a FuzzyART weight + Extract the bounding boxes from a FuzzyART weight. - Parameters: - - w: a fuzzy ART weight - - n: dimensions of the bounding box + Parameters + ---------- + w : np.ndarray + A fuzzy ART weight. + n : int, optional + Dimensions of the bounding box. + + Returns + ------- + tuple + A tuple containing the reference point and lengths of each edge. - Returns: - reference_point, lengths of each edge """ n_ = int(len(w) / 2) if n is None: @@ -46,19 +52,19 @@ class FuzzyART(BaseART): Fuzzy ART: Fast stable learning and categorization of analog patterns by an adaptive resonance system. Neural Networks, 4, 759 – 771. doi:10.1016/0893-6080(91)90056-B. Fuzzy ART is a hyper-box based clustering method. - - Parameters: - rho: float [0,1] for the vigilance parameter. - alpha: float choice parameter. 1e-7 recommended value. - beta: float [0,1] learning parameters. beta = 1 is fast learning and the recommended value. - """ def __init__(self, rho: float, alpha: float, beta: float): """ - Parameters: - - rho: vigilance parameter - - alpha: choice parameter - - beta: learning rate + Initialize the Fuzzy ART model. + + Parameters + ---------- + rho : float + Vigilance parameter. + alpha : float + Choice parameter. + beta : float + Learning rate. """ params = { @@ -70,13 +76,18 @@ def __init__(self, rho: float, alpha: float, beta: float): def prepare_data(self, X: np.ndarray) -> np.ndarray: """ - prepare data for clustering + Prepare data for clustering. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + Dataset. + + Returns + ------- + np.ndarray + Normalized and compliment coded data. - Returns: - normalized and compliment coded data """ normalized, self.d_max_, self.d_min_ = normalize(X, self.d_max_, self.d_min_) cc_data = compliment_code(normalized) @@ -84,13 +95,18 @@ def prepare_data(self, X: np.ndarray) -> np.ndarray: def restore_data(self, X: np.ndarray) -> np.ndarray: """ - restore data to state prior to preparation + Restore data to its state prior to preparation. + + Parameters + ---------- + X : np.ndarray + Dataset. - Parameters: - - X: data set + Returns + ------- + np.ndarray + Restored data. - Returns: - restored data """ out = de_compliment_code(X) return super(FuzzyART, self).restore_data(out) @@ -98,10 +114,12 @@ def restore_data(self, X: np.ndarray) -> np.ndarray: @staticmethod def validate_params(params: dict): """ - validate clustering parameters + Validate clustering parameters. - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ assert "rho" in params @@ -116,10 +134,12 @@ def validate_params(params: dict): def check_dimensions(self, X: np.ndarray): """ - check the data has the correct dimensions + Check that the data has the correct dimensions. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + Dataset. """ if not hasattr(self, "dim_"): @@ -130,10 +150,12 @@ def check_dimensions(self, X: np.ndarray): def validate_data(self, X: np.ndarray): """ - validates the data prior to clustering + Validate the data prior to clustering. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + Dataset. """ assert X.shape[1] % 2 == 0, "Data has not been compliment coded" @@ -144,31 +166,48 @@ def validate_data(self, X: np.ndarray): def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: """ - get the activation of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - Returns: - cluster activation, cache used for later processing + Get the activation of the cluster. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + + Returns + ------- + float + Cluster activation. + dict, optional + Cache used for later processing. """ return l1norm(fuzzy_and(i, w)) / (params["alpha"] + l1norm(w)), None def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: """ - get the match criterion of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - cluster match criterion, cache used for later processing + Get the match criterion of the cluster. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. + + Returns + ------- + float + Cluster match criterion. + dict + Cache used for later processing. """ return l1norm(fuzzy_and(i, w)) / self.dim_original, cache @@ -176,16 +215,23 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: """ - get the updated cluster weight - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - updated cluster weight, cache used for later processing + Get the updated cluster weight. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. + + Returns + ------- + np.ndarray + Updated cluster weight. """ b = params["beta"] @@ -195,36 +241,67 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ - generate a new cluster weight + Generate a new cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - updated cluster weight + Returns + ------- + np.ndarray + New cluster weight. """ return i - def get_bounding_boxes(self, n: Optional[int] = None): + def get_bounding_boxes(self, n: Optional[int] = None) -> List[tuple[list[int], list[int]]]: + """ + Get the bounding boxes for each cluster. + + Parameters + ---------- + n : int, optional + Dimensions of the bounding box. + + Returns + ------- + list + List of bounding boxes. + + """ return list(map(lambda w: get_bounding_box(w, n=n), self.W)) def get_cluster_centers(self) -> List[np.ndarray]: """ - function for getting centers of each cluster. Used for regression - Returns: - cluster centroid + Get the centers of each cluster, used for regression. + + Returns + ------- + list of np.ndarray + Cluster centroids. + """ - # centers = [] - # for w in self.W: - # ref_points, widths = get_bounding_box(w,None) - # centers.append(np.array(ref_points)+0.5*np.array(widths)) - # return return [self.restore_data(w.reshape((1,-1))).reshape((-1,)) for w in self.W] def shrink_clusters(self, shrink_ratio: float = 0.1): + """ + Shrink the clusters by adjusting the bounding box. + + Parameters + ---------- + shrink_ratio : float, optional + The ratio by which to shrink the clusters, by default 0.1. + + Returns + ------- + FuzzyART + Self after shrinking the clusters. + + """ new_W = [] dim = len(self.W[0])//2 for w in self.W: @@ -239,12 +316,16 @@ def shrink_clusters(self, shrink_ratio: float = 0.1): def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ - undefined function for visualizing the bounds of each cluster - - Parameters: - - ax: figure axes - - colors: colors to use for each cluster - - linewidth: width of boundary line + Visualize the bounds of each cluster. + + Parameters + ---------- + ax : matplotlib.axes.Axes + Figure axes. + colors : iterable + Colors to use for each cluster. + linewidth : int, optional + Width of boundary line, by default 1. """ from matplotlib.patches import Rectangle diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index db77800..7954731 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -22,19 +22,19 @@ class GaussianART(BaseART): in that the hyper-ellipsoid always have their principal axes square to the coordinate frame. It is also faster than Bayesian ART. - - Parameters: - rho: float [0,1] for the vigilance parameter. - sigma_init: np.ndarray the initial estimate of the variance of each dimension for each cluster. - alpha: float an arbitrarily small parameter used to prevent division-by-zero errors. 1e-10 is recommended - """ def __init__(self, rho: float, sigma_init: np.ndarray, alpha: float = 1e-10): """ - Parameters: - - rho: vigilance parameter - - sigma_init: initial estimate of the diagonal std - - alpha: used to prevent division by zero errors + Initialize the Gaussian ART model. + + Parameters + ---------- + rho : float + Vigilance parameter. + sigma_init : np.ndarray + Initial estimate of the diagonal standard deviations. + alpha : float, optional + Small parameter to prevent division by zero errors, by default 1e-10. """ params = { @@ -48,10 +48,12 @@ def __init__(self, rho: float, sigma_init: np.ndarray, alpha: float = 1e-10): @staticmethod def validate_params(params: dict): """ - validate clustering parameters + Validate clustering parameters. - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ assert "rho" in params @@ -65,15 +67,23 @@ def validate_params(params: dict): def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: """ - get the activation of the cluster + Get the activation of the cluster. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - cluster activation, cache used for later processing + Returns + ------- + float + Cluster activation. + dict, optional + Cache used for later processing. """ mean = w[:self.dim_] @@ -99,16 +109,25 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: """ - get the match criterion of the cluster + Get the match criterion of the cluster. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. - Returns: - cluster match criterion, cache used for later processing + Returns + ------- + float + Cluster match criterion. + dict + Cache used for later processing. """ if cache is None: @@ -120,16 +139,23 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: """ - get the updated cluster weight + Get the updated cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. - Returns: - updated cluster weight, cache used for later processing + Returns + ------- + np.ndarray + Updated cluster weight. """ mean = w[:self.dim_] @@ -149,15 +175,19 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ - generate a new cluster weight + Generate a new cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - updated cluster weight + Returns + ------- + np.ndarray + New cluster weight. """ sigma2 = np.multiply(params["sigma_init"], params["sigma_init"]) @@ -167,21 +197,29 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: def get_cluster_centers(self) -> List[np.ndarray]: """ - function for getting centers of each cluster. Used for regression - Returns: - cluster centroid + Get the centers of each cluster, used for regression. + + Returns + ------- + list of np.ndarray + Cluster centroids. + """ return [w[:self.dim_] for w in self.W] def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ - undefined function for visualizing the bounds of each cluster + Visualize the bounds of each cluster. - Parameters: - - ax: figure axes - - colors: colors to use for each cluster - - linewidth: width of boundary line + Parameters + ---------- + ax : matplotlib.axes.Axes + Figure axes. + colors : iterable + Colors to use for each cluster. + linewidth : int, optional + Width of boundary line, by default 1. """ for w, col in zip(self.W, colors): diff --git a/artlib/elementary/HypersphereART.py b/artlib/elementary/HypersphereART.py index c1e33ae..8c2d241 100644 --- a/artlib/elementary/HypersphereART.py +++ b/artlib/elementary/HypersphereART.py @@ -19,22 +19,21 @@ class HypersphereART(BaseART): (pp. 59–64). volume 6. doi:10.1109/IJCNN.2000.859373. Hyperpshere ART clusters data in Hyper-spheres similar to k-means with a dynamic k. - - Parameters: - rho: float [0,1] for the vigilance parameter. - alpha: float choice parameter. 1e-7 recommended value. - beta: float [0,1] learning parameters. beta = 1 is fast learning and the recommended value. - mu: float ratio between the major and minor axes - r_hat: float maximum possible radius - """ def __init__(self, rho: float, alpha: float, beta: float, r_hat: float): """ - Parameters: - - rho: vigilance parameter - - alpha: choice parameter - - beta: learning rate - - r_hat: maximum possible category radius + Initialize the Hypersphere ART model. + + Parameters + ---------- + rho : float + Vigilance parameter. + alpha : float + Choice parameter. + beta : float + Learning rate. + r_hat : float + Maximum possible category radius. """ params = { @@ -48,10 +47,12 @@ def __init__(self, rho: float, alpha: float, beta: float, r_hat: float): @staticmethod def validate_params(params: dict): """ - validate clustering parameters + Validate clustering parameters. - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ assert "rho" in params @@ -68,20 +69,48 @@ def validate_params(params: dict): @staticmethod def category_distance(i: np.ndarray, centroid: np.ndarray, radius: float, params) -> float: + """ + Compute the category distance between a data sample and a centroid. + + Parameters + ---------- + i : np.ndarray + Data sample. + centroid : np.ndarray + Cluster centroid. + radius : float + Cluster radius. + params : dict + Dictionary containing parameters for the algorithm. + + Returns + ------- + float + Category distance. + + """ return np.sqrt(l2norm2(i-centroid)) def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: """ - get the activation of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - Returns: - cluster activation, cache used for later processing + Get the activation of the cluster. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + + Returns + ------- + float + Cluster activation. + dict, optional + Cache used for later processing. """ centroid = w[:-1] @@ -99,16 +128,25 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: """ - get the match criterion of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - cluster match criterion, cache used for later processing + Get the match criterion of the cluster. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. + + Returns + ------- + float + Cluster match criterion. + dict + Cache used for later processing. """ radius = w[-1] @@ -122,16 +160,23 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: """ - get the updated cluster weight - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - updated cluster weight, cache used for later processing + Get the updated cluster weight. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. + + Returns + ------- + np.ndarray + Updated cluster weight. """ centroid = w[:-1] @@ -149,36 +194,48 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ - generate a new cluster weight + Generate a new cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - updated cluster weight + Returns + ------- + np.ndarray + New cluster weight. """ return np.concatenate([i, [0.]]) def get_cluster_centers(self) -> List[np.ndarray]: """ - function for getting centers of each cluster. Used for regression - Returns: - cluster centroid + Get the centers of each cluster, used for regression. + + Returns + ------- + list of np.ndarray + Cluster centroids. + """ return [w[:-1] for w in self.W] def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ - undefined function for visualizing the bounds of each cluster - - Parameters: - - ax: figure axes - - colors: colors to use for each cluster - - linewidth: width of boundary line + Visualize the bounds of each cluster. + + Parameters + ---------- + ax : matplotlib.axes.Axes + Figure axes. + colors : iterable + Colors to use for each cluster. + linewidth : int, optional + Width of boundary line, by default 1. """ from matplotlib.patches import Circle diff --git a/artlib/elementary/QuadraticNeuronART.py b/artlib/elementary/QuadraticNeuronART.py index 9d61e9e..7b38ae9 100644 --- a/artlib/elementary/QuadraticNeuronART.py +++ b/artlib/elementary/QuadraticNeuronART.py @@ -25,23 +25,23 @@ class QuadraticNeuronART(BaseART): Quadratic Neuron ART clusters data in Hyper-ellipsoid by utilizing a quadratic neural network for activation and resonance. - - Parameters: - rho: float [0,1] for the vigilance parameter. - s_init: float initial quadratic term - lr_b: float the bias learning rate - lr_w: float the weight matrix learning rate - lr_s: the learning rate for the quadratic term - """ def __init__(self, rho: float, s_init: float, lr_b: float, lr_w: float, lr_s: float): """ - Parameters: - - rho: vigilance parameter - - s_init: initial quadratic term - - lr_b: learning rate for cluster mean - - lr_w: learning rate for cluster weights - - lr_s: learning rate for the quadratic term + Initialize the Quadratic Neuron ART model. + + Parameters + ---------- + rho : float + Vigilance parameter. + s_init : float + Initial quadratic term. + lr_b : float + Learning rate for cluster mean (bias). + lr_w : float + Learning rate for cluster weights. + lr_s : float + Learning rate for the quadratic term. """ params = { @@ -56,10 +56,12 @@ def __init__(self, rho: float, s_init: float, lr_b: float, lr_w: float, lr_s: fl @staticmethod def validate_params(params: dict): """ - validate clustering parameters + Validate clustering parameters. - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ assert "rho" in params @@ -79,15 +81,23 @@ def validate_params(params: dict): def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: """ - get the activation of the cluster + Get the activation of the cluster. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - cluster activation, cache used for later processing + Returns + ------- + float + Cluster activation. + dict, optional + Cache used for later processing. """ dim2 = self.dim_ * self.dim_ @@ -110,16 +120,25 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: """ - get the match criterion of the cluster + Get the match criterion of the cluster. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. - Returns: - cluster match criterion, cache used for later processing + Returns + ------- + float + Cluster match criterion. + dict + Cache used for later processing. """ if cache is None: @@ -128,16 +147,23 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: """ - get the updated cluster weight + Get the updated cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. - Returns: - updated cluster weight, cache used for later processing + Returns + ------- + np.ndarray + Updated cluster weight, cache used for later processing. """ s = cache["s"] @@ -158,15 +184,19 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ - generate a new cluster weight + Generate a new cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - updated cluster weight + Returns + ------- + np.ndarray + New cluster weight. """ w_new = np.identity(self.dim_) @@ -174,21 +204,29 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: def get_cluster_centers(self) -> List[np.ndarray]: """ - function for getting centers of each cluster. Used for regression - Returns: - cluster centroid + Get the centers of each cluster, used for regression. + + Returns + ------- + list of np.ndarray + Cluster centroids. + """ dim2 = self.dim_ * self.dim_ return [w[dim2:-1] for w in self.W] def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ - undefined function for visualizing the bounds of each cluster + Visualize the bounds of each cluster. - Parameters: - - ax: figure axes - - colors: colors to use for each cluster - - linewidth: width of boundary line + Parameters + ---------- + ax : matplotlib.axes.Axes + Figure axes. + colors : iterable + Colors to use for each cluster. + linewidth : int, optional + Width of boundary line, by default 1. """ # kinda works diff --git a/artlib/experimental/ConvexHullART.py b/artlib/experimental/ConvexHullART.py index 84b2092..17feed5 100644 --- a/artlib/experimental/ConvexHullART.py +++ b/artlib/experimental/ConvexHullART.py @@ -12,11 +12,17 @@ def plot_convex_polygon(vertices: np.ndarray, ax: Axes, line_color: str = 'b', l """ Plots a convex polygon given its vertices using Matplotlib. - Parameters: - - vertices: A list of vertices representing a convex polygon. - - ax: A matplotlib Axes object to plot on. If None, creates a new figure and axes. - - line_color: The color of the polygon lines. - - line_width: The width of the polygon lines. + Parameters + ---------- + vertices : np.ndarray + A list of vertices representing a convex polygon. + ax : matplotlib.axes.Axes + A matplotlib Axes object to plot on. + line_color : str, optional + The color of the polygon lines, by default 'b'. + line_width : float, optional + The width of the polygon lines, by default 1.0. + """ vertices = np.array(vertices) # Close the polygon by appending the first vertex at the end @@ -25,15 +31,20 @@ def plot_convex_polygon(vertices: np.ndarray, ax: Axes, line_color: str = 'b', l ax.plot(vertices[:, 0], vertices[:, 1], linestyle='-', color=line_color, linewidth=line_width) -def volume_of_simplex(vertices): +def volume_of_simplex(vertices: np.ndarray) -> float: """ Calculates the n-dimensional volume of a simplex defined by its vertices. - Parameters: - - vertices: An (n+1) x n array representing the coordinates of the simplex vertices. + Parameters + ---------- + vertices : np.ndarray + An (n+1) x n array representing the coordinates of the simplex vertices. + + Returns + ------- + float + Volume of the simplex. - Returns: - - Volume of the simplex. """ vertices = np.asarray(vertices) # Subtract the first vertex from all vertices to form a matrix @@ -42,7 +53,23 @@ def volume_of_simplex(vertices): return np.abs(np.linalg.det(matrix)) / np.math.factorial(len(vertices) - 1) -def minimum_distance(a1, a2): +def minimum_distance(a1: np.ndarray, a2: np.ndarray) -> float: + """ + Calculates the minimum distance between points or line segments. + + Parameters + ---------- + a1 : np.ndarray + Array representing one point or line segment. + a2 : np.ndarray + Array representing another point or line segment. + + Returns + ------- + float + Minimum distance between the two inputs. + + """ def point_to_point_distance(P, Q): """Calculate the Euclidean distance between two points P and Q.""" return np.linalg.norm(P - Q) @@ -93,6 +120,15 @@ def line_segment_to_line_segment_distance(A1, A2, B1, B2): class PseudoConvexHull: def __init__(self, points: np.ndarray): + """ + Initializes a PseudoConvexHull object. + + Parameters + ---------- + points : np.ndarray + An array of points representing the convex hull. + + """ self.points = points @property @@ -110,11 +146,16 @@ def centroid_of_convex_hull(hull: HullTypes): """ Finds the centroid of the volume of a convex hull in n-dimensional space. - Parameters: - - vertices: An array of shape (m, n), where m is the number of vertices and n is the dimension. + Parameters + ---------- + hull : HullTypes + A ConvexHull or PseudoConvexHull object. + + Returns + ------- + np.ndarray + Centroid coordinates. - Returns: - - Centroid coordinates as a numpy array of length n. """ hull_vertices = hull.points[hull.vertices] @@ -137,10 +178,19 @@ def centroid_of_convex_hull(hull: HullTypes): class ConvexHullART(BaseART): + """ + ConvexHull ART for Clustering + """ def __init__(self, rho: float, merge_rho: float): """ - Parameters: - - rho: vigilance parameter + Initializes the ConvexHullART object. + + Parameters + ---------- + rho : float + Vigilance parameter. + merge_rho : float + Merge vigilance parameter. """ params = { @@ -152,10 +202,12 @@ def __init__(self, rho: float, merge_rho: float): @staticmethod def validate_params(params: dict): """ - validate clustering parameters + Validates clustering parameters. - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ assert "rho" in params @@ -164,15 +216,23 @@ def validate_params(params: dict): def category_choice(self, i: np.ndarray, w: HullTypes, params: dict) -> tuple[float, Optional[dict]]: """ - get the activation of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - Returns: - cluster activation, cache used for later processing + Get the activation of the cluster. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : HullTypes + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + + Returns + ------- + float + Cluster activation. + dict, optional + Cache used for later processing. """ if isinstance(w, PseudoConvexHull): @@ -194,16 +254,25 @@ def category_choice(self, i: np.ndarray, w: HullTypes, params: dict) -> tuple[fl def match_criterion(self, i: np.ndarray, w: HullTypes, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: """ - get the match criterion of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - cluster match criterion, cache used for later processing + Get the match criterion of the cluster. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : HullTypes + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values cached from previous calculations. + + Returns + ------- + float + Cluster match criterion. + dict + Cache used for later processing. """ return cache["activation"], cache @@ -211,37 +280,52 @@ def match_criterion(self, i: np.ndarray, w: HullTypes, params: dict, cache: Opti def update(self, i: np.ndarray, w: HullTypes, params: dict, cache: Optional[dict] = None) -> HullTypes: """ - get the updated cluster weight - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - updated cluster weight, cache used for later processing + Get the updated cluster weight. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : HullTypes + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values cached from previous calculations. + + Returns + ------- + HullTypes + Updated cluster weight. """ return cache["new_w"] def new_weight(self, i: np.ndarray, params: dict) -> HullTypes: """ - generate a new cluster weight + Generate a new cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - updated cluster weight + Returns + ------- + HullTypes + New cluster weight. """ new_w = PseudoConvexHull(i.reshape((1,-1))) return new_w def merge_clusters(self): + """ + Merge clusters based on certain conditions. + + """ def can_merge(w1, w2): combined_points = np.vstack([w1.points[w1.vertices,:], w2.points[w2.vertices,:]]) @@ -287,10 +371,12 @@ def can_merge(w1, w2): def post_fit(self, X: np.ndarray): """ - function called after fit. Useful for cluster pruning + Function called after fit. Useful for cluster pruning. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + Data set. """ self.merge_clusters() @@ -300,9 +386,13 @@ def post_fit(self, X: np.ndarray): def get_cluster_centers(self) -> List[np.ndarray]: """ - function for getting centers of each cluster. Used for regression - Returns: - cluster centroid + Get the centers of each cluster, used for regression. + + Returns + ------- + list of np.ndarray + Cluster centroids. + """ centers = [] for w in self.W: @@ -311,12 +401,16 @@ def get_cluster_centers(self) -> List[np.ndarray]: def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ - undefined function for visualizing the bounds of each cluster - - Parameters: - - ax: figure axes - - colors: colors to use for each cluster - - linewidth: width of boundary line + Visualize the bounds of each cluster. + + Parameters + ---------- + ax : matplotlib.axes.Axes + Figure axes. + colors : iterable + Colors to use for each cluster. + linewidth : int, optional + Width of boundary line, by default 1. """ for c, w in zip(colors, self.W): diff --git a/artlib/experimental/SeqART.py b/artlib/experimental/SeqART.py index 9900afe..2a2d023 100644 --- a/artlib/experimental/SeqART.py +++ b/artlib/experimental/SeqART.py @@ -1,16 +1,63 @@ import numpy as np -from typing import Optional, Callable +from typing import Optional, Callable, Tuple from artlib import BaseART import operator import re -def compress_dashes(input_string): +def compress_dashes(input_string: str) -> str: + """ + Compress consecutive dashes in a string into a single dash. + + Parameters + ---------- + input_string : str + The input string containing dashes. + + Returns + ------- + str + The string with consecutive dashes compressed into one dash. + """ return re.sub('-+', '-', input_string) -def arr2seq(x): +def arr2seq(x: np.ndarray) -> str: + """ + Convert an array of integers to a string. + + Parameters + ---------- + x : np.ndarray + Array of integers to be converted. + + Returns + ------- + str + The string representation of the array. + """ return "".join([str(i_) for i_ in x]) -def needleman_wunsch(seq1, seq2, match_score=1, gap_cost=-1, mismatch_cost=-1): +def needleman_wunsch(seq1: str, seq2: str, match_score: int =1, gap_cost: int =-1, mismatch_cost: int =-1) -> Tuple[str, float]: + """ + Perform Needleman-Wunsch sequence alignment between two sequences. + + Parameters + ---------- + seq1 : str + The first sequence to align. + seq2 : str + The second sequence to align. + match_score : int, optional + The score for a match (default is 1). + gap_cost : int, optional + The penalty for a gap (default is -1). + mismatch_cost : int, optional + The penalty for a mismatch (default is -1). + + Returns + ------- + tuple + The aligned sequences and the normalized alignment score. + """ m, n = len(seq1), len(seq2) # Initialize the scoring matrix @@ -70,21 +117,42 @@ def needleman_wunsch(seq1, seq2, match_score=1, gap_cost=-1, mismatch_cost=-1): align2 = align2[::-1] alignment = ''.join([a if a == b else '-' for a, b in zip(align1, align2)]) l = max(len(seq1), len(seq2)) + return alignment, float(score_matrix[m][n])/l def prepare_data(data: np.ndarray) -> np.ndarray: + """ + Prepares the data for clustering. + + Parameters + ---------- + data : np.ndarray + The input data. + + Returns + ------- + np.ndarray + The prepared data. + """ return data class SeqART(BaseART): - # template for ART module + """ + Sequence ART for clustering based on sequence alignment. + """ + def __init__(self, rho: float, metric: Callable = needleman_wunsch): """ - Parameters: - - rho: vigilance parameter - - metric: allignment function. Should be in the format alignment, score = metric(seq_a, seq_b) - + Initialize the SeqART instance. + + Parameters + ---------- + rho : float + The vigilance parameter. + metric : Callable, optional + The alignment function. Should be in the format: alignment, score = metric(seq_a, seq_b). """ params = { "rho": rho, @@ -94,48 +162,56 @@ def __init__(self, rho: float, metric: Callable = needleman_wunsch): @staticmethod def validate_params(params: dict): """ - validate clustering parameters - - Parameters: - - params: dict containing parameters for the algorithm + Validate clustering parameters. + Parameters + ---------- + params : dict + The parameters for the algorithm. """ assert "rho" in params assert isinstance(params["rho"], float) def validate_data(self, X: np.ndarray): """ - validates the data prior to clustering - - Parameters: - - X: data set + Validate the input data for clustering. + Parameters + ---------- + X : np.ndarray + The input data. """ pass def check_dimensions(self, X: np.ndarray): """ - check the data has the correct dimensions - - Parameters: - - X: data set + Check that the input data has the correct dimensions. + Parameters + ---------- + X : np.ndarray + The input data. """ pass def category_choice(self, i: str, w: str, params: dict) -> tuple[float, Optional[dict]]: """ - get the activation of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - Returns: - cluster activation, cache used for later processing - + Get the activation of the cluster. + + Parameters + ---------- + i : str + The data sample. + w : str + The cluster weight/info. + params : dict + The algorithm parameters. + + Returns + ------- + tuple + Cluster activation and cache used for later processing. """ alignment, score = self.metric(arr2seq(i), w) cache = {'alignment': alignment, 'score': score} @@ -143,17 +219,23 @@ def category_choice(self, i: str, w: str, params: dict) -> tuple[float, Optional def match_criterion(self, i: str, w: str, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: """ - get the match criterion of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - cluster match criterion, cache used for later processing - + Get the match criterion of the cluster. + + Parameters + ---------- + i : str + The data sample. + w : str + The cluster weight/info. + params : dict + The algorithm parameters. + cache : dict, optional + Cached values from previous calculations. + + Returns + ------- + tuple + Cluster match criterion and cache used for later processing. """ # _, M = self.metric(cache['alignment'], w) @@ -161,17 +243,25 @@ def match_criterion(self, i: str, w: str, params: dict, cache: Optional[dict] = def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None, op: Callable = operator.ge) -> tuple[bool, dict]: """ - get the binary match criterion of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - cluster match criterion binary, cache used for later processing - + Get the binary match criterion of the cluster. + + Parameters + ---------- + i : np.ndarray + The data sample. + w : np.ndarray + The cluster weight/info. + params : dict + The algorithm parameters. + cache : dict, optional + Cached values from previous calculations. + op : Callable, optional + Comparison operator for the match criterion (default is operator.ge). + + Returns + ------- + tuple + Binary match criterion and cache used for later processing. """ M, cache = self.match_criterion(arr2seq(i), w, params, cache) M_bin = op(M, params["rho"]) @@ -184,32 +274,41 @@ def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: def update(self, i: str, w: str, params: dict, cache: Optional[dict] = None) -> str: """ - get the updated cluster weight - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - updated cluster weight, cache used for later processing - + Update the cluster weight. + + Parameters + ---------- + i : str + The data sample. + w : str + The cluster weight/info. + params : dict + The algorithm parameters. + cache : dict, optional + Cached values from previous calculations. + + Returns + ------- + str + Updated cluster weight. """ # print(cache['alignment']) return compress_dashes(cache['alignment']) def new_weight(self, i: str, params: dict) -> str: """ - generate a new cluster weight - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - Returns: - updated cluster weight - + Generate a new cluster weight. + + Parameters + ---------- + i : str + The data sample. + params : dict + The algorithm parameters. + + Returns + ------- + str + New cluster weight. """ return arr2seq(i) \ No newline at end of file diff --git a/artlib/experimental/merging.py b/artlib/experimental/merging.py index 615a9d0..31334df 100644 --- a/artlib/experimental/merging.py +++ b/artlib/experimental/merging.py @@ -1,4 +1,22 @@ -def find(parent, i): +from typing import List, Callable + +def find(parent: List[int], i: int) -> int: + """ + Find the root of the set containing element i using path compression. + + Parameters + ---------- + parent : list + List representing the parent of each element. + i : int + The element to find the root of. + + Returns + ------- + int + The root of the set containing element i. + + """ if parent[i] == i: return i else: @@ -6,7 +24,22 @@ def find(parent, i): return parent[i] -def union(parent, rank, x, y): +def union(parent: List[int], rank: list[int], x: int, y: int): + """ + Perform union of two sets containing elements x and y using union by rank. + + Parameters + ---------- + parent : list + List representing the parent of each element. + rank : list + List representing the rank (depth) of each tree. + x : int + The first element. + y : int + The second element. + + """ root_x = find(parent, x) root_y = find(parent, y) @@ -21,7 +54,23 @@ def union(parent, rank, x, y): rank[root_x] += 1 -def merge_objects(objects, can_merge): +def merge_objects(objects: List, can_merge: Callable): + """ + Merge objects into groups based on a merge condition function using Union-Find algorithm. + + Parameters + ---------- + objects : list + List of objects to be merged. + can_merge : callable + A function that takes two objects and returns True if they can be merged. + + Returns + ------- + list of list + A list of merged groups, where each group is a list of object indices. + + """ # Initialize Union-Find structure n = len(objects) parent = list(range(n)) diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index 00b2d06..f0c2fac 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -14,6 +14,19 @@ import operator def get_channel_position_tuples(channel_dims: list[int]) -> list[tuple[int, int]]: + """ + Generate the start and end positions for each channel in the input data. + + Parameters + ---------- + channel_dims : list of int + A list representing the number of dimensions for each channel. + + Returns + ------- + list of tuple of int + A list of tuples where each tuple represents the start and end index for a channel. + """ positions = [] start = 0 for length in channel_dims: @@ -38,12 +51,6 @@ class FusionART(BaseART): molti-modal data and allows for different geometries of clusters to be used for each channel. Fusion ART also allows for fitting regression models and specific functions have been implemented to allow this. - - Parameters: - modules: List[BaseART] a list of instantiated ART modules to use for each channel - gamma_values: Union[List[float], np.ndarray] the activation ratio for each channel - channel_dims: Union[List[int], np.ndarray] the dimension of each channel - """ def __init__( @@ -54,10 +61,16 @@ def __init__( ): """ - Parameters: - - modules: List[BaseART] a list of instantiated ART modules to use for each channel - - gamma_values: Union[List[float], np.ndarray] the activation ratio for each channel - - channel_dims: Union[List[int], np.ndarray] the dimension of each channel + Initialize the FusionART instance. + + Parameters + ---------- + modules : List[BaseART] + A list of ART modules corresponding to each data channel. + gamma_values : Union[List[float], np.ndarray] + The activation ratio for each channel. + channel_dims : Union[List[int], np.ndarray] + The number of dimensions for each channel. """ assert len(modules) == len(gamma_values) == len(channel_dims) params = {"gamma_values": gamma_values} @@ -70,13 +83,17 @@ def __init__( def get_params(self, deep: bool = True) -> dict: """ + Get the parameters of the FusionART model. - Parameters: - - deep: If True, will return the parameters for this class and contained subobjects that are estimators. + Parameters + ---------- + deep : bool, optional + If True, will return parameters for this class and the contained sub-objects that are estimators (default is True). - Returns: + Returns + ------- + dict Parameter names mapped to their values. - """ out = self.params for i, module in enumerate(self.modules): @@ -87,10 +104,26 @@ def get_params(self, deep: bool = True) -> dict: @property def n_clusters(self) -> int: + """ + Return the number of clusters in the first ART module. + + Returns + ------- + int + The number of clusters. + """ return self.modules[0].n_clusters @property def W(self): + """ + Get the weights of all modules as a single array. + + Returns + ------- + np.ndarray + Concatenated weights of all channels from the ART modules. + """ W = [ np.concatenate( [ @@ -105,6 +138,14 @@ def W(self): @W.setter def W(self, new_W): + """ + Set the weights for each module by splitting the input weights. + + Parameters + ---------- + new_W : np.ndarray + New concatenated weights to be set for the modules. + """ for k in range(self.n): if len(new_W) > 0: self.modules[k].W = new_W[self._channel_indices[k][0]:self._channel_indices[k][1]] @@ -114,11 +155,12 @@ def W(self, new_W): @staticmethod def validate_params(params: dict): """ - validate clustering parameters - - Parameters: - - params: dict containing parameters for the algorithm + Validate clustering parameters. + Parameters + ---------- + params : dict + The parameters for the FusionART model. """ assert "gamma_values" in params assert all([1.0 >= g >= 0.0 for g in params["gamma_values"]]) @@ -128,11 +170,12 @@ def validate_params(params: dict): def validate_data(self, X: np.ndarray): """ - validates the data prior to clustering - - Parameters: - - X: data set + Validate the input data for clustering. + Parameters + ---------- + X : np.ndarray + The input dataset. """ self.check_dimensions(X) for k in range(self.n): @@ -141,36 +184,45 @@ def validate_data(self, X: np.ndarray): def check_dimensions(self, X: np.ndarray): """ - check the data has the correct dimensions - - Parameters: - - X: data set + Ensure that the input data has the correct dimensions. + Parameters + ---------- + X : np.ndarray + The input dataset. """ assert X.shape[1] == self.dim_, "Invalid data shape" def prepare_data(self, channel_data: List[np.ndarray]) -> np.ndarray: """ - prepare data for clustering + Prepare the input data by processing each channel's data through its respective ART module. - Parameters: - - channel_data: list of channel arrays + Parameters + ---------- + channel_data : list of np.ndarray + List of arrays, one for each channel. - Returns: - normalized data + Returns + ------- + np.ndarray + Processed and concatenated data. """ prepared_channel_data = [self.modules[i].prepare_data(channel_data[i]) for i in range(self.n)] return self.join_channel_data(prepared_channel_data) - def restore_data(self, X: np.ndarray) -> np.ndarray: + def restore_data(self, X: np.ndarray) -> List[np.ndarray]: """ - restore data to state prior to preparation + Restore data to its original state before preparation. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The prepared data. - Returns: - restored data + Returns + ------- + np.ndarray + Restored data for each channel. """ channel_data = self.split_channel_data(X) restored_channel_data = [self.modules[i].restore_data(channel_data[i]) for i in range(self.n)] @@ -178,16 +230,23 @@ def restore_data(self, X: np.ndarray) -> np.ndarray: def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict, skip_channels: List[int] = []) -> tuple[float, Optional[dict]]: """ - get the activation of the cluster + Get the activation of the cluster. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - Returns: - cluster activation, cache used for later processing + Parameters + ---------- + i : np.ndarray + The data sample. + w : np.ndarray + The cluster weight information. + params : dict + Parameters for the ART algorithm. + skip_channels : list of int, optional + Channels to be skipped (default is []). + Returns + ------- + tuple + Cluster activation and cache for further processing. """ activations, caches = zip( *[ @@ -206,6 +265,27 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict, skip_chann return activation, cache def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None, skip_channels: List[int] = []) -> tuple[list[float], dict]: + """ + Get the match criterion for the cluster. + + Parameters + ---------- + i : np.ndarray + The data sample. + w : np.ndarray + The cluster weight information. + params : dict + Parameters for the ART algorithm. + cache : dict, optional + Cache for previous calculations (default is None). + skip_channels : list of int, optional + Channels to be skipped (default is []). + + Returns + ------- + tuple + List of match criteria for each channel and the updated cache. + """ if cache is None: raise ValueError("No cache provided") M, caches = zip( @@ -226,17 +306,27 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None, skip_channels: List[int] = [], op: Callable = operator.ge) -> tuple[bool, dict]: """ - get the binary match criterion of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - cluster match criterion binary, cache used for later processing - + Get the binary match criterion for the cluster. + + Parameters + ---------- + i : np.ndarray + The data sample. + w : np.ndarray + The cluster weight information. + params : dict + Parameters for the ART algorithm. + cache : dict, optional + Cache for previous calculations (default is None). + skip_channels : list of int, optional + Channels to be skipped (default is []). + op : Callable, optional + Operator for comparison (default is operator.ge). + + Returns + ------- + tuple + Binary match criterion and cache for further processing. """ if cache is None: raise ValueError("No cache provided") @@ -259,6 +349,25 @@ def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: def _match_tracking(self, cache: List[dict], epsilon: float, params: List[dict], method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"]) -> bool: + """ + Perform match tracking for all channels using the specified method. + + Parameters + ---------- + cache : list of dict + Cached match criterion values for each channel. + epsilon : float + Small adjustment factor for match tracking. + params : list of dict + Parameters for each channel module. + method : Literal["MT+", "MT-", "MT0", "MT1", "MT~"] + Match tracking method to apply. + + Returns + ------- + bool + Whether to continue searching for a match across all channels. + """ keep_searching = [] for i in range(len(cache)): if cache[i]["match_criterion_bin"]: @@ -269,30 +378,44 @@ def _match_tracking(self, cache: List[dict], epsilon: float, params: List[dict], return all(keep_searching) - def _set_params(self, new_params): + def _set_params(self, new_params: List[dict]): + """ + Set the parameters for each module in FusionART. + + Parameters + ---------- + new_params : list of dict + A list of parameters for each module. + """ for i in range(self.n): self.modules[i].params = new_params[i] - def _deep_copy_params(self): + def _deep_copy_params(self) -> dict: + """ + Create a deep copy of the parameters for each module. + + Returns + ------- + dict + A dictionary with module indices as keys and their deep-copied parameters as values. + """ return {i: deepcopy(module.params) for i, module in enumerate(self.modules)} def partial_fit(self, X: np.ndarray, match_reset_func: Optional[Callable] = None, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0): """ - iteratively fit the model to the data - - Parameters: - - X: data set - - match_reset_func: a callable accepting the data sample, a cluster weight, the params dict, and the cache dict - Permits external factors to influence cluster creation. - Returns True if the cluster is valid for the sample, False otherwise - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho + Iteratively fit the model to the data. + Parameters + ---------- + X : np.ndarray + Input dataset. + match_reset_func : callable, optional + Function to reset the match criteria based on external factors. + match_reset_method : Literal["MT+", "MT-", "MT0", "MT1", "MT~"], optional + Method for resetting match criteria (default is "MT+"). + epsilon : float, optional + Value to adjust the vigilance parameter (default is 0.0). """ self.validate_data(X) @@ -313,14 +436,19 @@ def partial_fit(self, X: np.ndarray, match_reset_func: Optional[Callable] = None def step_pred(self, x, skip_channels: List[int] = []) -> int: """ - predict the label for a single sample + Predict the label for a single sample. - Parameters: - - x: data sample - - Returns: - cluster label of the input sample + Parameters + ---------- + x : np.ndarray + Input sample. + skip_channels : list of int, optional + Channels to skip (default is []). + Returns + ------- + int + Predicted cluster label for the input sample. """ assert len(self.W) >= 0, "ART module is not fit." @@ -330,14 +458,19 @@ def step_pred(self, x, skip_channels: List[int] = []) -> int: def predict(self, X: np.ndarray, skip_channels: List[int] = []) -> np.ndarray: """ - predict labels for the data + Predict labels for the input data. - Parameters: - - X: data set - - Returns: - labels for the data + Parameters + ---------- + X : np.ndarray + Input dataset. + skip_channels : list of int, optional + Channels to skip (default is []). + Returns + ------- + np.ndarray + Predicted labels for the input data. """ check_is_fitted(self) @@ -352,17 +485,23 @@ def predict(self, X: np.ndarray, skip_channels: List[int] = []) -> np.ndarray: def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: """ - get the updated cluster weight - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations + Update the cluster weight. - Returns: - updated cluster weight, cache used for later processing + Parameters + ---------- + i : np.ndarray + Input data sample. + w : np.ndarray + Cluster weight information. + params : dict + Parameters for the ART algorithm. + cache : dict, optional + Cache for previous calculations (default is None). + Returns + ------- + np.ndarray + Updated cluster weight. """ W = [ self.modules[k].update( @@ -377,16 +516,19 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ - generate a new cluster weight - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Generate a new cluster weight. - Returns: - updated cluster weight + Parameters + ---------- + i : np.ndarray + Input data sample. + params : dict + Parameters for the ART algorithm. + Returns + ------- + np.ndarray + New cluster weight. """ W = [ self.modules[k].new_weight( @@ -424,9 +566,12 @@ def set_weight(self, idx: int, new_w: np.ndarray): def get_cluster_centers(self) -> List[np.ndarray]: """ - function for getting centers of each cluster. Used for regression - Returns: - cluster centroid + Get the center points for each cluster. + + Returns + ------- + list of np.ndarray + Center points of the clusters. """ centers_ = [module.get_cluster_centers() for module in self.modules] centers = [ @@ -442,9 +587,39 @@ def get_cluster_centers(self) -> List[np.ndarray]: return centers def get_channel_centers(self, channel: int): + """ + Get the center points of clusters for a specific channel. + + Parameters + ---------- + channel : int + The channel index. + + Returns + ------- + np.ndarray + Cluster centers for the specified channel. + """ return self.modules[channel].get_cluster_centers() def predict_regression(self, X: np.ndarray, target_channels: List[int] = [-1]) -> Union[np.ndarray, List[np.ndarray]]: + """ + Predict regression values for the input data using the target channels. + + Parameters + ---------- + X : np.ndarray + Input dataset. + target_channels : list of int, optional + List of target channels to use for regression. If negative values are used, they are considered as + channels counting backward from the last channel. By default, it uses the last channel (-1). + + Returns + ------- + Union[np.ndarray, list of np.ndarray] + Predicted regression values. If only one target channel is used, returns a single np.ndarray. + If multiple target channels are used, returns a list of np.ndarray, one for each channel. + """ target_channels = [self.n+k if k < 0 else k for k in target_channels] C = self.predict(X, skip_channels=target_channels) centers = [self.get_channel_centers(k) for k in target_channels] @@ -454,6 +629,21 @@ def predict_regression(self, X: np.ndarray, target_channels: List[int] = [-1]) - return [np.array([centers[k][c] for c in C]) for k in target_channels] def join_channel_data(self, channel_data: List[np.ndarray], skip_channels: List[int] = []) -> np.ndarray: + """ + Concatenate data from different channels into a single array. + + Parameters + ---------- + channel_data : list of np.ndarray + Data from each channel. + skip_channels : list of int, optional + Channels to skip (default is []). + + Returns + ------- + np.ndarray + Concatenated data. + """ skip_channels = [self.n+k if k < 0 else k for k in skip_channels] n_samples = channel_data[0].shape[0] @@ -470,6 +660,21 @@ def join_channel_data(self, channel_data: List[np.ndarray], skip_channels: List[ return X def split_channel_data(self, joined_data: np.ndarray, skip_channels: List[int] = []) -> List[np.ndarray]: + """ + Split the concatenated data into its original channels. + + Parameters + ---------- + joined_data : np.ndarray + Concatenated data from multiple channels. + skip_channels : list of int, optional + Channels to skip (default is []). + + Returns + ------- + list of np.ndarray + Split data, one array for each channel. + """ skip_channels = [self.n + k if k < 0 else k for k in skip_channels] channel_data = [] diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index 58a51fc..fd3fecb 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -22,19 +22,21 @@ class DeepARTMAP(BaseEstimator, ClassifierMixin, ClusterMixin): and the second module is the B module. DeepARTMAP does not currently have a direct citation and is an original creation of this library. - - Parameters: - modules: An list of instatiated BaseART classes to use as layers. e.g. [FuzzyART(), HyperpshereART()]. - - """ def __init__(self, modules: list[BaseART]): """ + Initialize the DeepARTMAP model. - Parameters: - - modules: list of ART modules + Parameters + ---------- + modules : list of BaseART + A list of instantiated ART modules to use as layers in the DeepARTMAP model. + Raises + ------ + AssertionError + If no ART modules are provided. """ assert len(modules) >= 1, "Must provide at least one ART module" self.modules = modules @@ -43,13 +45,17 @@ def __init__(self, modules: list[BaseART]): def get_params(self, deep: bool = True) -> dict: """ + Get parameters for this estimator. - Parameters: - - deep: If True, will return the parameters for this class and contained subobjects that are estimators. + Parameters + ---------- + deep : bool, optional, default=True + If True, will return the parameters for this class and contained subobjects that are estimators. - Returns: + Returns + ------- + dict Parameter names mapped to their values. - """ out = dict() for i, module in enumerate(self.modules): @@ -60,15 +66,18 @@ def get_params(self, deep: bool = True) -> dict: return out def set_params(self, **params): - """Set the parameters of this estimator. - - Specific redefinition of sklearn.BaseEstimator.set_params for ARTMAP classes + """ + Set the parameters of this estimator. - Parameters: - - **params : Estimator parameters. + Parameters + ---------- + **params : dict + Estimator parameters. - Returns: - - self : estimator instance + Returns + ------- + self : DeepARTMAP + The estimator instance. """ if not params: @@ -99,11 +108,27 @@ def set_params(self, **params): return self @property - def labels_(self): + def labels_(self) -> np.ndarray: + """ + Get the labels from the first layer. + + Returns + ------- + np.ndarray + The labels from the first ART layer. + """ return self.layers[0].labels_ @property - def labels_deep_(self): + def labels_deep_(self) -> np.ndarray: + """ + Get the deep labels from all layers. + + Returns + ------- + np.ndarray + Deep labels from all ART layers concatenated together. + """ return np.concatenate( [ layer.labels_.reshape((-1, 1)) @@ -115,24 +140,44 @@ def labels_deep_(self): ) @property - def n_modules(self): + def n_modules(self) -> int: + """ + Get the number of ART modules. + + Returns + ------- + int + The number of ART modules. + """ return len(self.modules) @property - def n_layers(self): + def n_layers(self) -> int: + """ + Get the number of layers. + + Returns + ------- + int + The number of layers in DeepARTMAP. + """ return len(self.layers) def map_deep(self, level: int, y_a: Union[np.ndarray, int]) -> Union[np.ndarray, int]: """ - map a label from one arbitrary level to the highest (B) level - - Parameters: - - level: level the label is from - - y_a: the cluster label(s) - - Returns: - cluster label(s) at highest level - + Map a label from one arbitrary level to the highest (B) level. + + Parameters + ---------- + level : int + The level from which the label is taken. + y_a : np.ndarray or int + The cluster label(s) at the input level. + + Returns + ------- + np.ndarray or int + The cluster label(s) at the highest level (B). """ if level < 0: level += len(self.layers) @@ -149,12 +194,19 @@ def validate_data( y: Optional[np.ndarray] = None ): """ - validates the data prior to clustering - - Parameters: - - X: list of deep data sets - - y: optional labels for data - + Validate the data before clustering. + + Parameters + ---------- + X : list of np.ndarray + The input data sets for each module. + y : np.ndarray, optional + The corresponding labels, by default None. + + Raises + ------ + AssertionError + If the input data is inconsistent or does not match the expected format. """ assert len(X) == self.n_modules, \ f"Must provide {self.n_modules} input matrices for {self.n_modules} ART modules" @@ -166,44 +218,62 @@ def validate_data( def prepare_data(self, X: list[np.ndarray], y: Optional[np.ndarray] = None) ->Tuple[list[np.ndarray], Optional[np.ndarray]]: """ - prepare data for clustering - - Parameters: - - X: data set - - Returns: - prepared data + Prepare the data for clustering. + + Parameters + ---------- + X : list of np.ndarray + The input data set for each module. + y : np.ndarray, optional + The corresponding labels, by default None. + + Returns + ------- + tuple of (list of np.ndarray, np.ndarray) + The prepared data set and labels (if any). """ return [self.modules[i].prepare_data(X[i]) for i in range(self.n_modules)], y def restore_data(self, X: list[np.ndarray], y: Optional[np.ndarray] = None) ->Tuple[list[np.ndarray], Optional[np.ndarray]]: """ - restore data to state prior to preparation - - Parameters: - - X: data set - - Returns: - prepared data + Restore the data to its original state before preparation. + + Parameters + ---------- + X : list of np.ndarray + The input data set for each module. + y : np.ndarray, optional + The corresponding labels, by default None. + + Returns + ------- + tuple of (list of np.ndarray, np.ndarray) + The restored data set and labels (if any). """ return [self.modules[i].restore_data(X[i]) for i in range(self.n_modules)], y def fit(self, X: list[np.ndarray], y: Optional[np.ndarray] = None, max_iter=1, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0): """ - Fit the model to the data - - Parameters: - - X: list of deep datasets - - y: optional labels - - max_iter: number of iterations to fit the model on the same data set - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho - + Fit the DeepARTMAP model to the data. + + Parameters + ---------- + X : list of np.ndarray + The input data sets for each module. + y : np.ndarray, optional + The corresponding labels for supervised learning, by default None. + max_iter : int, optional + The number of iterations to fit the model, by default 1. + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, optional + The method to reset vigilance if a mismatch occurs, by default "MT+". + epsilon : float, optional + A small adjustment factor for match tracking, by default 0.0. + + Returns + ------- + DeepARTMAP + The fitted DeepARTMAP model. """ self.validate_data(X, y) if y is not None: @@ -226,18 +296,23 @@ def fit(self, X: list[np.ndarray], y: Optional[np.ndarray] = None, max_iter=1, m def partial_fit(self, X: list[np.ndarray], y: Optional[np.ndarray] = None, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0): """ - Partial fit the model to the data - - Parameters: - - X: list of deep datasets - - y: optional labels - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho - + Partially fit the DeepARTMAP model to the data. + + Parameters + ---------- + X : list of np.ndarray + The input data sets for each module. + y : np.ndarray, optional + The corresponding labels for supervised learning, by default None. + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, optional + The method to reset vigilance if a mismatch occurs, by default "MT+". + epsilon : float, optional + A small adjustment factor for match tracking, by default 0.0. + + Returns + ------- + DeepARTMAP + The partially fitted DeepARTMAP model. """ self.validate_data(X, y) if y is not None: @@ -268,14 +343,17 @@ def partial_fit(self, X: list[np.ndarray], y: Optional[np.ndarray] = None, match def predict(self, X: Union[np.ndarray, list[np.ndarray]]) -> list[np.ndarray]: """ - predict labels for the data - - Parameters: - - X: list of deep data sets + Predict the labels for the input data. - Returns: - B labels for the data + Parameters + ---------- + X : np.ndarray or list of np.ndarray + The input data set for prediction. + Returns + ------- + list of np.ndarray + The predicted labels for each layer. """ if isinstance(X, list): x = X[-1] diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index 1385597..6858f1b 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -23,22 +23,22 @@ class SMART(DeepARTMAP): vigilance values that monotonically increase in their restrictiveness. SMART is a special case of DeepARTMAP, which forms the backbone of this class, where all channels receive the same data. - - Parameters: - base_ART_class: An uninstatiated BaseART class. e.g. FuzzyART - rho_values: Union[list[float], np.ndarray] a set of monotonically increasing vigilance values - base_params: all other params used to instantiate the base ART (will be identical across all layers) - """ def __init__(self, base_ART_class: Type, rho_values: Union[list[float], np.ndarray], base_params: dict, **kwargs): """ - - Parameters: - - base_ART_class: some ART class - - rho_values: rho parameters for each sub-module - - base_params: base param dict for each sub-module - + Initialize the SMART model. + + Parameters + ---------- + base_ART_class : Type + Some ART class to instantiate the layers. + rho_values : list of float or np.ndarray + The vigilance parameter values for each layer, must be monotonically increasing for most ART modules. + base_params : dict + Parameters for the base ART module, used to instantiate each layer. + **kwargs : + Additional keyword arguments for ART module initialization. """ if base_ART_class.__name__ != "BayesianART": assert all(np.diff(rho_values) > 0), "rho_values must be monotonically increasing" @@ -54,62 +54,102 @@ def __init__(self, base_ART_class: Type, rho_values: Union[list[float], np.ndarr def prepare_data(self, X: np.ndarray) -> np.ndarray: """ - prepare data for clustering + Prepare data for clustering. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset to prepare. - Returns: - prepared data + Returns + ------- + np.ndarray + Prepared data. """ X_, _ = super(SMART, self).prepare_data([X]*self.n_modules) return X_[0] def restore_data(self, X: np.ndarray) -> np.ndarray: """ - restore data to state prior to preparation + Restore data to its original form before preparation. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset to restore. - Returns: - restored data + Returns + ------- + np.ndarray + Restored data. """ X_, _ = super(SMART, self).restore_data([X] * self.n_modules) return X_[0] def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, max_iter=1, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0): """ - Fit the model to the data - - Parameters: - - X: data set A - - y: not used - - max_iter: number of iterations to fit the model on the same data set - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho - + Fit the SMART model to the data. + + Parameters + ---------- + X : np.ndarray + The dataset to fit the model on. + y : np.ndarray, optional + Not used, present for compatibility. + max_iter : int, optional + The number of iterations to run the model on the data. + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, optional + The match reset method to use when adjusting vigilance. + epsilon : float, optional + A small value to adjust vigilance during match tracking. + + Returns + ------- + SMART + Fitted SMART model. """ X_list = [X]*self.n_modules return super().fit(X_list, max_iter=max_iter, match_reset_method=match_reset_method, epsilon=epsilon) def partial_fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0): + """ + Partial fit the SMART model to the data. + + Parameters + ---------- + X : np.ndarray + The dataset to partially fit the model on. + y : np.ndarray, optional + Not used, present for compatibility. + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, optional + The match reset method to use when adjusting vigilance. + epsilon : float, optional + A small value to adjust vigilance during match tracking. + + Returns + ------- + SMART + Partially fitted SMART model. + """ X_list = [X] * self.n_modules return super(SMART, self).partial_fit(X_list, match_reset_method=match_reset_method, epsilon=epsilon) def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ - undefined function for visualizing the bounds of each cluster - - Parameters: - - ax: figure axes - - colors: colors to use for each cluster - - linewidth: width of boundary line - + Visualize the cluster boundaries. + + Parameters + ---------- + ax : Axes + The matplotlib axes on which to plot the cluster boundaries. + colors : Iterable + The colors to use for each cluster. + linewidth : int, optional + The width of the boundary lines. + + Returns + ------- + None """ for j in range(len(self.modules)): layer_colors = [] @@ -131,16 +171,26 @@ def visualize( colors: Optional[Iterable] = None ): """ - Visualize the clustering of the data - - Parameters: - - X: data set - - y: sample labels - - ax: figure axes - - marker_size: size used for data points - - linewidth: width of boundary line - - colors: colors to use for each cluster - + Visualize the clustering of the data with cluster boundaries. + + Parameters + ---------- + X : np.ndarray + The dataset to visualize. + y : np.ndarray + The cluster labels for the data points. + ax : Axes, optional + The matplotlib axes on which to plot the visualization. + marker_size : int, optional + The size of the data points in the plot. + linewidth : int, optional + The width of the cluster boundary lines. + colors : Iterable, optional + The colors to use for each cluster. + + Returns + ------- + None """ import matplotlib.pyplot as plt diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index 0fb94f1..1ecdc8b 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -19,14 +19,6 @@ class FALCON: FALCON is based on a Fusion-ART backbone but only accepts 3 channels: State, Action, and Reward. Specific functions are implemented for getting optimal reward and action predictions. - - Parameters: - state_art: BaseART the instantiated ART module that wil cluster the state-space - action_art: BaseART the instantiated ART module that wil cluster the action-space - reward_art: BaseART the instantiated ART module that wil cluster the reward-space - gamma_values: Union[List[float], np.ndarray] the activation ratio for each channel - channel_dims: Union[List[int], np.ndarray] the dimension of each channel - """ def __init__( self, @@ -37,12 +29,20 @@ def __init__( channel_dims: Union[List[int], np.ndarray] = list[int] ): """ - Parameters: - - state_art: BaseART the instantiated ART module that wil cluster the state-space - - action_art: BaseART the instantiated ART module that wil cluster the action-space - - reward_art: BaseART the instantiated ART module that wil cluster the reward-space - - gamma_values: Union[List[float], np.ndarray] the activation ratio for each channel - - channel_dims: Union[List[int], np.ndarray] the dimension of each channel + Initialize the FALCON model. + + Parameters + ---------- + state_art : BaseART + The instantiated ART module that will cluster the state-space. + action_art : BaseART + The instantiated ART module that will cluster the action-space. + reward_art : BaseART + The instantiated ART module that will cluster the reward-space. + gamma_values : list of float or np.ndarray, optional + The activation ratio for each channel, by default [0.33, 0.33, 0.34]. + channel_dims : list of int or np.ndarray + The dimension of each channel. """ self.fusion_art = FusionART( modules=[state_art, action_art, reward_art], @@ -52,39 +52,104 @@ def __init__( def prepare_data(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ - prepare data for clustering - - Parameters: - - channel_data: list of channel arrays - - Returns: - normalized data + Prepare data for clustering. + + Parameters + ---------- + states : np.ndarray + The state data. + actions : np.ndarray + The action data. + rewards : np.ndarray + The reward data. + + Returns + ------- + tuple of np.ndarray + Normalized state, action, and reward data. """ return self.fusion_art.modules[0].prepare_data(states), self.fusion_art.modules[1].prepare_data(actions), self.fusion_art.modules[2].prepare_data(rewards) def restore_data(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ - restore data to state prior to preparation - - Parameters: - - X: data set - - Returns: - restored data + Restore data to its original form before preparation. + + Parameters + ---------- + states : np.ndarray + The state data. + actions : np.ndarray + The action data. + rewards : np.ndarray + The reward data. + + Returns + ------- + tuple of np.ndarray + Restored state, action, and reward data. """ return self.fusion_art.modules[0].restore_data(states), self.fusion_art.modules[1].restore_data(actions), self.fusion_art.modules[2].restore_data(rewards) def fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray): + """ + Fit the FALCON model to the data. + + Parameters + ---------- + states : np.ndarray + The state data. + actions : np.ndarray + The action data. + rewards : np.ndarray + The reward data. + + Returns + ------- + FALCON + The fitted FALCON model. + """ data = self.fusion_art.join_channel_data([states, actions, rewards]) self.fusion_art = self.fusion_art.fit(data) return self def partial_fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray): + """ + Partially fit the FALCON model to the data. + + Parameters + ---------- + states : np.ndarray + The state data. + actions : np.ndarray + The action data. + rewards : np.ndarray + The reward data. + + Returns + ------- + FALCON + The partially fitted FALCON model. + """ data = self.fusion_art.join_channel_data([states, actions, rewards]) self.fusion_art = self.fusion_art.partial_fit(data) return self def get_actions_and_rewards(self, state: np.ndarray, action_space: Optional[np.ndarray] = None) -> Tuple[np.ndarray, np.ndarray]: + """ + Get possible actions and their associated rewards for a given state. + + Parameters + ---------- + state : np.ndarray + The current state. + action_space : np.ndarray, optional + The available action space, by default None. + + Returns + ------- + tuple of np.ndarray + The possible actions and their corresponding rewards. + """ reward_centers = self.fusion_art.get_channel_centers(2) if action_space is None: action_space = self.fusion_art.get_channel_centers(1) @@ -102,6 +167,23 @@ def get_actions_and_rewards(self, state: np.ndarray, action_space: Optional[np.n def get_action(self, state: np.ndarray, action_space: Optional[np.ndarray] = None, optimality: Literal["min", "max"] = "max") -> np.ndarray: + """ + Get the best action for a given state based on optimality. + + Parameters + ---------- + state : np.ndarray + The current state. + action_space : np.ndarray, optional + The available action space, by default None. + optimality : {"min", "max"}, optional + Whether to choose the action with the minimum or maximum reward, by default "max". + + Returns + ------- + np.ndarray + The optimal action. + """ action_space, rewards = self.get_actions_and_rewards(state, action_space) if optimality == "max": c_winner = np.argmax(rewards) @@ -110,6 +192,25 @@ def get_action(self, state: np.ndarray, action_space: Optional[np.ndarray] = Non return action_space[c_winner] def get_probabilistic_action(self, state: np.ndarray, action_space: Optional[np.ndarray] = None, offset: float = 0.1, optimality: Literal["min", "max"] = "max") -> np.ndarray: + """ + Get a probabilistic action for a given state based on reward distribution. + + Parameters + ---------- + state : np.ndarray + The current state. + action_space : np.ndarray, optional + The available action space, by default None. + offset : float, optional + The reward offset to adjust probability distribution, by default 0.1. + optimality : {"min", "max"}, optional + Whether to prefer minimum or maximum rewards, by default "max". + + Returns + ------- + np.ndarray + The chosen action based on probability. + """ action_space, rewards = self.get_actions_and_rewards(state, action_space) action_indices = np.array(range(len(action_space))) @@ -128,6 +229,21 @@ def get_probabilistic_action(self, state: np.ndarray, action_space: Optional[np. return action_space[a_i[0]][0] def get_rewards(self, states: np.ndarray, actions: np.ndarray) -> np.ndarray: + """ + Get the rewards for given states and actions. + + Parameters + ---------- + states : np.ndarray + The state data. + actions : np.ndarray + The action data. + + Returns + ------- + np.ndarray + The rewards corresponding to the given state-action pairs. + """ reward_centers = self.fusion_art.get_channel_centers(2) data = self.fusion_art.join_channel_data([states, actions], skip_channels=[2]) C = self.fusion_art.predict(data, skip_channels=[2]) diff --git a/artlib/reinforcement/TDFALCON.py b/artlib/reinforcement/TDFALCON.py index 666c773..1cf78ef 100644 --- a/artlib/reinforcement/TDFALCON.py +++ b/artlib/reinforcement/TDFALCON.py @@ -20,16 +20,6 @@ class TD_FALCON(FALCON): TD-FALCON is based on a FALCON backbone but includes specific function for temporal-difference learning. Currently, only SARSA is implemented and only Fuzzy ART base modules are supported. - - Parameters: - state_art: BaseART the instantiated ART module that wil cluster the state-space - action_art: BaseART the instantiated ART module that wil cluster the action-space - reward_art: BaseART the instantiated ART module that wil cluster the reward-space - gamma_values: Union[List[float], np.ndarray] the activation ratio for each channel - channel_dims: Union[List[int], np.ndarray] the dimension of each channel - td_alpha: float the learning rate for the temporal difference estimator - td_lambda: float the future-cost factor - """ def __init__( @@ -43,23 +33,60 @@ def __init__( td_lambda: float = 1.0, ): """ - Parameters: - - state_art: BaseART the instantiated ART module that wil cluster the state-space - - action_art: BaseART the instantiated ART module that wil cluster the action-space - - reward_art: BaseART the instantiated ART module that wil cluster the reward-space - - gamma_values: Union[List[float], np.ndarray] the activation ratio for each channel - - channel_dims: Union[List[int], np.ndarray] the dimension of each channel - - td_alpha: float the learning rate for the temporal difference estimator - - td_lambda: float the future-cost factor + Initialize the TD-FALCON model. + + Parameters + ---------- + state_art : BaseART + The instantiated ART module that will cluster the state-space. + action_art : BaseART + The instantiated ART module that will cluster the action-space. + reward_art : BaseART + The instantiated ART module that will cluster the reward-space. + gamma_values : list of float or np.ndarray, optional + The activation ratio for each channel, by default [0.33, 0.33, 0.34]. + channel_dims : list of int or np.ndarray + The dimension of each channel. + td_alpha : float, optional + The learning rate for the temporal difference estimator, by default 1.0. + td_lambda : float, optional + The future-cost factor for temporal difference learning, by default 1.0. """ self.td_alpha = td_alpha self.td_lambda = td_lambda super(TD_FALCON, self).__init__(state_art, action_art, reward_art, gamma_values, channel_dims) def fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray): + """ + Fit the TD-FALCON model to the data. + + Raises + ------ + NotImplementedError + TD-FALCON can only be trained with partial fit. + """ raise NotImplementedError("TD-FALCON can only be trained with partial fit") def calculate_SARSA(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray, single_sample_reward: Optional[float] = None): + """ + Calculate the SARSA values for reinforcement learning. + + Parameters + ---------- + states : np.ndarray + The state data. + actions : np.ndarray + The action data. + rewards : np.ndarray + The reward data. + single_sample_reward : float, optional + The reward for a single sample, if applicable, by default None. + + Returns + ------- + tuple of np.ndarray + The state, action, and SARSA-adjusted reward data to be used for fitting. + """ # calculate SARSA values rewards_dcc = de_compliment_code(rewards) if len(states) > 1: @@ -91,7 +118,25 @@ def calculate_SARSA(self, states: np.ndarray, actions: np.ndarray, rewards: np.n return states_fit, actions_fit, sarsa_rewards_fit def partial_fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray, single_sample_reward: Optional[float] = None): + """ + Partially fit the TD-FALCON model using SARSA. + + Parameters + ---------- + states : np.ndarray + The state data. + actions : np.ndarray + The action data. + rewards : np.ndarray + The reward data. + single_sample_reward : float, optional + The reward for a single sample, if applicable, by default None. + Returns + ------- + TD_FALCON + The partially fitted TD-FALCON model. + """ states_fit, actions_fit, sarsa_rewards_fit = self.calculate_SARSA(states, actions, rewards, single_sample_reward) data = self.fusion_art.join_channel_data([states_fit, actions_fit, sarsa_rewards_fit]) self.fusion_art = self.fusion_art.partial_fit(data) diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index e397a1a..7dd7955 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -4,7 +4,7 @@ Neural Networks, 4, 565 – 588. doi:10.1016/0893-6080(91)90012-T. """ import numpy as np -from typing import Literal, Tuple +from typing import Literal, Tuple, Dict from artlib.common.BaseART import BaseART from artlib.supervised.SimpleARTMAP import SimpleARTMAP from sklearn.utils.validation import check_is_fitted @@ -24,31 +24,34 @@ class ARTMAP(SimpleARTMAP): ARTMAP also provides the ability to fit a regression model to data and specific functions have been implemented to allow this. However, FusionART provides substantially better fit for regression problems which are not monotonic. - Parameters: - module_a: The instantiated ART module used for clustering the independent channel - module_b: The instantiated ART module used for clustering the dependent channel - """ def __init__(self, module_a: BaseART, module_b: BaseART): """ - - Parameters: - - module_a: a-side ART module - - module_b: b-side ART module - + Initialize the ARTMAP model with two ART modules. + + Parameters + ---------- + module_a : BaseART + A-side ART module for clustering the independent channel. + module_b : BaseART + B-side ART module for clustering the dependent channel. """ self.module_b = module_b super(ARTMAP, self).__init__(module_a) def get_params(self, deep: bool = True) -> dict: """ + Get the parameters of the ARTMAP model. - Parameters: - - deep: If True, will return the parameters for this class and contained subobjects that are estimators. + Parameters + ---------- + deep : bool, optional + If True, will return the parameters for this class and contained subobjects that are estimators. - Returns: + Returns + ------- + dict Parameter names mapped to their values. - """ out = { "module_a": self.module_a, @@ -65,68 +68,112 @@ def get_params(self, deep: bool = True) -> dict: @property - def labels_a(self): + def labels_a(self) -> np.ndarray: + """ + Get the labels generated by the A-side ART module. + + Returns + ------- + np.ndarray + Labels for the A-side data (independent channel). + """ return self.module_a.labels_ @property - def labels_b(self): + def labels_b(self) -> np.ndarray: + """ + Get the labels generated by the B-side ART module. + + Returns + ------- + np.ndarray + Labels for the B-side data (dependent channel). + """ return self.module_b.labels_ @property - def labels_ab(self): + def labels_ab(self) -> Dict[str, np.ndarray]: + """ + Get the labels generated by both the A-side and B-side ART modules. + + Returns + ------- + dict + Dictionary containing both A-side and B-side labels. + """ return {"A": self.labels_a, "B": self.module_b.labels_} def validate_data(self, X: np.ndarray, y: np.ndarray): """ - validates the data prior to clustering - - Parameters: - - X: data set A - - y: data set B - + Validate the input data prior to clustering. + + Parameters + ---------- + X : np.ndarray + Data set A (independent channel). + y : np.ndarray + Data set B (dependent channel). """ self.module_a.validate_data(X) self.module_b.validate_data(y) def prepare_data(self, X: np.ndarray, y: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: """ - prepare data for clustering - - Parameters: - - X: data set - - Returns: - normalized data + Prepare data for clustering by normalizing and transforming. + + Parameters + ---------- + X : np.ndarray + Data set A (independent channel). + y : np.ndarray + Data set B (dependent channel). + + Returns + ------- + tuple of np.ndarray + Normalized data for both channels. """ return self.module_a.prepare_data(X), self.module_b.prepare_data(y) def restore_data(self, X: np.ndarray, y: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: """ - restore data to state prior to preparation - - Parameters: - - X: data set - - Returns: - restored data + Restore data to its original state before preparation. + + Parameters + ---------- + X : np.ndarray + Data set A (independent channel). + y : np.ndarray + Data set B (dependent channel). + + Returns + ------- + tuple of np.ndarray + Restored data for both channels. """ return self.module_a.restore_data(X), self.module_b.restore_data(y) def fit(self, X: np.ndarray, y: np.ndarray, max_iter=1, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10): """ - Fit the model to the data - - Parameters: - - X: data set A - - y: data set B - - max_iter: number of iterations to fit the model on the same data set - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho - + Fit the ARTMAP model to the data. + + Parameters + ---------- + X : np.ndarray + Data set A (independent channel). + y : np.ndarray + Data set B (dependent channel). + max_iter : int, optional + Number of iterations to fit the model on the same data set. + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, optional + Method for resetting the vigilance parameter when match criterion fails. + epsilon : float, optional + Small increment to modify the vigilance parameter. + + Returns + ------- + self : ARTMAP + Fitted ARTMAP model. """ # Check that X and y have correct shape self.validate_data(X, y) @@ -142,18 +189,23 @@ def fit(self, X: np.ndarray, y: np.ndarray, max_iter=1, match_reset_method: Lite def partial_fit(self, X: np.ndarray, y: np.ndarray, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10): """ - Partial fit the model to the data - - Parameters: - - X: data set A - - y: data set B - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho - + Partially fit the ARTMAP model to the data. + + Parameters + ---------- + X : np.ndarray + Data set A (independent channel). + y : np.ndarray + Data set B (dependent channel). + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, optional + Method for resetting the vigilance parameter when match criterion fails. + epsilon : float, optional + Small increment to modify the vigilance parameter. + + Returns + ------- + self : ARTMAP + Partially fitted ARTMAP model. """ self.validate_data(X, y) self.module_b.partial_fit(y, match_reset_method=match_reset_method, epsilon=epsilon) @@ -163,43 +215,52 @@ def partial_fit(self, X: np.ndarray, y: np.ndarray, match_reset_method: Literal[ def predict(self, X: np.ndarray) -> np.ndarray: """ - predict labels for the data + Predict the labels for the given data. - Parameters: - - X: data set A - - Returns: - B labels for the data + Parameters + ---------- + X : np.ndarray + Data set A (independent channel). + Returns + ------- + np.ndarray + Predicted labels for data set B (dependent channel). """ check_is_fitted(self) return super(ARTMAP, self).predict(X) def predict_ab(self, X: np.ndarray) -> tuple[np.ndarray, np.ndarray]: """ - predict labels for the data, both A-side and B-side - - Parameters: - - X: data set A + Predict both A-side and B-side labels for the given data. - Returns: - A labels for the data, B labels for the data + Parameters + ---------- + X : np.ndarray + Data set A (independent channel). + Returns + ------- + tuple of np.ndarray + A labels and B labels for the data. """ check_is_fitted(self) return super(ARTMAP, self).predict_ab(X) def predict_regression(self, X: np.ndarray) -> np.ndarray: """ - predict values for the data - ARTMAP is not recommended for regression. Use FusionART instead. - - Parameters: - - X: data set A - - Returns: - predicted values using cluster centers - + Predict values for the given data using cluster centers. + Note: ARTMAP is not recommended for regression. Use FusionART for regression tasks. + + Parameters + ---------- + X : np.ndarray + Data set A (independent channel). + + Returns + ------- + np.ndarray + Predicted values using cluster centers. """ check_is_fitted(self) C = self.predict(X) diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index 1216899..2cb7c2d 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -4,7 +4,7 @@ Norwell, MA, USA: Kluwer Academic Publishers. """ import numpy as np -from typing import Optional, Iterable, Literal +from typing import Optional, Iterable, Literal, Dict from matplotlib.axes import Axes from artlib.common.BaseART import BaseART from artlib.common.BaseARTMAP import BaseARTMAP @@ -25,15 +25,16 @@ class SimpleARTMAP(BaseARTMAP): when the many-to-one mapping is violated. This enables SimpleARTMAP to identify discrete clusters belonging to each category label. - Parameters: - module_a: The instantiated ART module used for clustering the independent channel - """ def __init__(self, module_a: BaseART): """ - Parameters: - - module_a: The instantiated ART module used for clustering the independent channel + Initialize SimpleARTMAP. + + Parameters + ---------- + module_a : BaseART + The instantiated ART module used for clustering the independent channel. """ self.module_a = module_a super().__init__() @@ -51,17 +52,25 @@ def match_reset_func( """ Permits external factors to influence cluster creation. - Parameters: - - i: data sample - - w: cluster weight / info - - cluster_a: a-side cluster label - - params: dict containing parameters for the algorithm - - extra: additional parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - true if match is permitted - + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight / info. + cluster_a : int + A-side cluster label. + params : dict + Parameters for the algorithm. + extra : dict + Additional parameters, including "cluster_b". + cache : dict, optional + Values cached from previous calculations. + + Returns + ------- + bool + True if the match is permitted, False otherwise. """ cluster_b = extra["cluster_b"] if cluster_a in self.map and self.map[cluster_a] != cluster_b: @@ -70,13 +79,17 @@ def match_reset_func( def get_params(self, deep: bool = True) -> dict: """ + Get parameters of the model. - Parameters: - - deep: If True, will return the parameters for this class and contained subobjects that are estimators. + Parameters + ---------- + deep : bool, default=True + If True, will return the parameters for this class and contained subobjects that are estimators. - Returns: + Returns + ------- + dict Parameter names mapped to their values. - """ out = {"module_a": self.module_a} if deep: @@ -87,12 +100,19 @@ def get_params(self, deep: bool = True) -> dict: def validate_data(self, X: np.ndarray, y: np.ndarray) -> tuple[np.ndarray, np.ndarray]: """ - validates the data prior to clustering - - Parameters: - - X: data set A - - y: data set B - + Validate data prior to clustering. + + Parameters + ---------- + X : np.ndarray + Data set A. + y : np.ndarray + Data set B. + + Returns + ------- + tuple[np.ndarray, np.ndarray] + The validated datasets X and y. """ X, y = check_X_y(X, y, dtype=None) self.module_a.validate_data(X) @@ -100,45 +120,55 @@ def validate_data(self, X: np.ndarray, y: np.ndarray) -> tuple[np.ndarray, np.nd def prepare_data(self, X: np.ndarray) -> np.ndarray: """ - prepare data for clustering + Prepare data for clustering. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + Data set. - Returns: - prepared data + Returns + ------- + np.ndarray + Prepared data. """ return self.module_a.prepare_data(X) def restore_data(self, X: np.ndarray) -> np.ndarray: """ - restore data to state prior to preparation + Restore data to state prior to preparation. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + Data set. - Returns: - restored data + Returns + ------- + np.ndarray + Restored data. """ return self.module_a.restore_data(X) def step_fit(self, x: np.ndarray, c_b: int, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10) -> int: """ - Fit the model to a single sample - - Parameters: - - x: data sample for side A - - c_b: side b label - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho - - Returns: - side A cluster label - + Fit the model to a single sample. + + Parameters + ---------- + x : np.ndarray + Data sample for side A. + c_b : int + Side B label. + match_reset_method : Literal, default="MT+" + Method to reset the match. + epsilon : float, default=1e-10 + Small value to adjust the vigilance. + + Returns + ------- + int + Side A cluster label. """ match_reset_func = lambda i, w, cluster, params, cache: self.match_reset_func( i, w, cluster, params=params, extra={"cluster_b": c_b}, cache=cache @@ -152,19 +182,27 @@ def step_fit(self, x: np.ndarray, c_b: int, match_reset_method: Literal["MT+", " def fit(self, X: np.ndarray, y: np.ndarray, max_iter=1, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10, verbose: bool = False): """ - Fit the model to the data - - Parameters: - - X: data set A - - y: data set B - - max_iter: number of iterations to fit the model on the same data set - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho - + Fit the model to the data. + + Parameters + ---------- + X : np.ndarray + Data set A. + y : np.ndarray + Data set B. + max_iter : int, default=1 + Number of iterations to fit the model on the same data set. + match_reset_method : Literal, default="MT+" + Method to reset the match. + epsilon : float, default=1e-10 + Small value to adjust the vigilance. + verbose : bool, default=False + If True, displays a progress bar during training. + + Returns + ------- + self : SimpleARTMAP + The fitted model. """ # Check that X and y have correct shape SimpleARTMAP.validate_data(self, X, y) @@ -190,18 +228,23 @@ def fit(self, X: np.ndarray, y: np.ndarray, max_iter=1, match_reset_method: Lite def partial_fit(self, X: np.ndarray, y: np.ndarray, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10): """ - Partial fit the model to the data - - Parameters: - - X: data set A - - y: data set B - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho - + Partial fit the model to the data. + + Parameters + ---------- + X : np.ndarray + Data set A. + y : np.ndarray + Data set B. + match_reset_method : Literal, default="MT+" + Method to reset the match. + epsilon : float, default=1e-10 + Small value to adjust the vigilance. + + Returns + ------- + self : SimpleARTMAP + The partially fitted model. """ SimpleARTMAP.validate_data(self, X, y) if not hasattr(self, 'labels_'): @@ -222,39 +265,90 @@ def partial_fit(self, X: np.ndarray, y: np.ndarray, match_reset_method: Literal[ return self @property - def labels_a(self): + def labels_a(self) -> np.ndarray: + """ + Get labels from side A (module A). + + Returns + ------- + np.ndarray + Labels from module A. + """ return self.module_a.labels_ @property - def labels_b(self): + def labels_b(self) -> np.ndarray: + """ + Get labels from side B. + + Returns + ------- + np.ndarray + Labels from side B. + """ return self.labels_ @property - def labels_ab(self): + def labels_ab(self) -> Dict[str, np.ndarray]: + """ + Get labels from both A-side and B-side. + + Returns + ------- + dict + A dictionary with keys "A" and "B" containing labels from sides A and B, respectively. + """ return {"A": self.labels_a, "B": self.labels_} @property - def n_clusters(self): + def n_clusters(self) -> int: + """ + Get the number of clusters in side A. + + Returns + ------- + int + Number of clusters. + """ return self.module_a.n_clusters @property - def n_clusters_a(self): + def n_clusters_a(self) -> int: + """ + Get the number of clusters in side A. + + Returns + ------- + int + Number of clusters in side A. + """ return self.n_clusters @property - def n_clusters_b(self): + def n_clusters_b(self) -> int: + """ + Get the number of clusters in side B. + + Returns + ------- + int + Number of clusters in side B. + """ return len(set(c for c in self.map.values())) def step_pred(self, x: np.ndarray) -> tuple[int, int]: """ - Predict the label for a single sample - - Parameters: - - x: data sample for side A + Predict the label for a single sample. - Returns: - side A cluster label, side B cluster label + Parameters + ---------- + x : np.ndarray + Data sample for side A. + Returns + ------- + tuple[int, int] + Side A cluster label, side B cluster label. """ c_a = self.module_a.step_pred(x) c_b = self.map[c_a] @@ -262,14 +356,17 @@ def step_pred(self, x: np.ndarray) -> tuple[int, int]: def predict(self, X: np.ndarray) -> np.ndarray: """ - predict labels for the data + Predict labels for the data. - Parameters: - - X: data set A - - Returns: - B labels for the data + Parameters + ---------- + X : np.ndarray + Data set A. + Returns + ------- + np.ndarray + B labels for the data. """ check_is_fitted(self) y_b = np.zeros((X.shape[0],), dtype=int) @@ -280,14 +377,17 @@ def predict(self, X: np.ndarray) -> np.ndarray: def predict_ab(self, X: np.ndarray) -> tuple[np.ndarray, np.ndarray]: """ - predict labels for the data, both A-side and B-side - - Parameters: - - X: data set A + Predict labels for the data, both A-side and B-side. - Returns: - A labels for the data, B labels for the data + Parameters + ---------- + X : np.ndarray + Data set A. + Returns + ------- + tuple[np.ndarray, np.ndarray] + A labels for the data, B labels for the data. """ check_is_fitted(self) y_a = np.zeros((X.shape[0],), dtype=int) @@ -300,13 +400,16 @@ def predict_ab(self, X: np.ndarray) -> tuple[np.ndarray, np.ndarray]: def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ - undefined function for visualizing the bounds of each cluster - - Parameters: - - ax: figure axes - - colors: colors to use for each cluster - - linewidth: width of boundary line - + Visualize the cluster boundaries. + + Parameters + ---------- + ax : Axes + Figure axes. + colors : Iterable + Colors to use for each cluster. + linewidth : int, default=1 + Width of boundary lines. """ colors_a = [] for k_a in range(self.n_clusters): @@ -323,16 +426,22 @@ def visualize( colors: Optional[Iterable] = None ): """ - Visualize the clustering of the data - - Parameters: - - X: data set - - y: sample labels - - ax: figure axes - - marker_size: size used for data points - - linewidth: width of boundary line - - colors: colors to use for each cluster - + Visualize the clustering of the data. + + Parameters + ---------- + X : np.ndarray + Data set. + y : np.ndarray + Sample labels. + ax : Optional[Axes], default=None + Figure axes. + marker_size : int, default=10 + Size used for data points. + linewidth : int, default=1 + Width of boundary lines. + colors : Optional[Iterable], default=None + Colors to use for each cluster. """ import matplotlib.pyplot as plt diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index cc3dded..98ffd4f 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -31,22 +31,22 @@ class TopoART(BaseART): the first and second resonant cluster relationships in an adjacency matrix. Further, it updates the second resonant cluster with a lower learning rate than the first, providing for a distributed learning model. - - Parameters: - base_module: an instantiated ART module - beta_lower: the learning rate for the second resonant cluster - tau: number of samples after which we prune - phi: minimum number of samples a cluster must have association with to be kept - """ def __init__(self, base_module: BaseART, beta_lower: float, tau: int, phi: int): """ - Parameters: - - base_module: an instantiated ART module - - beta_lower: the learning rate for the second resonant cluster - - tau: number of samples after which we prune - - phi: minimum number of samples a cluster must have association with to be kept + Initialize TopoART. + + Parameters + ---------- + base_module : BaseART + An instantiated ART module. + beta_lower : float + The learning rate for the second resonant cluster. + tau : int + Number of samples after which clusters are pruned. + phi : int + Minimum number of samples a cluster must be associated with to be kept. """ assert isinstance(base_module, BaseART) if hasattr(base_module, "base_module"): @@ -60,14 +60,21 @@ def __init__(self, base_module: BaseART, beta_lower: float, tau: int, phi: int): self.adjacency = np.zeros([], dtype=int) self._permanent_mask = np.zeros([], dtype=bool) + @staticmethod def validate_params(params: dict): """ - validate clustering parameters + Validate clustering parameters. - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + A dictionary containing parameters for the algorithm. + Raises + ------ + AssertionError + If the required parameters are not provided or are invalid. """ assert "beta" in params, "TopoART is only compatible with ART modules relying on 'beta' for learning." assert "beta_lower" in params @@ -80,125 +87,188 @@ def validate_params(params: dict): assert isinstance(params["tau"], int) assert isinstance(params["phi"], int) + @property - def W(self): + def W(self) -> List[np.ndarray]: + """ + Get the weight matrix of the base module. + + Returns + ------- + list[np.ndarray] + The weight matrix of the base ART module. + """ return self.base_module.W + @W.setter def W(self, new_W: list[np.ndarray]): + """ + Set the weight matrix of the base module. + + Parameters + ---------- + new_W : list[np.ndarray] + The new weight matrix. + """ self.base_module.W = new_W + def validate_data(self, X: np.ndarray): """ - validates the data prior to clustering - - Parameters: - - X: data set + Validate the data prior to clustering. + Parameters + ---------- + X : np.ndarray + The input dataset. """ self.base_module.validate_data(X) + def prepare_data(self, X: np.ndarray) -> np.ndarray: """ - prepare data for clustering + Prepare data for clustering. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The input dataset. - Returns: - normalized data + Returns + ------- + np.ndarray + Prepared (normalized) data. """ return self.base_module.prepare_data(X) + def restore_data(self, X: np.ndarray) -> np.ndarray: """ - restore data to state prior to preparation + Restore data to the state prior to preparation. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The input dataset. - Returns: - restored data + Returns + ------- + np.ndarray + Restored data. """ return self.base_module.restore_data(X) + def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: """ - get the activation of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - Returns: - cluster activation, cache used for later processing - + Get the activation of the cluster. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Parameters for the algorithm. + + Returns + ------- + tuple[float, Optional[dict]] + Cluster activation and cache used for later processing. """ return self.base_module.category_choice(i, w, params) + def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: """ - get the match criterion of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - cluster match criterion, cache used for later processing - + Get the match criterion of the cluster. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Parameters for the algorithm. + cache : dict, optional + Values cached from previous calculations. + + Returns + ------- + tuple[float, dict] + Cluster match criterion and cache used for later processing. """ return self.base_module.match_criterion(i, w, params, cache) + def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None, op: Callable = operator.ge) -> tuple[bool, dict]: """ - get the binary match criterion of the cluster - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - cluster match criterion binary, cache used for later processing - + Get the binary match criterion of the cluster. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Parameters for the algorithm. + cache : dict, optional + Values cached from previous calculations. + op : Callable, default=operator.ge + Comparison operator to use for the binary match criterion. + + Returns + ------- + tuple[bool, dict] + Binary match criterion and cache used for later processing. """ return self.base_module.match_criterion_bin(i, w, params, cache, op) + def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: """ - get the updated cluster weight - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations - - Returns: - updated cluster weight, cache used for later processing - + Update the cluster weight. + + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Parameters for the algorithm. + cache : dict, optional + Values cached from previous calculations. + + Returns + ------- + np.ndarray + Updated cluster weight. """ if cache.get("resonant_c", -1) >= 0: self.adjacency[cache["resonant_c"], cache["current_c"]] += 1 return self.base_module.update(i, w, params, cache) + def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ - generate a new cluster weight - - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - Returns: - updated cluster weight - + Generate a new cluster weight. + + Parameters + ---------- + i : np.ndarray + Data sample. + params : dict + Parameters for the algorithm. + + Returns + ------- + np.ndarray + Newly generated cluster weight. """ return self.base_module.new_weight(i, params) @@ -206,11 +276,12 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: def add_weight(self, new_w: np.ndarray): """ - add a new cluster weight - - Parameters: - - new_w: new cluster weight to add + Add a new cluster weight. + Parameters + ---------- + new_w : np.ndarray + New cluster weight to add. """ if len(self.W) == 0: self.adjacency = np.zeros((1, 1)) @@ -222,6 +293,14 @@ def add_weight(self, new_w: np.ndarray): def prune(self, X: np.ndarray): + """ + Prune clusters based on the number of associated samples. + + Parameters + ---------- + X : np.ndarray + The input dataset. + """ a = np.array(self.weight_sample_counter_).reshape(-1,) >= self.phi b = self._permanent_mask print(a.shape, b.shape) @@ -249,18 +328,40 @@ def prune(self, X: np.ndarray): else: self.labels_[i] = -1 + def post_step_fit(self, X: np.ndarray): """ - Function called after each sample fit. Used for cluster pruning - - Parameters: - - X: data set + Perform post-fit operations, such as cluster pruning, after fitting each sample. + Parameters + ---------- + X : np.ndarray + The input dataset. """ if self.sample_counter_ > 0 and self.sample_counter_ % self.tau == 0: self.prune(X) + def _match_tracking(self, cache: dict, epsilon: float, params: dict, method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"]) -> bool: + """ + Adjust the vigilance parameter based on match tracking methods. + + Parameters + ---------- + cache : dict + Cached values from previous calculations. + epsilon : float + Adjustment factor for the vigilance parameter. + params : dict + Parameters for the algorithm. + method : Literal["MT+", "MT-", "MT0", "MT1", "MT~"] + Method to use for match tracking. + + Returns + ------- + bool + True if the match tracking continues, False otherwise. + """ M = cache["match_criterion"] if method == "MT+": self.base_module.params["rho"] = M+epsilon @@ -279,32 +380,50 @@ def _match_tracking(self, cache: dict, epsilon: float, params: dict, method: Lit else: raise ValueError(f"Invalid Match Tracking Method: {method}") + def _set_params(self, new_params): + """ + Set new parameters for the base module. + + Parameters + ---------- + new_params : dict + New parameters to set. + """ self.base_module.params = new_params - def _deep_copy_params(self) -> dict: - return deepcopy(self.base_module.params) - def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0) -> int: + def _deep_copy_params(self) -> dict: """ - fit the model to a single sample - - Parameters: - - x: data sample - - match_reset_func: a callable accepting the data sample, a cluster weight, the params dict, and the cache dict - Permits external factors to influence cluster creation. - Returns True if the cluster is valid for the sample, False otherwise - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho + Create a deep copy of the parameters. + Returns + ------- + dict + Deep copy of the parameters. + """ + return deepcopy(self.base_module.params) - Returns: - cluster label of the input sample + def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0) -> int: + """ + Fit the model to a single sample. + + Parameters + ---------- + x : np.ndarray + Data sample. + match_reset_func : Callable, optional + Function to reset the match based on custom criteria. + match_reset_method : Literal["MT+", "MT-", "MT0", "MT1", "MT~"], default="MT+" + Method to reset the match. + epsilon : float, default=0.0 + Adjustment factor for vigilance. + + Returns + ------- + int + Cluster label of the input sample. """ base_params = self._deep_copy_params() mt_operator = self._match_tracking_operator(match_reset_method) @@ -367,21 +486,28 @@ def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None, m def get_cluster_centers(self) -> List[np.ndarray]: """ - function for getting centers of each cluster. Used for regression - Returns: - cluster centroid + Get the centers of each cluster. + + Returns + ------- + List[np.ndarray] + Cluster centroids. """ return self.base_module.get_cluster_centers() + def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ - undefined function for visualizing the bounds of each cluster - - Parameters: - - ax: figure axes - - colors: colors to use for each cluster - - linewidth: width of boundary line - + Visualize the boundaries of each cluster. + + Parameters + ---------- + ax : Axes + Figure axes. + colors : Iterable + Colors to use for each cluster. + linewidth : int, default=1 + Width of boundary lines. """ try: self.base_module.plot_cluster_bounds(ax=ax, colors=colors, linewidth=linewidth) From 629db581f985bb6de5cfc9060150cd62525d0443 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Tue, 15 Oct 2024 18:05:15 -0500 Subject: [PATCH 4/8] put readme on index --- README.md | 6 +- artlib/common/BaseART.py | 409 +++++++++++------- artlib/common/BaseARTMAP.py | 147 ++++--- artlib/common/VAT.py | 24 +- artlib/common/utils.py | 127 ++++-- artlib/common/visualization.py | 81 ++-- .../img}/comparison_of_elementary_methods.jpg | Bin docs/source/artlib.biclustering.rst | 21 + docs/source/artlib.common.rst | 53 +++ docs/source/artlib.cvi.iCVIs.rst | 21 + docs/source/artlib.cvi.rst | 29 ++ docs/source/artlib.elementary.rst | 85 ++++ docs/source/artlib.experimental.rst | 37 ++ docs/source/artlib.fusion.rst | 21 + docs/source/artlib.hierarchical.rst | 29 ++ docs/source/artlib.reinforcement.rst | 29 ++ docs/source/artlib.rst | 28 ++ docs/source/artlib.supervised.rst | 29 ++ docs/source/artlib.topological.rst | 21 + docs/source/conf.py | 4 +- docs/source/index.rst | 6 +- docs/source/modules.rst | 7 + pyproject.toml | 1 + 23 files changed, 919 insertions(+), 296 deletions(-) rename {img => docs/_static/img}/comparison_of_elementary_methods.jpg (100%) create mode 100644 docs/source/artlib.biclustering.rst create mode 100644 docs/source/artlib.common.rst create mode 100644 docs/source/artlib.cvi.iCVIs.rst create mode 100644 docs/source/artlib.cvi.rst create mode 100644 docs/source/artlib.elementary.rst create mode 100644 docs/source/artlib.experimental.rst create mode 100644 docs/source/artlib.fusion.rst create mode 100644 docs/source/artlib.hierarchical.rst create mode 100644 docs/source/artlib.reinforcement.rst create mode 100644 docs/source/artlib.rst create mode 100644 docs/source/artlib.supervised.rst create mode 100644 docs/source/artlib.topological.rst create mode 100644 docs/source/modules.rst diff --git a/README.md b/README.md index 228a2e3..9d60629 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ AdaptiveResonanceLib includes implementations for the following ART models: - Fuzzy ART - Quadratic Neuron ART - Dual Vigilance ART - - Gram ART - #### Metric Informed - CVI ART - iCVI Fuzzy ART @@ -36,12 +35,13 @@ AdaptiveResonanceLib includes implementations for the following ART models: - #### Reinforcement Learning - FALCON - TD-FALCON - - #### Biclustering - Biclustering ARTMAP ## Comparison of Elementary Models -![Comparison of Elementary Images](./img/comparison_of_elementary_methods.jpg?raw=true") + +[comment]: <> (![Comparison of Elementary Images](https://github.com/NiklasMelton/AdaptiveResonanceLib/raw/main/docs/_static/comparison_of_elementary_methods.jpg?raw=true")) +![Comparison of Elementary Images](https://github.com/NiklasMelton/AdaptiveResonanceLib/raw/main/img/comparison_of_elementary_methods.jpg?raw=true") ## Installation diff --git a/artlib/common/BaseART.py b/artlib/common/BaseART.py index 91f19a1..dfac044 100644 --- a/artlib/common/BaseART.py +++ b/artlib/common/BaseART.py @@ -11,12 +11,15 @@ class BaseART(BaseEstimator, ClusterMixin): - # Generic implementation of Adaptive Resonance Theory (ART) + """ + Generic implementation of Adaptive Resonance Theory (ART) + """ def __init__(self, params: dict): """ - - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ self.validate_params(params) @@ -44,26 +47,36 @@ def __setattr__(self, key, value): def get_params(self, deep: bool = True) -> dict: """ + Parameters + ---------- + deep : bool, default=True + If True, will return the parameters for this class and contained subobjects + that are estimators. - Parameters: - - deep: If True, will return the parameters for this class and contained subobjects that are estimators. - - Returns: + Returns + ------- + dict Parameter names mapped to their values. """ return self.params def set_params(self, **params): - """Set the parameters of this estimator. + """ + Set the parameters of this estimator. - Specific redefinition of sklearn.BaseEstimator.set_params for ART classes + Specific redefinition of `sklearn.BaseEstimator.set_params` for ART classes. - Parameters: - - **params : Estimator parameters. + Parameters + ---------- + **params : dict + Estimator parameters. + + Returns + ------- + self : object + Estimator instance. - Returns: - - self : estimator instance """ if not params: # Simple optimization to gain speed (inspect is slow) @@ -96,36 +109,49 @@ def set_params(self, **params): def prepare_data(self, X: np.ndarray) -> np.ndarray: """ - prepare data for clustering + Prepare data for clustering. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset. + + Returns + ------- + np.ndarray + Normalized data. - Returns: - normalized data """ normalized, self.d_max_, self.d_min_ = normalize(X, self.d_max_, self.d_min_) return normalized def restore_data(self, X: np.ndarray) -> np.ndarray: """ - restore data to state prior to preparation + Restore data to state prior to preparation. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset. + + Returns + ------- + np.ndarray + Restored data. - Returns: - restored data """ return de_normalize(X, d_max=self.d_max_, d_min=self.d_min_) @property def n_clusters(self) -> int: """ - get the current number of clusters + Get the current number of clusters. + + Returns + ------- + int + The number of clusters. - Returns: - the number of clusters """ if hasattr(self, "W"): return len(self.W) @@ -135,20 +161,24 @@ def n_clusters(self) -> int: @staticmethod def validate_params(params: dict): """ - validate clustering parameters + Validate clustering parameters. - Parameters: - - params: dict containing parameters for the algorithm + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ raise NotImplementedError def check_dimensions(self, X: np.ndarray): """ - check the data has the correct dimensions + Check the data has the correct dimensions. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset. """ if not hasattr(self, "dim_"): @@ -170,47 +200,67 @@ def validate_data(self, X: np.ndarray): def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: """ - get the activation of the cluster + Get the activation of the cluster. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - cluster activation, cache used for later processing + Returns + ------- + tuple + Cluster activation and cache used for later processing. """ raise NotImplementedError def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: """ - get the match criterion of the cluster + Get the match criterion of the cluster. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. - Returns: - cluster match criterion, cache used for later processing + Returns + ------- + tuple + Cluster match criterion and cache used for later processing. """ raise NotImplementedError def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None, op: Callable = operator.ge) -> tuple[bool, dict]: """ - get the binary match criterion of the cluster + Get the binary match criterion of the cluster. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. - Returns: - cluster match criterion binary, cache used for later processing + Returns + ------- + tuple + Binary match criterion and cache used for later processing. """ M, cache = self.match_criterion(i, w, params=params, cache=cache) @@ -223,41 +273,54 @@ def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: """ - get the updated cluster weight + Get the updated cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm - - cache: dict containing values cached from previous calculations + Parameters + ---------- + i : np.ndarray + Data sample. + w : np.ndarray + Cluster weight or information. + params : dict + Dictionary containing parameters for the algorithm. + cache : dict, optional + Cache containing values from previous calculations. - Returns: - updated cluster weight, cache used for later processing + Returns + ------- + np.ndarray + Updated cluster weight. """ raise NotImplementedError def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ - generate a new cluster weight + Generate a new cluster weight. - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + Parameters + ---------- + i : np.ndarray + Data sample. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - updated cluster weight + Returns + ------- + np.ndarray + Updated cluster weight. """ raise NotImplementedError def add_weight(self, new_w: np.ndarray): """ - add a new cluster weight + Add a new cluster weight. - Parameters: - - new_w: new cluster weight to add + Parameters + ---------- + new_w : np.ndarray + New cluster weight to add. """ self.weight_sample_counter_.append(1) @@ -265,11 +328,14 @@ def add_weight(self, new_w: np.ndarray): def set_weight(self, idx: int, new_w: np.ndarray): """ - set the value of a cluster weight + Set the value of a cluster weight. - Parameters: - - idx: index of cluster to update - - new_w: new cluster weight + Parameters + ---------- + idx : int + Index of cluster to update. + new_w : np.ndarray + New cluster weight. """ self.weight_sample_counter_[idx] += 1 @@ -312,23 +378,23 @@ def _deep_copy_params(self) -> dict: def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0) -> int: """ - fit the model to a single sample - - Parameters: - - x: data sample - - match_reset_func: a callable accepting the data sample, a cluster weight, the params dict, and the cache dict - Permits external factors to influence cluster creation. - Returns True if the cluster is valid for the sample, False otherwise - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho + Fit the model to a single sample. + Parameters + ---------- + x : np.ndarray + Data sample. + match_reset_func : callable, optional + A callable that influences cluster creation. + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, default="MT+" + Method for resetting match criterion. + epsilon : float, default=0.0 + Epsilon value used for adjusting match criterion. - Returns: - cluster label of the input sample + Returns + ------- + int + Cluster label of the input sample. """ self.sample_counter_ += 1 @@ -381,13 +447,17 @@ def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None, m def step_pred(self, x) -> int: """ - predict the label for a single sample + Predict the label for a single sample. - Parameters: - - x: data sample + Parameters + ---------- + x : np.ndarray + Data sample. - Returns: - cluster label of the input sample + Returns + ------- + int + Cluster label of the input sample. """ assert len(self.W) >= 0, "ART module is not fit." @@ -398,10 +468,12 @@ def step_pred(self, x) -> int: def pre_step_fit(self, X: np.ndarray): """ - undefined function called prior to each sample fit. Useful for cluster pruning + Undefined function called prior to each sample fit. Useful for cluster pruning. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset. """ # this is where pruning steps can go @@ -409,10 +481,12 @@ def pre_step_fit(self, X: np.ndarray): def post_step_fit(self, X: np.ndarray): """ - undefined function called after each sample fit. Useful for cluster pruning + Undefined function called after each sample fit. Useful for cluster pruning. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset. """ # this is where pruning steps can go @@ -420,10 +494,12 @@ def post_step_fit(self, X: np.ndarray): def post_fit(self, X: np.ndarray): """ - undefined function called after fit. Useful for cluster pruning + Undefined function called after fit. Useful for cluster pruning. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset. """ # this is where pruning steps can go @@ -432,21 +508,24 @@ def post_fit(self, X: np.ndarray): def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, match_reset_func: Optional[Callable] = None, max_iter=1, match_reset_method:Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, verbose: bool = False): """ - Fit the model to the data - - Parameters: - - X: data set - - y: not used. For compatibility. - - match_reset_func: a callable accepting the data sample, a cluster weight, the params dict, and the cache dict - Permits external factors to influence cluster creation. - Returns True if the cluster is valid for the sample, False otherwise - - max_iter: number of iterations to fit the model on the same data set - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho + Fit the model to the data. + + Parameters + ---------- + X : np.ndarray + The dataset. + y : np.ndarray, optional + Not used. For compatibility. + match_reset_func : callable, optional + A callable that influences cluster creation. + max_iter : int, default=1 + Number of iterations to fit the model on the same dataset. + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, default="MT+" + Method for resetting match criterion. + epsilon : float, default=0.0 + Epsilon value used for adjusting match criterion. + verbose : bool, default=False + If True, displays progress of the fitting process. """ self.validate_data(X) @@ -472,19 +551,18 @@ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, match_reset_func: O def partial_fit(self, X: np.ndarray, match_reset_func: Optional[Callable] = None, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0): """ - iteratively fit the model to the data + Iteratively fit the model to the data. - Parameters: - - X: data set - - match_reset_func: a callable accepting the data sample, a cluster weight, the params dict, and the cache dict - Permits external factors to influence cluster creation. - Returns True if the cluster is valid for the sample, False otherwise - - match_reset_method: - "MT+": Original method, rho=M+epsilon - "MT-": rho=M-epsilon - "MT0": rho=M, using > operator - "MT1": rho=1.0, Immediately create a new cluster on mismatch - "MT~": do not change rho + Parameters + ---------- + X : np.ndarray + The dataset. + match_reset_func : callable, optional + A callable that influences cluster creation. + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, default="MT+" + Method for resetting match criterion. + epsilon : float, default=0.0 + Epsilon value used for adjusting match criterion. """ @@ -507,13 +585,17 @@ def partial_fit(self, X: np.ndarray, match_reset_func: Optional[Callable] = None def predict(self, X: np.ndarray) -> np.ndarray: """ - predict labels for the data + Predict labels for the data. - Parameters: - - X: data set + Parameters + ---------- + X : np.ndarray + The dataset. - Returns: - labels for the data + Returns + ------- + np.ndarray + Labels for the data. """ @@ -532,21 +614,29 @@ def shrink_clusters(self, shrink_ratio: float = 0.1): def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ - undefined function for visualizing the bounds of each cluster + Undefined function for visualizing the bounds of each cluster. - Parameters: - - ax: figure axes - - colors: colors to use for each cluster - - linewidth: width of boundary line + Parameters + ---------- + ax : matplotlib.axes.Axes + Figure axes. + colors : iterable + Colors to use for each cluster. + linewidth : int, default=1 + Width of boundary line. """ raise NotImplementedError def get_cluster_centers(self) -> List[np.ndarray]: """ - undefined function for getting centers of each cluster. Used for regression - Returns: - cluster centroid + Undefined function for getting centers of each cluster. Used for regression. + + Returns + ------- + list of np.ndarray + Cluster centroids. + """ raise NotImplementedError @@ -561,15 +651,22 @@ def visualize( colors: Optional[Iterable] = None ): """ - Visualize the clustering of the data - - Parameters: - - X: data set - - y: sample labels - - ax: figure axes - - marker_size: size used for data points - - linewidth: width of boundary line - - colors: colors to use for each cluster + Visualize the clustering of the data. + + Parameters + ---------- + X : np.ndarray + The dataset. + y : np.ndarray + Sample labels. + ax : matplotlib.axes.Axes, optional + Figure axes. + marker_size : int, default=10 + Size used for data points. + linewidth : int, default=1 + Width of boundary line. + colors : iterable, optional + Colors to use for each cluster. """ import matplotlib.pyplot as plt diff --git a/artlib/common/BaseARTMAP.py b/artlib/common/BaseARTMAP.py index cf2f694..d45c3a2 100644 --- a/artlib/common/BaseARTMAP.py +++ b/artlib/common/BaseARTMAP.py @@ -5,20 +5,28 @@ from sklearn.base import BaseEstimator, ClassifierMixin, ClusterMixin class BaseARTMAP(BaseEstimator, ClassifierMixin, ClusterMixin): - + """ + Generic implementation of Adaptive Resonance Theory MAP (ARTMAP) + """ def __init__(self): self.map: dict[int, int] = dict() def set_params(self, **params): - """Set the parameters of this estimator. + """ + Set the parameters of this estimator. - Specific redefinition of sklearn.BaseEstimator.set_params for ARTMAP classes + Specific redefinition of `sklearn.BaseEstimator.set_params` for ARTMAP classes. - Parameters: - - **params : Estimator parameters. + Parameters + ---------- + **params : dict + Estimator parameters. + + Returns + ------- + self : object + Estimator instance. - Returns: - - self : estimator instance """ if not params: @@ -50,13 +58,17 @@ def set_params(self, **params): def map_a2b(self, y_a: Union[np.ndarray, int]) -> Union[np.ndarray, int]: """ - map an a-side label to a b-side label + Map an a-side label to a b-side label. - Parameters: - - y_a: side a label(s) + Parameters + ---------- + y_a : Union[np.ndarray, int] + Side A label(s). - Returns: - side B cluster label(s) + Returns + ------- + Union[np.ndarray, int] + Side B cluster label(s). """ if isinstance(y_a, int): @@ -66,72 +78,102 @@ def map_a2b(self, y_a: Union[np.ndarray, int]) -> Union[np.ndarray, int]: def validate_data(self, X: np.ndarray, y: np.ndarray): """ - validates the data prior to clustering + Validate the data prior to clustering. - Parameters: - - X: data set A - - y: data set B + Parameters + ---------- + X : np.ndarray + Dataset A. + y : np.ndarray + Dataset B. """ raise NotImplementedError def fit(self, X: np.ndarray, y: np.ndarray, max_iter=1, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10): """ - Fit the model to the data + Fit the model to the data. - Parameters: - - X: data set A - - y: data set B - - max_iter: number of iterations to fit the model on the same data set + Parameters + ---------- + X : np.ndarray + Dataset A. + y : np.ndarray + Dataset B. + max_iter : int, optional + Number of iterations to fit the model on the same dataset. + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, optional + Method for resetting match criterion. + epsilon : float, optional + Epsilon value used for adjusting match criterion, by default 1e-10. """ raise NotImplementedError def partial_fit(self, X: np.ndarray, y: np.ndarray, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10): """ - Partial fit the model to the data + Partial fit the model to the data. - Parameters: - - X: data set A - - y: data set B + Parameters + ---------- + X : np.ndarray + Dataset A. + y : np.ndarray + Dataset B. + match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, optional + Method for resetting match criterion. + epsilon : float, optional + Epsilon value used for adjusting match criterion, by default 1e-10. """ raise NotImplementedError def predict(self, X: np.ndarray) -> np.ndarray: """ - predict labels for the data + Predict labels for the data. - Parameters: - - X: data set A + Parameters + ---------- + X : np.ndarray + Dataset A. - Returns: - B labels for the data + Returns + ------- + np.ndarray + B-side labels for the data. """ raise NotImplementedError def predict_ab(self, X: np.ndarray) -> tuple[np.ndarray, np.ndarray]: """ - predict labels for the data, both A-side and B-side + Predict labels for the data, both A-side and B-side. - Parameters: - - X: data set A + Parameters + ---------- + X : np.ndarray + Dataset A. - Returns: - A labels for the data, B labels for the data + Returns + ------- + tuple[np.ndarray, np.ndarray] + A-side labels for the data, B-side labels for the data. """ raise NotImplementedError def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ - undefined function for visualizing the bounds of each cluster + Visualize the bounds of each cluster. - Parameters: - - ax: figure axes - - colors: colors to use for each cluster - - linewidth: width of boundary line + Parameters + ---------- + ax : matplotlib.axes.Axes + Figure axes. + colors : iterable + Colors to use for each cluster. + linewidth : int, optional + Width of boundary line, by default 1. """ raise NotImplementedError @@ -146,15 +188,22 @@ def visualize( colors: Optional[Iterable] = None ): """ - Visualize the clustering of the data - - Parameters: - - X: data set - - y: sample labels - - ax: figure axes - - marker_size: size used for data points - - linewidth: width of boundary line - - colors: colors to use for each cluster + Visualize the clustering of the data. + + Parameters + ---------- + X : np.ndarray + Dataset. + y : np.ndarray + Sample labels. + ax : matplotlib.axes.Axes, optional + Figure axes, by default None. + marker_size : int, optional + Size used for data points, by default 10. + linewidth : int, optional + Width of boundary line, by default 1. + colors : iterable, optional + Colors to use for each cluster, by default None. """ raise NotImplementedError \ No newline at end of file diff --git a/artlib/common/VAT.py b/artlib/common/VAT.py index 37fc7ae..db8fc28 100644 --- a/artlib/common/VAT.py +++ b/artlib/common/VAT.py @@ -7,18 +7,22 @@ def VAT(data: np.ndarray, distance_metric: Optional[Callable] = lambda X: pdist(X, "euclidean")) -> Tuple[np.ndarray, np.ndarray]: """ - Visual Assessment of cluster Tendency (VAT) algorithm. - - Parameters: - - data: Input data set as a 2D numpy array where each row is a sample. - - distance_metric: Optional callable function to calculate pairwise distances. - Defaults to Euclidean distance using pdist. - If None, assume data is a pre-computed distance matrix - - Returns: - Tuple containing: + Visual Assessment of Cluster Tendency (VAT) algorithm. + + Parameters + ---------- + data : np.ndarray + Input dataset as a 2D numpy array where each row is a sample. + distance_metric : callable, optional + Callable function to calculate pairwise distances. Defaults to Euclidean distance + using `pdist`. If None, assumes data is a pre-computed distance matrix. + + Returns + ------- + Tuple[np.ndarray, np.ndarray] - Reordered distance matrix reflecting cluster structure. - Reordered list of indices indicating the optimal clustering order. + """ if distance_metric is None: pairwise_dist = data diff --git a/artlib/common/utils.py b/artlib/common/utils.py index 285e804..694560e 100644 --- a/artlib/common/utils.py +++ b/artlib/common/utils.py @@ -7,15 +7,24 @@ def normalize(data: np.ndarray, d_max: Optional[np.ndarray] = None, d_min: Optio """ Normalize data column-wise between 0 and 1. - Parameters: - - data: 2D array of data set (rows = samples, columns = features) - - d_max: Optional, maximum values for each column - - d_min: Optional, minimum values for each column - - Returns: - - normalized: normalized data - - d_max: maximum values for each column - - d_min: minimum values for each column + Parameters + ---------- + data : np.ndarray + 2D array of dataset (rows = samples, columns = features). + d_max : np.ndarray, optional + Maximum values for each column. + d_min : np.ndarray, optional + Minimum values for each column. + + Returns + ------- + np.ndarray + Normalized data. + np.ndarray + Maximum values for each column. + np.ndarray + Minimum values for each column. + """ if d_min is None: d_min = np.min(data, axis=0) @@ -30,38 +39,55 @@ def de_normalize(data: np.ndarray, d_max: np.ndarray, d_min: np.ndarray) -> np.n """ Restore column-wise normalized data to original scale. - Parameters: - - data: normalized data - - d_max: maximum values for each column - - d_min: minimum values for each column + Parameters + ---------- + data : np.ndarray + Normalized data. + d_max : np.ndarray + Maximum values for each column. + d_min : np.ndarray + Minimum values for each column. + + Returns + ------- + np.ndarray + De-normalized data. - Returns: - - De-normalized data """ return data * (d_max - d_min) + d_min def compliment_code(data: np.ndarray) -> np.ndarray: """ - compliment code data + Compliment code the data. - Parameters: - - data: data set + Parameters + ---------- + data : np.ndarray + Dataset. + + Returns + ------- + np.ndarray + Compliment coded data. - Returns: - compliment coded data """ cc_data = np.hstack([data, 1.0-data]) return cc_data def de_compliment_code(data: np.ndarray) -> np.ndarray: """ - finds centroid of compliment coded data + Find the centroid of compliment coded data. + + Parameters + ---------- + data : np.ndarray + Dataset. - Parameters: - - data: data set + Returns + ------- + np.ndarray + De-compliment coded data. - Returns: - compliment coded data """ # Get the shape of the array n, total_columns = data.shape @@ -83,38 +109,53 @@ def de_compliment_code(data: np.ndarray) -> np.ndarray: def l1norm(x: np.ndarray) -> float: """ - get l1 norm of a vector + Get the L1 norm of a vector. - Parameters: - - x: some vector + Parameters + ---------- + x : np.ndarray + Input vector. + + Returns + ------- + float + L1 norm. - Returns: - l1 norm """ return float(np.sum(np.absolute(x))) def l2norm2(data: np.ndarray) -> float: """ - get (l2 norm)^2 of a vector + Get the squared L2 norm of a vector. + + Parameters + ---------- + data : np.ndarray + Input vector. - Parameters: - - x: some vector + Returns + ------- + float + Squared L2 norm. - Returns: - (l2 norm)^2 """ return float(np.matmul(data, data)) def fuzzy_and(x: np.ndarray, y: np.ndarray) -> np.ndarray: """ - get the fuzzy AND operation between two vectors - - Parameters: - - a: some vector - - b: some vector - - Returns: - Fuzzy AND result + Get the fuzzy AND operation between two vectors. + + Parameters + ---------- + x : np.ndarray + First input vector. + y : np.ndarray + Second input vector. + + Returns + ------- + np.ndarray + Fuzzy AND result. """ return np.minimum(x, y) diff --git a/artlib/common/visualization.py b/artlib/common/visualization.py index 7214a9d..a49afa7 100644 --- a/artlib/common/visualization.py +++ b/artlib/common/visualization.py @@ -11,16 +11,24 @@ def plot_gaussian_contours_fading( linewidth: int = 1 ): """ - Plots concentric ellipses to represent the contours of a 2D Gaussian distribution, with fading colors. - - Parameters: - - ax: Matplotlib axis object. If None, creates a new figure and axis. - - mean: A numpy array representing the mean (μ) of the distribution. - - std_dev: A numpy array representing the standard deviation (σ) of the distribution. - - color: A 4D numpy array including RGB and alpha channels to specify the color and initial opacity. - - max_std: Max standard deviations to draw contours to. Default is 2. - - sigma_steps: Step size in standard deviations for each contour. Default is 0.25. - - linewidth: width of boundary line + Plot concentric ellipses to represent the contours of a 2D Gaussian distribution with fading colors. + + Parameters + ---------- + ax : matplotlib.axes.Axes + Matplotlib axis object to plot the ellipses. + mean : np.ndarray + A numpy array representing the mean (μ) of the distribution. + std_dev : np.ndarray + A numpy array representing the standard deviation (σ) of the distribution. + color : np.ndarray + A 4D numpy array including RGB and alpha channels to specify the color and initial opacity. + max_std : int, optional + Maximum number of standard deviations to draw contours to, by default 2. + sigma_steps : float, optional + Step size in standard deviations for each contour, by default 0.25. + linewidth : int, optional + Width of the boundary line, by default 1. """ from matplotlib.patches import Ellipse @@ -54,18 +62,25 @@ def plot_gaussian_contours_covariance( linewidth: int = 1 ): """ - Plots concentric ellipses to represent the contours of a 2D Gaussian distribution, with fading colors. + Plot concentric ellipses to represent the contours of a 2D Gaussian distribution with fading colors. Accepts a covariance matrix to properly represent the distribution's orientation and shape. - - Parameters: - - ax: Matplotlib axis object. If None, creates a new figure and axis. - - mean: A numpy array representing the mean (μ) of the distribution. - - covariance: A 2x2 numpy array representing the covariance matrix of the distribution. - - color: A 4D numpy array including RGB and alpha channels to specify the color and initial opacity. - - max_std: Max standard deviations to draw contours to. Default is 2. - - sigma_steps: Step size in standard deviations for each contour. Default is 0.25. - - linewidth: width of boundary line + Parameters + ---------- + ax : matplotlib.axes.Axes + Matplotlib axis object to plot the ellipses. + mean : np.ndarray + A numpy array representing the mean (μ) of the distribution. + covariance : np.ndarray + A 2x2 numpy array representing the covariance matrix of the distribution. + color : np.ndarray + A 4D numpy array including RGB and alpha channels to specify the color and initial opacity. + max_std : int, optional + Maximum number of standard deviations to draw contours to, by default 2. + sigma_steps : float, optional + Step size in standard deviations for each contour, by default 0.25. + linewidth : int, optional + Width of the boundary line, by default 1. """ from matplotlib.patches import Ellipse @@ -103,15 +118,23 @@ def plot_weight_matrix_as_ellipse( linewidth: int = 1 ): """ - Plots the transformation of a unit circle by the weight matrix W as an ellipse. - - Parameters: - - ax: Matplotlib axis object. If None, creates a new figure and axis. - - mean: The center point (x, y) of the ellipse. - - s: Scalar to scale the weight matrix W. - - W: 2x2 weight matrix. - - color: Color of the ellipse. - - linewidth: width of boundary line + Plot the transformation of a unit circle by the weight matrix W as an ellipse. + + Parameters + ---------- + ax : matplotlib.axes.Axes + Matplotlib axis object to plot the ellipse. + s : float + Scalar to scale the weight matrix W. + W : np.ndarray + 2x2 weight matrix. + mean : np.ndarray + The center point (x, y) of the ellipse. + color : np.ndarray + Color of the ellipse. + linewidth : int, optional + Width of the boundary line, by default 1. + """ # Compute the transformation matrix transform_matrix = W[:2, :2] diff --git a/img/comparison_of_elementary_methods.jpg b/docs/_static/img/comparison_of_elementary_methods.jpg similarity index 100% rename from img/comparison_of_elementary_methods.jpg rename to docs/_static/img/comparison_of_elementary_methods.jpg diff --git a/docs/source/artlib.biclustering.rst b/docs/source/artlib.biclustering.rst new file mode 100644 index 0000000..a7b0ddb --- /dev/null +++ b/docs/source/artlib.biclustering.rst @@ -0,0 +1,21 @@ +artlib.biclustering package +=========================== + +Submodules +---------- + +artlib.biclustering.BARTMAP module +---------------------------------- + +.. automodule:: artlib.biclustering.BARTMAP + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.biclustering + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.common.rst b/docs/source/artlib.common.rst new file mode 100644 index 0000000..041e0b2 --- /dev/null +++ b/docs/source/artlib.common.rst @@ -0,0 +1,53 @@ +artlib.common package +===================== + +Submodules +---------- + +artlib.common.BaseART module +---------------------------- + +.. automodule:: artlib.common.BaseART + :members: + :undoc-members: + :show-inheritance: + +artlib.common.BaseARTMAP module +------------------------------- + +.. automodule:: artlib.common.BaseARTMAP + :members: + :undoc-members: + :show-inheritance: + +artlib.common.VAT module +------------------------ + +.. automodule:: artlib.common.VAT + :members: + :undoc-members: + :show-inheritance: + +artlib.common.utils module +-------------------------- + +.. automodule:: artlib.common.utils + :members: + :undoc-members: + :show-inheritance: + +artlib.common.visualization module +---------------------------------- + +.. automodule:: artlib.common.visualization + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.common + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.cvi.iCVIs.rst b/docs/source/artlib.cvi.iCVIs.rst new file mode 100644 index 0000000..78c93dc --- /dev/null +++ b/docs/source/artlib.cvi.iCVIs.rst @@ -0,0 +1,21 @@ +artlib.cvi.iCVIs package +======================== + +Submodules +---------- + +artlib.cvi.iCVIs.CalinkskiHarabasz module +----------------------------------------- + +.. automodule:: artlib.cvi.iCVIs.CalinkskiHarabasz + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.cvi.iCVIs + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.cvi.rst b/docs/source/artlib.cvi.rst new file mode 100644 index 0000000..e8d6653 --- /dev/null +++ b/docs/source/artlib.cvi.rst @@ -0,0 +1,29 @@ +artlib.cvi package +================== + +Submodules +---------- + +artlib.cvi.CVIART module +------------------------ + +.. automodule:: artlib.cvi.CVIART + :members: + :undoc-members: + :show-inheritance: + +artlib.cvi.iCVIFuzzyArt module +------------------------------ + +.. automodule:: artlib.cvi.iCVIFuzzyArt + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.cvi + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.elementary.rst b/docs/source/artlib.elementary.rst new file mode 100644 index 0000000..d678c2e --- /dev/null +++ b/docs/source/artlib.elementary.rst @@ -0,0 +1,85 @@ +artlib.elementary package +========================= + +Submodules +---------- + +artlib.elementary.ART1 module +----------------------------- + +.. automodule:: artlib.elementary.ART1 + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.ART2 module +----------------------------- + +.. automodule:: artlib.elementary.ART2 + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.BayesianART module +------------------------------------ + +.. automodule:: artlib.elementary.BayesianART + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.DualVigilanceART module +----------------------------------------- + +.. automodule:: artlib.elementary.DualVigilanceART + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.EllipsoidART module +------------------------------------- + +.. automodule:: artlib.elementary.EllipsoidART + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.FuzzyART module +--------------------------------- + +.. automodule:: artlib.elementary.FuzzyART + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.GaussianART module +------------------------------------ + +.. automodule:: artlib.elementary.GaussianART + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.HypersphereART module +--------------------------------------- + +.. automodule:: artlib.elementary.HypersphereART + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.QuadraticNeuronART module +------------------------------------------- + +.. automodule:: artlib.elementary.QuadraticNeuronART + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.elementary + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.experimental.rst b/docs/source/artlib.experimental.rst new file mode 100644 index 0000000..b4c1a5d --- /dev/null +++ b/docs/source/artlib.experimental.rst @@ -0,0 +1,37 @@ +artlib.experimental package +=========================== + +Submodules +---------- + +artlib.experimental.ConvexHullART module +---------------------------------------- + +.. automodule:: artlib.experimental.ConvexHullART + :members: + :undoc-members: + :show-inheritance: + +artlib.experimental.SeqART module +--------------------------------- + +.. automodule:: artlib.experimental.SeqART + :members: + :undoc-members: + :show-inheritance: + +artlib.experimental.merging module +---------------------------------- + +.. automodule:: artlib.experimental.merging + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.experimental + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.fusion.rst b/docs/source/artlib.fusion.rst new file mode 100644 index 0000000..b7a8f93 --- /dev/null +++ b/docs/source/artlib.fusion.rst @@ -0,0 +1,21 @@ +artlib.fusion package +===================== + +Submodules +---------- + +artlib.fusion.FusionART module +------------------------------ + +.. automodule:: artlib.fusion.FusionART + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.fusion + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.hierarchical.rst b/docs/source/artlib.hierarchical.rst new file mode 100644 index 0000000..5e6bfb3 --- /dev/null +++ b/docs/source/artlib.hierarchical.rst @@ -0,0 +1,29 @@ +artlib.hierarchical package +=========================== + +Submodules +---------- + +artlib.hierarchical.DeepARTMAP module +------------------------------------- + +.. automodule:: artlib.hierarchical.DeepARTMAP + :members: + :undoc-members: + :show-inheritance: + +artlib.hierarchical.SMART module +-------------------------------- + +.. automodule:: artlib.hierarchical.SMART + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.hierarchical + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.reinforcement.rst b/docs/source/artlib.reinforcement.rst new file mode 100644 index 0000000..d9a230a --- /dev/null +++ b/docs/source/artlib.reinforcement.rst @@ -0,0 +1,29 @@ +artlib.reinforcement package +============================ + +Submodules +---------- + +artlib.reinforcement.FALCON module +---------------------------------- + +.. automodule:: artlib.reinforcement.FALCON + :members: + :undoc-members: + :show-inheritance: + +artlib.reinforcement.TDFALCON module +------------------------------------ + +.. automodule:: artlib.reinforcement.TDFALCON + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.reinforcement + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.rst b/docs/source/artlib.rst new file mode 100644 index 0000000..b29b29d --- /dev/null +++ b/docs/source/artlib.rst @@ -0,0 +1,28 @@ +artlib package +============== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + artlib.biclustering + artlib.common + artlib.cvi + artlib.elementary + artlib.fusion + artlib.hierarchical + artlib.reinforcement + artlib.supervised + artlib.topological + + +Module contents +--------------- + +.. automodule:: artlib + :members: + :undoc-members: + :show-inheritance: + :exclude-members: compliment_code, normalize diff --git a/docs/source/artlib.supervised.rst b/docs/source/artlib.supervised.rst new file mode 100644 index 0000000..fb16080 --- /dev/null +++ b/docs/source/artlib.supervised.rst @@ -0,0 +1,29 @@ +artlib.supervised package +========================= + +Submodules +---------- + +artlib.supervised.ARTMAP module +------------------------------- + +.. automodule:: artlib.supervised.ARTMAP + :members: + :undoc-members: + :show-inheritance: + +artlib.supervised.SimpleARTMAP module +------------------------------------- + +.. automodule:: artlib.supervised.SimpleARTMAP + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.supervised + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.topological.rst b/docs/source/artlib.topological.rst new file mode 100644 index 0000000..bce9cc2 --- /dev/null +++ b/docs/source/artlib.topological.rst @@ -0,0 +1,21 @@ +artlib.topological package +========================== + +Submodules +---------- + +artlib.topological.TopoART module +--------------------------------- + +.. automodule:: artlib.topological.TopoART + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.topological + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/conf.py b/docs/source/conf.py index 9ac14cd..f127d00 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,7 +14,7 @@ # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = ['sphinx.ext.autodoc', 'autoapi.extension', 'sphinx.ext.napoleon'] +extensions = ['sphinx.ext.autodoc', 'autoapi.extension', 'sphinx.ext.napoleon', 'myst_parser'] templates_path = ['_templates'] exclude_patterns = ['artlib/experimental/*'] @@ -25,7 +25,7 @@ # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'classic' +html_theme = 'sphinx_rtd_theme' html_static_path = ['_static'] diff --git a/docs/source/index.rst b/docs/source/index.rst index 8550f17..ce1fb04 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,9 +6,8 @@ AdaptiveResonanceLib documentation ================================== -Add your content using ``reStructuredText`` syntax. See the -`reStructuredText `_ -documentation for details. +.. include:: ../../README.md + :parser: myst_parser.sphinx_ .. toctree:: @@ -16,4 +15,3 @@ documentation for details. :caption: Contents: artlib - diff --git a/docs/source/modules.rst b/docs/source/modules.rst new file mode 100644 index 0000000..b943c52 --- /dev/null +++ b/docs/source/modules.rst @@ -0,0 +1,7 @@ +artlib +====== + +.. toctree:: + :maxdepth: 4 + + artlib diff --git a/pyproject.toml b/pyproject.toml index c17fe78..ffe0d0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ pytest = "^6.2.2" sphinx = "^5.0" sphinx-rtd-theme = "^1.0.0" # If you're using the Read the Docs theme sphinx-autoapi = ">=1.8.1" +myst-parser = "^1.0" [build-system] requires = ["poetry-core>=1.0.0"] From 4676139ffeb1ba9da5f5ef75727e80731cdd44fd Mon Sep 17 00:00:00 2001 From: niklas melton Date: Tue, 15 Oct 2024 19:35:08 -0500 Subject: [PATCH 5/8] readthedocs working --- artlib/fusion/FusionART.py | 4 +- artlib/reinforcement/FALCON.py | 137 +++++++++++++++++++++++++ artlib/reinforcement/TDFALCON.py | 144 --------------------------- artlib/topological/TopoART.py | 2 +- docs/source/artlib.biclustering.rst | 21 ---- docs/source/artlib.common.rst | 53 ---------- docs/source/artlib.cvi.iCVIs.rst | 21 ---- docs/source/artlib.cvi.rst | 29 ------ docs/source/artlib.elementary.rst | 85 ---------------- docs/source/artlib.experimental.rst | 37 ------- docs/source/artlib.fusion.rst | 21 ---- docs/source/artlib.hierarchical.rst | 29 ------ docs/source/artlib.reinforcement.rst | 29 ------ docs/source/artlib.rst | 28 ------ docs/source/artlib.supervised.rst | 29 ------ docs/source/artlib.topological.rst | 21 ---- docs/source/conf.py | 18 +++- docs/source/index.rst | 4 +- docs/source/modules.rst | 7 -- 19 files changed, 156 insertions(+), 563 deletions(-) delete mode 100644 artlib/reinforcement/TDFALCON.py delete mode 100644 docs/source/artlib.biclustering.rst delete mode 100644 docs/source/artlib.common.rst delete mode 100644 docs/source/artlib.cvi.iCVIs.rst delete mode 100644 docs/source/artlib.cvi.rst delete mode 100644 docs/source/artlib.elementary.rst delete mode 100644 docs/source/artlib.experimental.rst delete mode 100644 docs/source/artlib.fusion.rst delete mode 100644 docs/source/artlib.hierarchical.rst delete mode 100644 docs/source/artlib.reinforcement.rst delete mode 100644 docs/source/artlib.rst delete mode 100644 docs/source/artlib.supervised.rst delete mode 100644 docs/source/artlib.topological.rst delete mode 100644 docs/source/modules.rst diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index f0c2fac..98cc53f 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -586,7 +586,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: ] return centers - def get_channel_centers(self, channel: int): + def get_channel_centers(self, channel: int) -> List[np.ndarray]: """ Get the center points of clusters for a specific channel. @@ -597,7 +597,7 @@ def get_channel_centers(self, channel: int): Returns ------- - np.ndarray + list of np.ndarray Cluster centers for the specified channel. """ return self.modules[channel].get_cluster_centers() diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index 1ecdc8b..78fe910 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -2,10 +2,16 @@ Tan, A.-H. (2004). FALCON: a fusion architecture for learning, cognition, and navigation. In Proc. IEEE International Joint Conference on Neural Networks (IJCNN) (pp. 3297–3302). volume 4. doi:10.1109/ IJCNN.2004.1381208 + +Tan, A.-H., Lu, N., & Xiao, D. (2008). Integrating Temporal Difference Methods and Self-Organizing Neural +Networks for Reinforcement Learning With Delayed Evaluative Feedback. IEEE Transactions on Neural +Networks, 19 , 230–244. doi:10.1109/TNN.2007.905839 """ + import numpy as np from typing import Optional, Literal, Tuple, Union, List from artlib.common.BaseART import BaseART +from artlib.common.utils import compliment_code, de_compliment_code from artlib.fusion.FusionART import FusionART @@ -250,3 +256,134 @@ def get_rewards(self, states: np.ndarray, actions: np.ndarray) -> np.ndarray: return np.array([reward_centers[c] for c in C]) +class TD_FALCON(FALCON): + """TD-FALCON for Reinforcement Learning + + This module implements TD-FALCON as first described in + Tan, A.-H., Lu, N., & Xiao, D. (2008). Integrating Temporal Difference Methods and Self-Organizing Neural + Networks for Reinforcement Learning With Delayed Evaluative Feedback. IEEE Transactions on Neural + Networks, 19 , 230–244. doi:10.1109/TNN.2007.905839. + TD-FALCON is based on a FALCON backbone but includes specific function for temporal-difference learning. + Currently, only SARSA is implemented and only Fuzzy ART base modules are supported. + + """ + + def __init__( + self, + state_art: BaseART, + action_art: BaseART, + reward_art: BaseART, + gamma_values: Union[List[float], np.ndarray] = np.array([0.33, 0.33, 0.34]), + channel_dims: Union[List[int], np.ndarray] = list[int], + td_alpha: float = 1.0, + td_lambda: float = 1.0, + ): + """ + Initialize the TD-FALCON model. + + Parameters + ---------- + state_art : BaseART + The instantiated ART module that will cluster the state-space. + action_art : BaseART + The instantiated ART module that will cluster the action-space. + reward_art : BaseART + The instantiated ART module that will cluster the reward-space. + gamma_values : list of float or np.ndarray, optional + The activation ratio for each channel, by default [0.33, 0.33, 0.34]. + channel_dims : list of int or np.ndarray + The dimension of each channel. + td_alpha : float, optional + The learning rate for the temporal difference estimator, by default 1.0. + td_lambda : float, optional + The future-cost factor for temporal difference learning, by default 1.0. + """ + self.td_alpha = td_alpha + self.td_lambda = td_lambda + super(TD_FALCON, self).__init__(state_art, action_art, reward_art, gamma_values, channel_dims) + + def fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray): + """ + Fit the TD-FALCON model to the data. + + Raises + ------ + NotImplementedError + TD-FALCON can only be trained with partial fit. + """ + raise NotImplementedError("TD-FALCON can only be trained with partial fit") + + def calculate_SARSA(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray, single_sample_reward: Optional[float] = None): + """ + Calculate the SARSA values for reinforcement learning. + + Parameters + ---------- + states : np.ndarray + The state data. + actions : np.ndarray + The action data. + rewards : np.ndarray + The reward data. + single_sample_reward : float, optional + The reward for a single sample, if applicable, by default None. + + Returns + ------- + tuple of np.ndarray + The state, action, and SARSA-adjusted reward data to be used for fitting. + """ + # calculate SARSA values + rewards_dcc = de_compliment_code(rewards) + if len(states) > 1: + + if hasattr(self.fusion_art.modules[0], "W"): + # if FALCON has been trained get predicted rewards + Q = self.get_rewards(states, actions) + else: + # otherwise set predicted rewards to 0 + Q = np.zeros_like(rewards_dcc) + # SARSA equation + sarsa_rewards = Q[:-1] + self.td_alpha * (rewards_dcc[:-1] + self.td_lambda * Q[1:] - Q[:-1]) + # ensure SARSA values are between 0 and 1 + sarsa_rewards = np.maximum(np.minimum(sarsa_rewards, 1.0), 0.0) + # compliment code rewards + sarsa_rewards_fit = compliment_code(sarsa_rewards) + # we cant train on the final state because no rewards are generated after it + states_fit = states[:-1, :] + actions_fit = actions[:-1, :] + else: + # if we only have a single sample, we cant learn from future samples + if single_sample_reward is None: + sarsa_rewards_fit = rewards + else: + sarsa_rewards_fit = compliment_code(np.array([[single_sample_reward]])) + states_fit = states + actions_fit = actions + + return states_fit, actions_fit, sarsa_rewards_fit + + def partial_fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray, single_sample_reward: Optional[float] = None): + """ + Partially fit the TD-FALCON model using SARSA. + + Parameters + ---------- + states : np.ndarray + The state data. + actions : np.ndarray + The action data. + rewards : np.ndarray + The reward data. + single_sample_reward : float, optional + The reward for a single sample, if applicable, by default None. + + Returns + ------- + TD_FALCON + The partially fitted TD-FALCON model. + """ + states_fit, actions_fit, sarsa_rewards_fit = self.calculate_SARSA(states, actions, rewards, single_sample_reward) + data = self.fusion_art.join_channel_data([states_fit, actions_fit, sarsa_rewards_fit]) + self.fusion_art = self.fusion_art.partial_fit(data) + return self diff --git a/artlib/reinforcement/TDFALCON.py b/artlib/reinforcement/TDFALCON.py deleted file mode 100644 index 1cf78ef..0000000 --- a/artlib/reinforcement/TDFALCON.py +++ /dev/null @@ -1,144 +0,0 @@ -""" -Tan, A.-H., Lu, N., & Xiao, D. (2008). Integrating Temporal Difference Methods and Self-Organizing Neural -Networks for Reinforcement Learning With Delayed Evaluative Feedback. IEEE Transactions on Neural -Networks, 19 , 230–244. doi:10.1109/TNN.2007.905839 -""" - -import numpy as np -from typing import Optional, List, Union -from artlib.common.BaseART import BaseART -from artlib.common.utils import compliment_code, de_compliment_code -from artlib.reinforcement.FALCON import FALCON - -class TD_FALCON(FALCON): - """TD-FALCON for Reinforcement Learning - - This module implements TD-FALCON as first described in - Tan, A.-H., Lu, N., & Xiao, D. (2008). Integrating Temporal Difference Methods and Self-Organizing Neural - Networks for Reinforcement Learning With Delayed Evaluative Feedback. IEEE Transactions on Neural - Networks, 19 , 230–244. doi:10.1109/TNN.2007.905839. - TD-FALCON is based on a FALCON backbone but includes specific function for temporal-difference learning. - Currently, only SARSA is implemented and only Fuzzy ART base modules are supported. - - """ - - def __init__( - self, - state_art: BaseART, - action_art: BaseART, - reward_art: BaseART, - gamma_values: Union[List[float], np.ndarray] = np.array([0.33, 0.33, 0.34]), - channel_dims: Union[List[int], np.ndarray] = list[int], - td_alpha: float = 1.0, - td_lambda: float = 1.0, - ): - """ - Initialize the TD-FALCON model. - - Parameters - ---------- - state_art : BaseART - The instantiated ART module that will cluster the state-space. - action_art : BaseART - The instantiated ART module that will cluster the action-space. - reward_art : BaseART - The instantiated ART module that will cluster the reward-space. - gamma_values : list of float or np.ndarray, optional - The activation ratio for each channel, by default [0.33, 0.33, 0.34]. - channel_dims : list of int or np.ndarray - The dimension of each channel. - td_alpha : float, optional - The learning rate for the temporal difference estimator, by default 1.0. - td_lambda : float, optional - The future-cost factor for temporal difference learning, by default 1.0. - """ - self.td_alpha = td_alpha - self.td_lambda = td_lambda - super(TD_FALCON, self).__init__(state_art, action_art, reward_art, gamma_values, channel_dims) - - def fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray): - """ - Fit the TD-FALCON model to the data. - - Raises - ------ - NotImplementedError - TD-FALCON can only be trained with partial fit. - """ - raise NotImplementedError("TD-FALCON can only be trained with partial fit") - - def calculate_SARSA(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray, single_sample_reward: Optional[float] = None): - """ - Calculate the SARSA values for reinforcement learning. - - Parameters - ---------- - states : np.ndarray - The state data. - actions : np.ndarray - The action data. - rewards : np.ndarray - The reward data. - single_sample_reward : float, optional - The reward for a single sample, if applicable, by default None. - - Returns - ------- - tuple of np.ndarray - The state, action, and SARSA-adjusted reward data to be used for fitting. - """ - # calculate SARSA values - rewards_dcc = de_compliment_code(rewards) - if len(states) > 1: - - if hasattr(self.fusion_art.modules[0], "W"): - # if FALCON has been trained get predicted rewards - Q = self.get_rewards(states, actions) - else: - # otherwise set predicted rewards to 0 - Q = np.zeros_like(rewards_dcc) - # SARSA equation - sarsa_rewards = Q[:-1] + self.td_alpha * (rewards_dcc[:-1] + self.td_lambda * Q[1:] - Q[:-1]) - # ensure SARSA values are between 0 and 1 - sarsa_rewards = np.maximum(np.minimum(sarsa_rewards, 1.0), 0.0) - # compliment code rewards - sarsa_rewards_fit = compliment_code(sarsa_rewards) - # we cant train on the final state because no rewards are generated after it - states_fit = states[:-1, :] - actions_fit = actions[:-1, :] - else: - # if we only have a single sample, we cant learn from future samples - if single_sample_reward is None: - sarsa_rewards_fit = rewards - else: - sarsa_rewards_fit = compliment_code(np.array([[single_sample_reward]])) - states_fit = states - actions_fit = actions - - return states_fit, actions_fit, sarsa_rewards_fit - - def partial_fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray, single_sample_reward: Optional[float] = None): - """ - Partially fit the TD-FALCON model using SARSA. - - Parameters - ---------- - states : np.ndarray - The state data. - actions : np.ndarray - The action data. - rewards : np.ndarray - The reward data. - single_sample_reward : float, optional - The reward for a single sample, if applicable, by default None. - - Returns - ------- - TD_FALCON - The partially fitted TD-FALCON model. - """ - states_fit, actions_fit, sarsa_rewards_fit = self.calculate_SARSA(states, actions, rewards, single_sample_reward) - data = self.fusion_art.join_channel_data([states_fit, actions_fit, sarsa_rewards_fit]) - self.fusion_art = self.fusion_art.partial_fit(data) - return self - diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index 98ffd4f..2fe4b1c 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -18,7 +18,7 @@ class TopoART(BaseART): - """Topo ART Clustering + """Topo ART for Topological Clustering This module implements Topo ART as first published in Tscherepanow, M. (2010). diff --git a/docs/source/artlib.biclustering.rst b/docs/source/artlib.biclustering.rst deleted file mode 100644 index a7b0ddb..0000000 --- a/docs/source/artlib.biclustering.rst +++ /dev/null @@ -1,21 +0,0 @@ -artlib.biclustering package -=========================== - -Submodules ----------- - -artlib.biclustering.BARTMAP module ----------------------------------- - -.. automodule:: artlib.biclustering.BARTMAP - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: artlib.biclustering - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/artlib.common.rst b/docs/source/artlib.common.rst deleted file mode 100644 index 041e0b2..0000000 --- a/docs/source/artlib.common.rst +++ /dev/null @@ -1,53 +0,0 @@ -artlib.common package -===================== - -Submodules ----------- - -artlib.common.BaseART module ----------------------------- - -.. automodule:: artlib.common.BaseART - :members: - :undoc-members: - :show-inheritance: - -artlib.common.BaseARTMAP module -------------------------------- - -.. automodule:: artlib.common.BaseARTMAP - :members: - :undoc-members: - :show-inheritance: - -artlib.common.VAT module ------------------------- - -.. automodule:: artlib.common.VAT - :members: - :undoc-members: - :show-inheritance: - -artlib.common.utils module --------------------------- - -.. automodule:: artlib.common.utils - :members: - :undoc-members: - :show-inheritance: - -artlib.common.visualization module ----------------------------------- - -.. automodule:: artlib.common.visualization - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: artlib.common - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/artlib.cvi.iCVIs.rst b/docs/source/artlib.cvi.iCVIs.rst deleted file mode 100644 index 78c93dc..0000000 --- a/docs/source/artlib.cvi.iCVIs.rst +++ /dev/null @@ -1,21 +0,0 @@ -artlib.cvi.iCVIs package -======================== - -Submodules ----------- - -artlib.cvi.iCVIs.CalinkskiHarabasz module ------------------------------------------ - -.. automodule:: artlib.cvi.iCVIs.CalinkskiHarabasz - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: artlib.cvi.iCVIs - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/artlib.cvi.rst b/docs/source/artlib.cvi.rst deleted file mode 100644 index e8d6653..0000000 --- a/docs/source/artlib.cvi.rst +++ /dev/null @@ -1,29 +0,0 @@ -artlib.cvi package -================== - -Submodules ----------- - -artlib.cvi.CVIART module ------------------------- - -.. automodule:: artlib.cvi.CVIART - :members: - :undoc-members: - :show-inheritance: - -artlib.cvi.iCVIFuzzyArt module ------------------------------- - -.. automodule:: artlib.cvi.iCVIFuzzyArt - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: artlib.cvi - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/artlib.elementary.rst b/docs/source/artlib.elementary.rst deleted file mode 100644 index d678c2e..0000000 --- a/docs/source/artlib.elementary.rst +++ /dev/null @@ -1,85 +0,0 @@ -artlib.elementary package -========================= - -Submodules ----------- - -artlib.elementary.ART1 module ------------------------------ - -.. automodule:: artlib.elementary.ART1 - :members: - :undoc-members: - :show-inheritance: - -artlib.elementary.ART2 module ------------------------------ - -.. automodule:: artlib.elementary.ART2 - :members: - :undoc-members: - :show-inheritance: - -artlib.elementary.BayesianART module ------------------------------------- - -.. automodule:: artlib.elementary.BayesianART - :members: - :undoc-members: - :show-inheritance: - -artlib.elementary.DualVigilanceART module ------------------------------------------ - -.. automodule:: artlib.elementary.DualVigilanceART - :members: - :undoc-members: - :show-inheritance: - -artlib.elementary.EllipsoidART module -------------------------------------- - -.. automodule:: artlib.elementary.EllipsoidART - :members: - :undoc-members: - :show-inheritance: - -artlib.elementary.FuzzyART module ---------------------------------- - -.. automodule:: artlib.elementary.FuzzyART - :members: - :undoc-members: - :show-inheritance: - -artlib.elementary.GaussianART module ------------------------------------- - -.. automodule:: artlib.elementary.GaussianART - :members: - :undoc-members: - :show-inheritance: - -artlib.elementary.HypersphereART module ---------------------------------------- - -.. automodule:: artlib.elementary.HypersphereART - :members: - :undoc-members: - :show-inheritance: - -artlib.elementary.QuadraticNeuronART module -------------------------------------------- - -.. automodule:: artlib.elementary.QuadraticNeuronART - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: artlib.elementary - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/artlib.experimental.rst b/docs/source/artlib.experimental.rst deleted file mode 100644 index b4c1a5d..0000000 --- a/docs/source/artlib.experimental.rst +++ /dev/null @@ -1,37 +0,0 @@ -artlib.experimental package -=========================== - -Submodules ----------- - -artlib.experimental.ConvexHullART module ----------------------------------------- - -.. automodule:: artlib.experimental.ConvexHullART - :members: - :undoc-members: - :show-inheritance: - -artlib.experimental.SeqART module ---------------------------------- - -.. automodule:: artlib.experimental.SeqART - :members: - :undoc-members: - :show-inheritance: - -artlib.experimental.merging module ----------------------------------- - -.. automodule:: artlib.experimental.merging - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: artlib.experimental - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/artlib.fusion.rst b/docs/source/artlib.fusion.rst deleted file mode 100644 index b7a8f93..0000000 --- a/docs/source/artlib.fusion.rst +++ /dev/null @@ -1,21 +0,0 @@ -artlib.fusion package -===================== - -Submodules ----------- - -artlib.fusion.FusionART module ------------------------------- - -.. automodule:: artlib.fusion.FusionART - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: artlib.fusion - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/artlib.hierarchical.rst b/docs/source/artlib.hierarchical.rst deleted file mode 100644 index 5e6bfb3..0000000 --- a/docs/source/artlib.hierarchical.rst +++ /dev/null @@ -1,29 +0,0 @@ -artlib.hierarchical package -=========================== - -Submodules ----------- - -artlib.hierarchical.DeepARTMAP module -------------------------------------- - -.. automodule:: artlib.hierarchical.DeepARTMAP - :members: - :undoc-members: - :show-inheritance: - -artlib.hierarchical.SMART module --------------------------------- - -.. automodule:: artlib.hierarchical.SMART - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: artlib.hierarchical - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/artlib.reinforcement.rst b/docs/source/artlib.reinforcement.rst deleted file mode 100644 index d9a230a..0000000 --- a/docs/source/artlib.reinforcement.rst +++ /dev/null @@ -1,29 +0,0 @@ -artlib.reinforcement package -============================ - -Submodules ----------- - -artlib.reinforcement.FALCON module ----------------------------------- - -.. automodule:: artlib.reinforcement.FALCON - :members: - :undoc-members: - :show-inheritance: - -artlib.reinforcement.TDFALCON module ------------------------------------- - -.. automodule:: artlib.reinforcement.TDFALCON - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: artlib.reinforcement - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/artlib.rst b/docs/source/artlib.rst deleted file mode 100644 index b29b29d..0000000 --- a/docs/source/artlib.rst +++ /dev/null @@ -1,28 +0,0 @@ -artlib package -============== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - artlib.biclustering - artlib.common - artlib.cvi - artlib.elementary - artlib.fusion - artlib.hierarchical - artlib.reinforcement - artlib.supervised - artlib.topological - - -Module contents ---------------- - -.. automodule:: artlib - :members: - :undoc-members: - :show-inheritance: - :exclude-members: compliment_code, normalize diff --git a/docs/source/artlib.supervised.rst b/docs/source/artlib.supervised.rst deleted file mode 100644 index fb16080..0000000 --- a/docs/source/artlib.supervised.rst +++ /dev/null @@ -1,29 +0,0 @@ -artlib.supervised package -========================= - -Submodules ----------- - -artlib.supervised.ARTMAP module -------------------------------- - -.. automodule:: artlib.supervised.ARTMAP - :members: - :undoc-members: - :show-inheritance: - -artlib.supervised.SimpleARTMAP module -------------------------------------- - -.. automodule:: artlib.supervised.SimpleARTMAP - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: artlib.supervised - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/artlib.topological.rst b/docs/source/artlib.topological.rst deleted file mode 100644 index bce9cc2..0000000 --- a/docs/source/artlib.topological.rst +++ /dev/null @@ -1,21 +0,0 @@ -artlib.topological package -========================== - -Submodules ----------- - -artlib.topological.TopoART module ---------------------------------- - -.. automodule:: artlib.topological.TopoART - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: artlib.topological - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/conf.py b/docs/source/conf.py index f127d00..3551048 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,18 +14,30 @@ # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = ['sphinx.ext.autodoc', 'autoapi.extension', 'sphinx.ext.napoleon', 'myst_parser'] +extensions = ['sphinx.ext.autodoc', 'autoapi.extension', 'sphinx.ext.napoleon', 'myst_parser', 'sphinx.ext.intersphinx'] templates_path = ['_templates'] -exclude_patterns = ['artlib/experimental/*'] +exclude_patterns = ['artlib/experimental/*', '../../artlib/experimental/*'] autoapi_type = 'python' autoapi_dirs = ['../../artlib'] # Adjust this to point to your source code directory +autoapi_ignore = ['*/experimental', '*/experimental/*'] + + +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), + 'sklearn': ('https://scikit-learn.org/stable/', None) +} + +suppress_warnings = ['ref.duplicate', 'duplicate.object', "myst.duplicate_def"] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = 'sphinx_rtd_theme' -html_static_path = ['_static'] +html_static_path = ['../_static'] + + + diff --git a/docs/source/index.rst b/docs/source/index.rst index ce1fb04..48da726 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -12,6 +12,4 @@ AdaptiveResonanceLib documentation .. toctree:: :maxdepth: 2 - :caption: Contents: - - artlib + :caption: Contents: \ No newline at end of file diff --git a/docs/source/modules.rst b/docs/source/modules.rst deleted file mode 100644 index b943c52..0000000 --- a/docs/source/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -artlib -====== - -.. toctree:: - :maxdepth: 4 - - artlib From 1d7a358a9c5c78c760374092bcdfb31fd06a79be Mon Sep 17 00:00:00 2001 From: niklas melton Date: Tue, 15 Oct 2024 19:35:46 -0500 Subject: [PATCH 6/8] readthedocs working --- artlib/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/artlib/__init__.py b/artlib/__init__.py index 5e2d787..5cd96db 100644 --- a/artlib/__init__.py +++ b/artlib/__init__.py @@ -23,8 +23,7 @@ from artlib.fusion.FusionART import FusionART -from artlib.reinforcement.FALCON import FALCON -from artlib.reinforcement.TDFALCON import TD_FALCON +from artlib.reinforcement.FALCON import FALCON, TD_FALCON from artlib.biclustering.BARTMAP import BARTMAP From 41a19cc1472ea97a35679eb76c1382f19ba8cfe3 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 01:03:17 -0500 Subject: [PATCH 7/8] add section intro and readme to readthedocs --- README.md | 20 ++++- artlib/__init__.py | 17 +++- artlib/biclustering/__init__.py | 12 +++ artlib/common/__init__.py | 4 + artlib/cvi/__init__.py | 13 ++++ artlib/elementary/__init__.py | 4 + artlib/experimental/__init__.py | 3 + artlib/fusion/__init__.py | 14 ++++ artlib/hierarchical/__init__.py | 12 +++ artlib/reinforcement/__init__.py | 17 ++++ artlib/supervised/__init__.py | 13 ++++ .../DualVigilanceART.py | 0 artlib/topological/__init__.py | 15 ++++ docs/source/artlib.biclustering.rst | 21 +++++ docs/source/artlib.common.rst | 53 +++++++++++++ docs/source/artlib.cvi.iCVIs.rst | 21 +++++ docs/source/artlib.cvi.rst | 37 +++++++++ docs/source/artlib.elementary.rst | 77 +++++++++++++++++++ docs/source/artlib.experimental.rst | 37 +++++++++ docs/source/artlib.fusion.rst | 21 +++++ docs/source/artlib.hierarchical.rst | 29 +++++++ docs/source/artlib.reinforcement.rst | 21 +++++ docs/source/artlib.rst | 27 +++++++ docs/source/artlib.supervised.rst | 29 +++++++ docs/source/artlib.topological.rst | 29 +++++++ docs/source/available_models.rst | 5 ++ docs/source/comparison.rst | 5 ++ docs/source/conf.py | 32 +++++++- docs/source/contact.rst | 5 ++ docs/source/contributing.rst | 5 ++ docs/source/examples.rst | 5 ++ docs/source/index.rst | 28 ++++++- docs/source/installation.rst | 5 ++ docs/source/license.rst | 5 ++ docs/source/modules.rst | 7 ++ docs/source/quick_start.rst | 5 ++ 36 files changed, 647 insertions(+), 6 deletions(-) rename artlib/{elementary => topological}/DualVigilanceART.py (100%) create mode 100644 docs/source/artlib.biclustering.rst create mode 100644 docs/source/artlib.common.rst create mode 100644 docs/source/artlib.cvi.iCVIs.rst create mode 100644 docs/source/artlib.cvi.rst create mode 100644 docs/source/artlib.elementary.rst create mode 100644 docs/source/artlib.experimental.rst create mode 100644 docs/source/artlib.fusion.rst create mode 100644 docs/source/artlib.hierarchical.rst create mode 100644 docs/source/artlib.reinforcement.rst create mode 100644 docs/source/artlib.rst create mode 100644 docs/source/artlib.supervised.rst create mode 100644 docs/source/artlib.topological.rst create mode 100644 docs/source/available_models.rst create mode 100644 docs/source/comparison.rst create mode 100644 docs/source/contact.rst create mode 100644 docs/source/contributing.rst create mode 100644 docs/source/examples.rst create mode 100644 docs/source/installation.rst create mode 100644 docs/source/license.rst create mode 100644 docs/source/modules.rst create mode 100644 docs/source/quick_start.rst diff --git a/README.md b/README.md index 9d60629..f8f5dd6 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Welcome to AdaptiveResonanceLib, a comprehensive and modular Python library for Adaptive Resonance Theory (ART) algorithms. Based on scikit-learn, our library offers a wide range of ART models designed for both researchers and practitioners in the field of machine learning and neural networks. Whether you're working on classification, clustering, or pattern recognition, AdaptiveResonanceLib provides the tools you need to implement ART algorithms efficiently and effectively. + ## Available Models AdaptiveResonanceLib includes implementations for the following ART models: @@ -37,12 +38,17 @@ AdaptiveResonanceLib includes implementations for the following ART models: - TD-FALCON - #### Biclustering - Biclustering ARTMAP + + + ## Comparison of Elementary Models [comment]: <> (![Comparison of Elementary Images](https://github.com/NiklasMelton/AdaptiveResonanceLib/raw/main/docs/_static/comparison_of_elementary_methods.jpg?raw=true")) ![Comparison of Elementary Images](https://github.com/NiklasMelton/AdaptiveResonanceLib/raw/main/img/comparison_of_elementary_methods.jpg?raw=true") + + ## Installation To install AdaptiveResonanceLib, simply use pip: @@ -58,7 +64,9 @@ pip install artlib ``` Ensure you have Python 3.9 or newer installed. + + ## Quick Start Here's a quick example of how to use AdaptiveResonanceLib with the Fuzzy ART model: @@ -81,28 +89,38 @@ model.fit(train_X) predictions = model.predict(test_X) ``` -Replace `params` with the parameters appropriate for your use case. + + ## Documentation For more detailed documentation, including the full list of parameters for each model, visit our [documentation page](https://github.com/NiklasMelton/AdaptiveResonanceLib). + + ## Examples For examples of how to use each model in AdaptiveResonanceLib, check out the `/examples` directory in our repository. + + ## Contributing We welcome contributions to AdaptiveResonanceLib! If you have suggestions for improvements, or if you'd like to add more ART models, please see our `CONTRIBUTING.md` file for guidelines on how to contribute. You can also join our [Discord server](https://discord.gg/E465HBwEuN) and participate directly in the discussion. + + ## License AdaptiveResonanceLib is open source and available under the MIT license. See the `LICENSE` file for more info. + + ## Contact For questions and support, please open an issue in the GitHub issue tracker or message us on our [Discord server](https://discord.gg/E465HBwEuN). We'll do our best to assist you. Happy Modeling with AdaptiveResonanceLib! + diff --git a/artlib/__init__.py b/artlib/__init__.py index 5cd96db..59eab5d 100644 --- a/artlib/__init__.py +++ b/artlib/__init__.py @@ -1,3 +1,18 @@ +""" +Adaptive Resonance Theory (ART) is a cognitive and neural network model that explains how the brain learns to recognize +patterns while maintaining stability in the face of new, potentially conflicting information. ART networks are known for +their ability to perform unsupervised learning and adaptively categorize data without forgetting previously learned +patterns, a feature known as "plasticity-stability balance." + +The ART modules provided here support classification, clustering, and reinforcement learning tasks by dynamically +adjusting to incoming data streams. They also offer advanced capabilities, including hierarchical clustering, +topological clustering, data fusion, and regression, enabling flexible exploration of complex data structures. + +`Adaptive Resonance Theory `_ + +""" + + from artlib.common.BaseART import BaseART from artlib.common.BaseARTMAP import BaseARTMAP from artlib.common.utils import normalize, compliment_code, de_compliment_code, de_normalize @@ -6,7 +21,6 @@ from artlib.elementary.ART1 import ART1 from artlib.elementary.ART2 import ART2A from artlib.elementary.BayesianART import BayesianART -from artlib.elementary.DualVigilanceART import DualVigilanceART from artlib.elementary.EllipsoidART import EllipsoidART from artlib.elementary.GaussianART import GaussianART from artlib.elementary.FuzzyART import FuzzyART @@ -28,6 +42,7 @@ from artlib.biclustering.BARTMAP import BARTMAP from artlib.topological.TopoART import TopoART +from artlib.topological.DualVigilanceART import DualVigilanceART __all__ = [ "BaseART", diff --git a/artlib/biclustering/__init__.py b/artlib/biclustering/__init__.py index e69de29..4b3a0d0 100644 --- a/artlib/biclustering/__init__.py +++ b/artlib/biclustering/__init__.py @@ -0,0 +1,12 @@ +""" +Biclustering is a data mining technique used to find subgroups of rows and columns in a matrix that exhibit similar +patterns. Unlike traditional clustering, which only groups rows or columns independently, biclustering simultaneously +clusters both dimensions, allowing for the discovery of local patterns in the data. It is commonly used in fields such +as bioinformatics, particularly for gene expression data analysis, where subsets of genes and conditions may exhibit +correlated behavior. + +The ART module contained herein allows for ART to solve biclustering problems. + +`Biclustering `_ + +""" \ No newline at end of file diff --git a/artlib/common/__init__.py b/artlib/common/__init__.py index e69de29..10c2ceb 100644 --- a/artlib/common/__init__.py +++ b/artlib/common/__init__.py @@ -0,0 +1,4 @@ +""" +This module implements several functions and classes used across ARTLib as well as some functions like VAT which are +useful in a variety of contexts. +""" \ No newline at end of file diff --git a/artlib/cvi/__init__.py b/artlib/cvi/__init__.py index e69de29..3fded22 100644 --- a/artlib/cvi/__init__.py +++ b/artlib/cvi/__init__.py @@ -0,0 +1,13 @@ +""" +Cluster validity indices are metrics used to evaluate the quality of clustering results. These indices help to +determine the optimal number of clusters and assess the performance of clustering algorithms by measuring the +compactness and separation of the clusters. Common cluster validity indices include the Silhouette score, +Davies-Bouldin index, and Dunn index. These indices play an important role in unsupervised learning tasks where true +labels are not available for evaluation. + +This module implements CVI-driven ART modules which utilize the CVI to inform clustering; often resulting in objectively +superior results. + +`Cluster validity indices `_ + +""" \ No newline at end of file diff --git a/artlib/elementary/__init__.py b/artlib/elementary/__init__.py index e69de29..1eebf63 100644 --- a/artlib/elementary/__init__.py +++ b/artlib/elementary/__init__.py @@ -0,0 +1,4 @@ +""" +This module contains elementary ART modules which are those ART modules that do not implement an abstraction layer on +top of another module. +""" \ No newline at end of file diff --git a/artlib/experimental/__init__.py b/artlib/experimental/__init__.py index e69de29..6786ead 100644 --- a/artlib/experimental/__init__.py +++ b/artlib/experimental/__init__.py @@ -0,0 +1,3 @@ +""" +Code within this module is highly experimental and therefore comes with no warranty on its use. +""" \ No newline at end of file diff --git a/artlib/fusion/__init__.py b/artlib/fusion/__init__.py index e69de29..2bd37d9 100644 --- a/artlib/fusion/__init__.py +++ b/artlib/fusion/__init__.py @@ -0,0 +1,14 @@ +""" +Data fusion is the process of integrating multiple data sources to produce more consistent, accurate, and useful +information than would be possible when using the sources independently. It is used in a wide range of fields, +including sensor networks, image processing, and decision-making systems. + +The ART module contained herein allows for the fusion of an arbitrary number of data channels. This structure not only +supports classification tasks but also enables it to be used for regression on polytonic (as opposed to monotonic) +problems. By leveraging data from multiple channels, the module improves regression accuracy by combining diverse +information sources, making it particularly suited for complex problems where single-channel approaches fall short. + +This is the recommended module for such regression problems. + +`Data fusion `_ +""" \ No newline at end of file diff --git a/artlib/hierarchical/__init__.py b/artlib/hierarchical/__init__.py index e69de29..af78747 100644 --- a/artlib/hierarchical/__init__.py +++ b/artlib/hierarchical/__init__.py @@ -0,0 +1,12 @@ +""" +Hierarchical clustering is a method of cluster analysis that seeks to build a hierarchy of clusters. In divisive +clustering, also known as top-down clustering, the process starts with all data points in a single cluster and +recursively splits it into smaller clusters. This contrasts with agglomerative (bottom-up) clustering, which starts +with individual points and merges them. Divisive clustering is useful when trying to divide data into broad categories +before refining into finer subcategories. + +The modules herein are exclusively divisive clustering approaches for ART + +`Divisive clustering `_ + +""" \ No newline at end of file diff --git a/artlib/reinforcement/__init__.py b/artlib/reinforcement/__init__.py index e69de29..0df7fc7 100644 --- a/artlib/reinforcement/__init__.py +++ b/artlib/reinforcement/__init__.py @@ -0,0 +1,17 @@ +""" +Reinforcement learning (RL) is a type of machine learning where agents learn to make decisions by interacting with an +environment and receiving feedback in the form of rewards or penalties. The SARSA (State-Action-Reward-State-Action) +algorithm is an on-policy RL method that updates the agent’s policy based on the action actually taken. This contrasts +with Q-learning, which is off-policy and learns the optimal action independently of the agent’s current policy. + +Reactive learning, on the other hand, is a more straightforward approach where decisions are made solely based on +immediate observations, without the complex state-action-reward feedback loop typical of RL models like SARSA or +Q-learning. It lacks the depth of planning and long-term reward optimization seen in traditional RL. + + +The modules herein only provide for reactive and SARSA style learning. + +`SARSA `_ +`Reactive agents `_ + +""" \ No newline at end of file diff --git a/artlib/supervised/__init__.py b/artlib/supervised/__init__.py index e69de29..dafe66d 100644 --- a/artlib/supervised/__init__.py +++ b/artlib/supervised/__init__.py @@ -0,0 +1,13 @@ +""" +Supervised learning is a type of machine learning where a model is trained on labeled data, meaning that the input data +is paired with the correct output. The goal is for the model to learn the relationship between inputs and outputs so +it can make accurate predictions on new, unseen data. Supervised learning tasks can generally be categorized into +two types: classification and regression. + +Classification involves predicting discrete labels or categories, such as spam detection or image recognition. +Regression, on the other hand, deals with predicting continuous values, like stock prices or temperature. Both +classification and regression are essential tools in many real-world applications. + +`Supervised learning `_ + +""" \ No newline at end of file diff --git a/artlib/elementary/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py similarity index 100% rename from artlib/elementary/DualVigilanceART.py rename to artlib/topological/DualVigilanceART.py diff --git a/artlib/topological/__init__.py b/artlib/topological/__init__.py index e69de29..bafc184 100644 --- a/artlib/topological/__init__.py +++ b/artlib/topological/__init__.py @@ -0,0 +1,15 @@ +""" +Topological clustering is a method of grouping data points based on their topological structure, capturing the shape or +connectivity of the data rather than relying solely on distance measures. This approach is particularly useful when +the data has a non-linear structure or when traditional clustering algorithms fail to capture the intrinsic geometry +of the data. Topological clustering techniques, such as hierarchical clustering and Mapper, are often used in fields +like data analysis and computational topology. + +The two modules hereing provide contrasting advantages. TopoART allows for the creation of an adjacency matrix which +can be useful when clusters overlap or are in close proximity. Dual Vigilance ART allows for the abstract merging of +many smaller clusters and is well suited to problems where the clusters take-on complex geometries where other +clustering approaches would fail. + +`Topological clustering `_ + +""" \ No newline at end of file diff --git a/docs/source/artlib.biclustering.rst b/docs/source/artlib.biclustering.rst new file mode 100644 index 0000000..a7b0ddb --- /dev/null +++ b/docs/source/artlib.biclustering.rst @@ -0,0 +1,21 @@ +artlib.biclustering package +=========================== + +Submodules +---------- + +artlib.biclustering.BARTMAP module +---------------------------------- + +.. automodule:: artlib.biclustering.BARTMAP + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.biclustering + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.common.rst b/docs/source/artlib.common.rst new file mode 100644 index 0000000..041e0b2 --- /dev/null +++ b/docs/source/artlib.common.rst @@ -0,0 +1,53 @@ +artlib.common package +===================== + +Submodules +---------- + +artlib.common.BaseART module +---------------------------- + +.. automodule:: artlib.common.BaseART + :members: + :undoc-members: + :show-inheritance: + +artlib.common.BaseARTMAP module +------------------------------- + +.. automodule:: artlib.common.BaseARTMAP + :members: + :undoc-members: + :show-inheritance: + +artlib.common.VAT module +------------------------ + +.. automodule:: artlib.common.VAT + :members: + :undoc-members: + :show-inheritance: + +artlib.common.utils module +-------------------------- + +.. automodule:: artlib.common.utils + :members: + :undoc-members: + :show-inheritance: + +artlib.common.visualization module +---------------------------------- + +.. automodule:: artlib.common.visualization + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.common + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.cvi.iCVIs.rst b/docs/source/artlib.cvi.iCVIs.rst new file mode 100644 index 0000000..78c93dc --- /dev/null +++ b/docs/source/artlib.cvi.iCVIs.rst @@ -0,0 +1,21 @@ +artlib.cvi.iCVIs package +======================== + +Submodules +---------- + +artlib.cvi.iCVIs.CalinkskiHarabasz module +----------------------------------------- + +.. automodule:: artlib.cvi.iCVIs.CalinkskiHarabasz + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.cvi.iCVIs + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.cvi.rst b/docs/source/artlib.cvi.rst new file mode 100644 index 0000000..2d5a886 --- /dev/null +++ b/docs/source/artlib.cvi.rst @@ -0,0 +1,37 @@ +artlib.cvi package +================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + artlib.cvi.iCVIs + +Submodules +---------- + +artlib.cvi.CVIART module +------------------------ + +.. automodule:: artlib.cvi.CVIART + :members: + :undoc-members: + :show-inheritance: + +artlib.cvi.iCVIFuzzyArt module +------------------------------ + +.. automodule:: artlib.cvi.iCVIFuzzyArt + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.cvi + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.elementary.rst b/docs/source/artlib.elementary.rst new file mode 100644 index 0000000..c281ed4 --- /dev/null +++ b/docs/source/artlib.elementary.rst @@ -0,0 +1,77 @@ +artlib.elementary package +========================= + +Submodules +---------- + +artlib.elementary.ART1 module +----------------------------- + +.. automodule:: artlib.elementary.ART1 + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.ART2 module +----------------------------- + +.. automodule:: artlib.elementary.ART2 + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.BayesianART module +------------------------------------ + +.. automodule:: artlib.elementary.BayesianART + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.EllipsoidART module +------------------------------------- + +.. automodule:: artlib.elementary.EllipsoidART + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.FuzzyART module +--------------------------------- + +.. automodule:: artlib.elementary.FuzzyART + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.GaussianART module +------------------------------------ + +.. automodule:: artlib.elementary.GaussianART + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.HypersphereART module +--------------------------------------- + +.. automodule:: artlib.elementary.HypersphereART + :members: + :undoc-members: + :show-inheritance: + +artlib.elementary.QuadraticNeuronART module +------------------------------------------- + +.. automodule:: artlib.elementary.QuadraticNeuronART + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.elementary + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.experimental.rst b/docs/source/artlib.experimental.rst new file mode 100644 index 0000000..b4c1a5d --- /dev/null +++ b/docs/source/artlib.experimental.rst @@ -0,0 +1,37 @@ +artlib.experimental package +=========================== + +Submodules +---------- + +artlib.experimental.ConvexHullART module +---------------------------------------- + +.. automodule:: artlib.experimental.ConvexHullART + :members: + :undoc-members: + :show-inheritance: + +artlib.experimental.SeqART module +--------------------------------- + +.. automodule:: artlib.experimental.SeqART + :members: + :undoc-members: + :show-inheritance: + +artlib.experimental.merging module +---------------------------------- + +.. automodule:: artlib.experimental.merging + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.experimental + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.fusion.rst b/docs/source/artlib.fusion.rst new file mode 100644 index 0000000..b7a8f93 --- /dev/null +++ b/docs/source/artlib.fusion.rst @@ -0,0 +1,21 @@ +artlib.fusion package +===================== + +Submodules +---------- + +artlib.fusion.FusionART module +------------------------------ + +.. automodule:: artlib.fusion.FusionART + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.fusion + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.hierarchical.rst b/docs/source/artlib.hierarchical.rst new file mode 100644 index 0000000..5e6bfb3 --- /dev/null +++ b/docs/source/artlib.hierarchical.rst @@ -0,0 +1,29 @@ +artlib.hierarchical package +=========================== + +Submodules +---------- + +artlib.hierarchical.DeepARTMAP module +------------------------------------- + +.. automodule:: artlib.hierarchical.DeepARTMAP + :members: + :undoc-members: + :show-inheritance: + +artlib.hierarchical.SMART module +-------------------------------- + +.. automodule:: artlib.hierarchical.SMART + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.hierarchical + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.reinforcement.rst b/docs/source/artlib.reinforcement.rst new file mode 100644 index 0000000..c051ffc --- /dev/null +++ b/docs/source/artlib.reinforcement.rst @@ -0,0 +1,21 @@ +artlib.reinforcement package +============================ + +Submodules +---------- + +artlib.reinforcement.FALCON module +---------------------------------- + +.. automodule:: artlib.reinforcement.FALCON + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.reinforcement + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.rst b/docs/source/artlib.rst new file mode 100644 index 0000000..0bb6975 --- /dev/null +++ b/docs/source/artlib.rst @@ -0,0 +1,27 @@ +artlib package +============== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + artlib.biclustering + artlib.common + artlib.cvi + artlib.elementary + artlib.experimental + artlib.fusion + artlib.hierarchical + artlib.reinforcement + artlib.supervised + artlib.topological + +Module contents +--------------- + +.. automodule:: artlib + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.supervised.rst b/docs/source/artlib.supervised.rst new file mode 100644 index 0000000..fb16080 --- /dev/null +++ b/docs/source/artlib.supervised.rst @@ -0,0 +1,29 @@ +artlib.supervised package +========================= + +Submodules +---------- + +artlib.supervised.ARTMAP module +------------------------------- + +.. automodule:: artlib.supervised.ARTMAP + :members: + :undoc-members: + :show-inheritance: + +artlib.supervised.SimpleARTMAP module +------------------------------------- + +.. automodule:: artlib.supervised.SimpleARTMAP + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.supervised + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/artlib.topological.rst b/docs/source/artlib.topological.rst new file mode 100644 index 0000000..0e2424a --- /dev/null +++ b/docs/source/artlib.topological.rst @@ -0,0 +1,29 @@ +artlib.topological package +========================== + +Submodules +---------- + +artlib.topological.DualVigilanceART module +------------------------------------------ + +.. automodule:: artlib.topological.DualVigilanceART + :members: + :undoc-members: + :show-inheritance: + +artlib.topological.TopoART module +--------------------------------- + +.. automodule:: artlib.topological.TopoART + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: artlib.topological + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/available_models.rst b/docs/source/available_models.rst new file mode 100644 index 0000000..fcc20cf --- /dev/null +++ b/docs/source/available_models.rst @@ -0,0 +1,5 @@ +.. include:: ../../README.md + :parser: myst_parser.sphinx_ + :start-after: + :end-before: + diff --git a/docs/source/comparison.rst b/docs/source/comparison.rst new file mode 100644 index 0000000..3765d81 --- /dev/null +++ b/docs/source/comparison.rst @@ -0,0 +1,5 @@ +.. include:: ../../README.md + :parser: myst_parser.sphinx_ + :start-after: + :end-before: + diff --git a/docs/source/conf.py b/docs/source/conf.py index 3551048..6505a53 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,7 +14,18 @@ # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = ['sphinx.ext.autodoc', 'autoapi.extension', 'sphinx.ext.napoleon', 'myst_parser', 'sphinx.ext.intersphinx'] +extensions = [ + 'sphinx.ext.autodoc', + 'autoapi.extension', + 'sphinx.ext.napoleon', + 'myst_parser', + 'sphinx.ext.intersphinx', +] + +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} templates_path = ['_templates'] exclude_patterns = ['artlib/experimental/*', '../../artlib/experimental/*'] @@ -22,6 +33,23 @@ autoapi_type = 'python' autoapi_dirs = ['../../artlib'] # Adjust this to point to your source code directory autoapi_ignore = ['*/experimental', '*/experimental/*'] +autoapi_python_class_content = 'both' + +myst_enable_extensions = [ + "colon_fence", + "deflist", + "html_admonition", + "html_image", + "linkify", + "replacements", + "smartquotes", + "strikethrough", + "substitution", + "tasklist", + "attrs_block", + "attrs_inline", + "fieldlist", +] intersphinx_mapping = { @@ -29,7 +57,7 @@ 'sklearn': ('https://scikit-learn.org/stable/', None) } -suppress_warnings = ['ref.duplicate', 'duplicate.object', "myst.duplicate_def"] +suppress_warnings = ['ref.duplicate', 'duplicate.object', 'myst.duplicate_def', 'ref.python'] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output diff --git a/docs/source/contact.rst b/docs/source/contact.rst new file mode 100644 index 0000000..0fc9e70 --- /dev/null +++ b/docs/source/contact.rst @@ -0,0 +1,5 @@ +.. include:: ../../README.md + :parser: myst_parser.sphinx_ + :start-after: + :end-before: + diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst new file mode 100644 index 0000000..38baf90 --- /dev/null +++ b/docs/source/contributing.rst @@ -0,0 +1,5 @@ +.. include:: ../../README.md + :parser: myst_parser.sphinx_ + :start-after: + :end-before: + diff --git a/docs/source/examples.rst b/docs/source/examples.rst new file mode 100644 index 0000000..5983c5b --- /dev/null +++ b/docs/source/examples.rst @@ -0,0 +1,5 @@ +.. include:: ../../README.md + :parser: myst_parser.sphinx_ + :start-after: + :end-before: + diff --git a/docs/source/index.rst b/docs/source/index.rst index 48da726..de3a984 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,7 +3,7 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -AdaptiveResonanceLib documentation +AdaptiveResonanceLib Home ================================== .. include:: ../../README.md @@ -12,4 +12,28 @@ AdaptiveResonanceLib documentation .. toctree:: :maxdepth: 2 - :caption: Contents: \ No newline at end of file + :caption: Main Contents: + + installation + available_models + comparison + quick_start + examples + contributing + contact + license + +.. toctree:: + :maxdepth: 2 + :caption: API Reference: + + artlib + artlib.biclustering + artlib.common + artlib.cvi + artlib.elementary + artlib.fusion + artlib.hierarchical + artlib.reinforcement + artlib.supervised + artlib.topological \ No newline at end of file diff --git a/docs/source/installation.rst b/docs/source/installation.rst new file mode 100644 index 0000000..5a58eb8 --- /dev/null +++ b/docs/source/installation.rst @@ -0,0 +1,5 @@ +.. include:: ../../README.md + :parser: myst_parser.sphinx_ + :start-after: + :end-before: + diff --git a/docs/source/license.rst b/docs/source/license.rst new file mode 100644 index 0000000..9ef5fea --- /dev/null +++ b/docs/source/license.rst @@ -0,0 +1,5 @@ +.. include:: ../../README.md + :parser: myst_parser.sphinx_ + :start-after: + :end-before: + diff --git a/docs/source/modules.rst b/docs/source/modules.rst new file mode 100644 index 0000000..b943c52 --- /dev/null +++ b/docs/source/modules.rst @@ -0,0 +1,7 @@ +artlib +====== + +.. toctree:: + :maxdepth: 4 + + artlib diff --git a/docs/source/quick_start.rst b/docs/source/quick_start.rst new file mode 100644 index 0000000..d9f1602 --- /dev/null +++ b/docs/source/quick_start.rst @@ -0,0 +1,5 @@ +.. include:: ../../README.md + :parser: myst_parser.sphinx_ + :start-after: + :end-before: + From 41074d24b483bb74b72aa5a507e7eb1ffa4e791b Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 01:03:35 -0500 Subject: [PATCH 8/8] add section intro and readme to readthedocs --- unit_tests/test_DualVigilanceART.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit_tests/test_DualVigilanceART.py b/unit_tests/test_DualVigilanceART.py index 2db253b..f9e10d3 100644 --- a/unit_tests/test_DualVigilanceART.py +++ b/unit_tests/test_DualVigilanceART.py @@ -1,7 +1,7 @@ import pytest import numpy as np from typing import Optional -from artlib.elementary.DualVigilanceART import DualVigilanceART +from artlib.topological.DualVigilanceART import DualVigilanceART from artlib.common.BaseART import BaseART # Mock BaseART class for testing purposes