From 1ce9ff6a7179289408e08a12b60a07c0df487e92 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 3 Oct 2024 01:02:42 -0500 Subject: [PATCH 001/139] citation workflow --- .github/workflows/citation-update.yml | 36 +++++++++++++++++++++++++++ CITATION.cff | 19 ++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 .github/workflows/citation-update.yml create mode 100644 CITATION.cff diff --git a/.github/workflows/citation-update.yml b/.github/workflows/citation-update.yml new file mode 100644 index 0000000..e8e720d --- /dev/null +++ b/.github/workflows/citation-update.yml @@ -0,0 +1,36 @@ +name: Update CITATION.cff on Release +on: + release: + types: [published] + +jobs: + update_citation: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Get Release Data + id: release_info + run: | + echo "RELEASE_TAG=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_ENV + + - name: Fetch DOI from Zenodo + run: | + # Replace with actual API request or data fetch logic + # Example command to fetch the DOI for the latest release via Zenodo API + curl -s "https://zenodo.org/api/records?query=AdaptiveResonanceLib" | jq '.hits.hits[0].doi' > doi.txt + + - name: Update CITATION.cff + run: | + DOI=$(cat doi.txt) + sed -i "s/doi: .*/doi: \"$DOI\"/" CITATION.cff + sed -i "s/version: .*/version: \"$RELEASE_TAG\"/" CITATION.cff + + - name: Commit updated CITATION.cff + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add CITATION.cff + git commit -m "Update CITATION.cff with DOI and version for release $RELEASE_TAG" + git push origin main diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..e506f8b --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,19 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +title: "AdaptiveResonanceLib" +version: "0.1.2" +doi: "10.5281/zenodo.9999999" +authors: + - family-names: "Melton" + given-names: "Niklas" + orcid: "0000-0001-9625-7086" +date-released: 2024-10-03 +url: "https://github.com/NiklasMelton/AdaptiveResonanceLib" +repository-code: "https://github.com/NiklasMelton/AdaptiveResonanceLib" +license: "MIT" +keywords: + - "adaptive resonance theory" + - "ART" + - "machine learning" + - "clustering" + - "neural networks" From 46e5432140c1e6437b427d47c6cf7a6b7882a1d7 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 4 Oct 2024 17:47:13 -0500 Subject: [PATCH 002/139] add first unit test --- .github/workflows/citation-update.yml | 36 +++++++ CITATION.cff | 19 ++++ unit_tests/test_FuzzyART.py | 135 ++++++++++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 .github/workflows/citation-update.yml create mode 100644 CITATION.cff create mode 100644 unit_tests/test_FuzzyART.py diff --git a/.github/workflows/citation-update.yml b/.github/workflows/citation-update.yml new file mode 100644 index 0000000..e8e720d --- /dev/null +++ b/.github/workflows/citation-update.yml @@ -0,0 +1,36 @@ +name: Update CITATION.cff on Release +on: + release: + types: [published] + +jobs: + update_citation: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Get Release Data + id: release_info + run: | + echo "RELEASE_TAG=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_ENV + + - name: Fetch DOI from Zenodo + run: | + # Replace with actual API request or data fetch logic + # Example command to fetch the DOI for the latest release via Zenodo API + curl -s "https://zenodo.org/api/records?query=AdaptiveResonanceLib" | jq '.hits.hits[0].doi' > doi.txt + + - name: Update CITATION.cff + run: | + DOI=$(cat doi.txt) + sed -i "s/doi: .*/doi: \"$DOI\"/" CITATION.cff + sed -i "s/version: .*/version: \"$RELEASE_TAG\"/" CITATION.cff + + - name: Commit updated CITATION.cff + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add CITATION.cff + git commit -m "Update CITATION.cff with DOI and version for release $RELEASE_TAG" + git push origin main diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..e506f8b --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,19 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +title: "AdaptiveResonanceLib" +version: "0.1.2" +doi: "10.5281/zenodo.9999999" +authors: + - family-names: "Melton" + given-names: "Niklas" + orcid: "0000-0001-9625-7086" +date-released: 2024-10-03 +url: "https://github.com/NiklasMelton/AdaptiveResonanceLib" +repository-code: "https://github.com/NiklasMelton/AdaptiveResonanceLib" +license: "MIT" +keywords: + - "adaptive resonance theory" + - "ART" + - "machine learning" + - "clustering" + - "neural networks" diff --git a/unit_tests/test_FuzzyART.py b/unit_tests/test_FuzzyART.py new file mode 100644 index 0000000..7b38cb4 --- /dev/null +++ b/unit_tests/test_FuzzyART.py @@ -0,0 +1,135 @@ +import pytest +import numpy as np +from unittest.mock import MagicMock +from artlib.elementary.FuzzyART import FuzzyART + +# Assuming BaseART is imported and available in the current namespace + +@pytest.fixture +def art_model(): + # Fixture that sets up the model before each test + params = { + 'rho': 0.5, + 'alpha': 0.0, + 'beta': 1.0, + } + return FuzzyART(**params) + +def test_initialization(art_model): + # Test that the ART model initializes correctly + assert art_model.params == {'rho': 0.5, 'alpha': 0.0, 'beta': 1.0} + assert art_model.sample_counter_ == 0 + assert art_model.weight_sample_counter_ == [] + +def test_set_get_params(art_model): + # Test set_params and get_params functions + new_params = {'rho': 0.7, 'alpha': 0.05, 'beta': 0.9} + art_model.set_params(**new_params) + assert art_model.get_params() == new_params + assert art_model.rho == 0.7 + assert art_model.alpha == 0.05 + assert art_model.beta == 0.9 + +def test_attribute_access(art_model): + # Test dynamic attribute access and setting using params + assert art_model.rho == 0.5 + art_model.rho = 0.8 + assert art_model.rho == 0.8 + +def test_invalid_attribute(art_model): + # Test accessing an invalid attribute + with pytest.raises(AttributeError): + art_model.non_existing_attribute + +def test_prepare_restore_data(art_model): + # Test data normalization and denormalization + X = np.array([[1, 2], [3, 4], [5, 6]]) + normalized_X = art_model.prepare_data(X) + restored_X = art_model.restore_data(normalized_X) + np.testing.assert_array_almost_equal(restored_X, X) + +def test_check_dimensions(art_model): + # Test check_dimensions with valid data + X = np.array([[1, 2], [3, 4]]) + art_model.check_dimensions(X) + assert art_model.dim_ == 2 + + # Test check_dimensions with invalid data (mismatch) + X_invalid = np.array([[1, 2, 3]]) + with pytest.raises(AssertionError): + art_model.check_dimensions(X_invalid) + +def test_match_tracking(art_model): + # Test match tracking with different methods + cache = {'match_criterion': 0.5} + art_model._match_tracking(cache, epsilon=0.01, params=art_model.params, method='MT+') + assert art_model.rho == 0.51 + + art_model._match_tracking(cache, epsilon=0.01, params=art_model.params, method='MT-') + assert art_model.rho == 0.49 + + art_model._match_tracking(cache, epsilon=0.01, params=art_model.params, method='MT0') + assert art_model.rho == 0.5 + + art_model._match_tracking(cache, epsilon=0.01, params=art_model.params, method='MT~') + assert art_model.rho == 0.5 + + art_model._match_tracking(cache, epsilon=0.01, params=art_model.params, method='MT1') + assert np.isinf(art_model.rho) + +def test_step_fit(art_model): + # Test step_fit for creating new clusters + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + X = art_model.prepare_data(X) + art_model.W = [] + art_model.new_weight = MagicMock(return_value=np.array([0.1, 0.2])) + art_model.add_weight = MagicMock() + art_model.update = MagicMock(return_value=np.array([0.15, 0.25])) + art_model.category_choice = MagicMock(return_value=(1.0, None)) + art_model.match_criterion_bin = MagicMock(return_value=(True, {})) + + label = art_model.step_fit(X[0]) + assert label == 0 + art_model.add_weight.assert_called_once() + +def test_partial_fit(art_model): + # Test partial_fit + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + X = art_model.prepare_data(X) + art_model.new_weight = MagicMock(return_value=np.array([0.1, 0.2])) + art_model.add_weight = MagicMock() + art_model.update = MagicMock(return_value=np.array([0.15, 0.25])) + art_model.category_choice = MagicMock(return_value=(1.0, None)) + art_model.match_criterion_bin = MagicMock(return_value=(True, {})) + + art_model.partial_fit(X) + art_model.add_weight.assert_called() + +def test_predict(art_model): + # Test predict function + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + X = art_model.prepare_data(X) + art_model.category_choice = MagicMock(return_value=(1.0, None)) + art_model.step_pred = MagicMock(return_value=0) + + labels = art_model.predict(X) + np.testing.assert_array_equal(labels, [0, 0]) + + +def test_clustering(art_model): + new_params = {'rho': 0.9, 'alpha': 0.05, 'beta': 1.0} + art_model.set_params(**new_params) + + data = np.array( + [ + [0.0, 0.0], + [0.0, 0.08], + [0.0, 1.0], + [1.0, 1.0], + [1.0, 0.0] + ] + ) + data = art_model.prepare_data(data) + labels = art_model.fit_predict(data) + + assert np.all(np.equal(labels, np.array([0, 0, 1, 2, 3]))) From 4d49e1c51df4a9f35f34c33123dbdcc4419d2f68 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 4 Oct 2024 17:53:56 -0500 Subject: [PATCH 003/139] create unit-test workflow --- .github/workflows/pytest.yml | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/pytest.yml diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 0000000..b372904 --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,37 @@ +name: Run Python Unit Tests + +on: + pull_request: + branches: + - main + - master # Adjust based on your main branch name + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' # Specify the Python version + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install poetry # If you're using Poetry + poetry install # Install dependencies from pyproject.toml + + - name: Run tests + run: | + pytest unit_tests/ --maxfail=5 --disable-warnings # Run tests from unit_tests/ directory + + - name: Publish test results + if: failure() + uses: actions/upload-artifact@v3 + with: + name: pytest-results + path: ./test-results/pytest-report.xml From 34c08bb4b2ffba8243f7972c38e1cc45c1207969 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 4 Oct 2024 17:54:16 -0500 Subject: [PATCH 004/139] create unit-test workflow --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index b372904..18ef1bb 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -4,7 +4,7 @@ on: pull_request: branches: - main - - master # Adjust based on your main branch name + - develop jobs: test: From 1f47ad4aad17d8f14ec5ba962a8bb95105c8e5ca Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 4 Oct 2024 17:59:14 -0500 Subject: [PATCH 005/139] create unit-test workflow --- .github/workflows/pytest.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 18ef1bb..282ccdc 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -17,13 +17,13 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' # Specify the Python version + python-version: '3.9' - name: Install dependencies run: | python -m pip install --upgrade pip - pip install poetry # If you're using Poetry - poetry install # Install dependencies from pyproject.toml + pip install poetry + poetry install --with dev # Install dependencies from pyproject.toml - name: Run tests run: | From 1db73dc08b38e1e1f112d0e3fe53e000592201a1 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 4 Oct 2024 18:01:26 -0500 Subject: [PATCH 006/139] create unit-test workflow --- .github/workflows/pytest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 282ccdc..93d9507 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -23,11 +23,11 @@ jobs: run: | python -m pip install --upgrade pip pip install poetry - poetry install --with dev # Install dependencies from pyproject.toml + poetry install --with dev # Install dependencies from pyproject.toml, including dev dependencies - name: Run tests run: | - pytest unit_tests/ --maxfail=5 --disable-warnings # Run tests from unit_tests/ directory + poetry run pytest unit_tests/ --maxfail=5 --disable-warnings # Run tests inside Poetry's virtual environment - name: Publish test results if: failure() From c1ecb8cee043494108e9e4951c975ec2491ffaf7 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 4 Oct 2024 18:03:40 -0500 Subject: [PATCH 007/139] create unit-test workflow --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 93d9507..7fb5907 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -27,7 +27,7 @@ jobs: - name: Run tests run: | - poetry run pytest unit_tests/ --maxfail=5 --disable-warnings # Run tests inside Poetry's virtual environment + poetry run pytest unit_tests/ --maxfail=5 --disable-warnings -v --tb=short # Add verbose and short traceback options - name: Publish test results if: failure() From 506f2fe91e920b4a02cec9b85d8dada04a21900f Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 4 Oct 2024 18:05:29 -0500 Subject: [PATCH 008/139] test a failure --- unit_tests/test_FuzzyART.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/unit_tests/test_FuzzyART.py b/unit_tests/test_FuzzyART.py index 7b38cb4..8812b1c 100644 --- a/unit_tests/test_FuzzyART.py +++ b/unit_tests/test_FuzzyART.py @@ -133,3 +133,7 @@ def test_clustering(art_model): labels = art_model.fit_predict(data) assert np.all(np.equal(labels, np.array([0, 0, 1, 2, 3]))) + + +def test_runner(): + assert False From 57c77e8ec513e05945453431323050af75d1826a Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 4 Oct 2024 18:06:39 -0500 Subject: [PATCH 009/139] remove test a failure --- unit_tests/test_FuzzyART.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/unit_tests/test_FuzzyART.py b/unit_tests/test_FuzzyART.py index 8812b1c..8512756 100644 --- a/unit_tests/test_FuzzyART.py +++ b/unit_tests/test_FuzzyART.py @@ -134,6 +134,3 @@ def test_clustering(art_model): assert np.all(np.equal(labels, np.array([0, 0, 1, 2, 3]))) - -def test_runner(): - assert False From a197b952200576f69c3c8e7f0de8a3291e1b7a1a Mon Sep 17 00:00:00 2001 From: niklas melton Date: Sat, 5 Oct 2024 03:12:28 -0500 Subject: [PATCH 010/139] test GaussianART --- unit_tests/test_GaussianART.py | 114 +++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 unit_tests/test_GaussianART.py diff --git a/unit_tests/test_GaussianART.py b/unit_tests/test_GaussianART.py new file mode 100644 index 0000000..a8739ee --- /dev/null +++ b/unit_tests/test_GaussianART.py @@ -0,0 +1,114 @@ +import pytest +import numpy as np +from unittest.mock import MagicMock +from artlib.elementary.GaussianART import GaussianART + +# Fixture to initialize a GaussianART instance for testing +@pytest.fixture +def art_model(): + rho = 0.7 + sigma_init = np.array([0.5, 0.5]) + alpha = 1e-5 + return GaussianART(rho=rho, sigma_init=sigma_init, alpha=alpha) + +def test_initialization(art_model): + # Test that the model initializes correctly + assert art_model.params['rho'] == 0.7 + assert np.array_equal(art_model.params['sigma_init'], np.array([0.5, 0.5])) + assert art_model.params['alpha'] == 1e-5 + assert art_model.sample_counter_ == 0 + assert art_model.weight_sample_counter_ == [] + +def test_validate_params(): + # Test the validate_params method + valid_params = { + "rho": 0.5, + "sigma_init": np.array([0.5, 0.5]), + "alpha": 1e-5 + } + GaussianART.validate_params(valid_params) + + invalid_params = { + "rho": 1.5, # Invalid vigilance parameter + "sigma_init": np.array([0.5, 0.5]), + "alpha": -1e-5 # Invalid alpha + } + with pytest.raises(AssertionError): + GaussianART.validate_params(invalid_params) + +def test_category_choice(art_model): + # Test the category_choice method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35, 2.0, 2.5, 0.5, 0.6, 1.2, 1.0, 5]) # Mock weight vector + art_model.W = [w] + params = { + "rho": 0.7, + "alpha": 1e-5 + } + + activation, cache = art_model.category_choice(i, w, params) + assert 'exp_dist_sig_dist' in cache + assert isinstance(activation, float) + +def test_match_criterion(art_model): + # Test the match_criterion method + cache = {"exp_dist_sig_dist": 0.8} + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35, 2.0, 2.5, 0.5, 0.6, 1.2, 1.0, 5]) # Mock weight vector + params = {"rho": 0.7} + + match_criterion, new_cache = art_model.match_criterion(i, w, params, cache=cache) + assert match_criterion == cache["exp_dist_sig_dist"] + +def test_update(art_model): + # Test the update method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35, 2.0, 2.5, 0.5, 0.6, 1.2, 1.0, 5]) # Mock weight vector + params = {"alpha": 1e-5} + + updated_weight = art_model.update(i, w, params) + assert updated_weight[-1] == 6 # Check that the sample count has been updated + +def test_new_weight(art_model): + # Test the new_weight method + i = np.array([0.2, 0.3]) + params = {"sigma_init": np.array([0.5, 0.5])} + + new_weight = art_model.new_weight(i, params) + assert len(new_weight) == 8 # Mean, sigma, inverse sigma, determinant, and count + +def test_get_cluster_centers(art_model): + # Test getting cluster centers + art_model.W = [np.array([0.2, 0.3, 1.0, 1.0, 1.0, 1.0, 1.0, 5])] + art_model.dim_ = 2 + centers = art_model.get_cluster_centers() + assert len(centers) == 1 + assert np.array_equal(centers[0], np.array([0.2, 0.3])) + + +def test_fit(art_model): + # Test fitting the model + X = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]) + X = art_model.prepare_data(X) + art_model.fit(X) + + assert len(art_model.W) > 0 # Ensure that clusters were created + +def test_partial_fit(art_model): + # Test partial_fit method + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + X = art_model.prepare_data(X) + art_model.partial_fit(X) + + assert len(art_model.W) > 0 # Ensure that clusters were partially fit + +def test_predict(art_model): + # Test predict method + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + X = art_model.prepare_data(X) + art_model.W = [np.array([0.1, 0.2, 0.5, 0.5, 2.0, 2.0, 2.0, 1])] + + labels = art_model.predict(X) + assert len(labels) == 2 From 50a30f789b8a89755daf9fbe9f39f476b9a35c1a Mon Sep 17 00:00:00 2001 From: niklas melton Date: Sun, 6 Oct 2024 14:27:36 -0500 Subject: [PATCH 011/139] add hypersphere tests --- unit_tests/test_HypersphereART.py | 115 ++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 unit_tests/test_HypersphereART.py diff --git a/unit_tests/test_HypersphereART.py b/unit_tests/test_HypersphereART.py new file mode 100644 index 0000000..c45de66 --- /dev/null +++ b/unit_tests/test_HypersphereART.py @@ -0,0 +1,115 @@ +import pytest +import numpy as np +from artlib.elementary.HypersphereART import HypersphereART + +# Fixture to initialize a HypersphereART instance for testing +@pytest.fixture +def art_model(): + rho = 0.7 + alpha = 1e-5 + beta = 0.1 + r_hat = 1.0 + return HypersphereART(rho=rho, alpha=alpha, beta=beta, r_hat=r_hat) + +def test_initialization(art_model): + # Test that the model initializes correctly + assert art_model.params['rho'] == 0.7 + assert art_model.params['alpha'] == 1e-5 + assert art_model.params['beta'] == 0.1 + assert art_model.params['r_hat'] == 1.0 + +def test_validate_params(): + # Test the validate_params method + valid_params = { + "rho": 0.7, + "alpha": 1e-5, + "beta": 0.1, + "r_hat": 1.0 + } + HypersphereART.validate_params(valid_params) + + invalid_params = { + "rho": 1.5, # Invalid vigilance parameter + "alpha": -1e-5, # Invalid alpha + "beta": 1.1, # Invalid beta + "r_hat": -1.0 # Invalid r_hat + } + with pytest.raises(AssertionError): + HypersphereART.validate_params(invalid_params) + +def test_category_choice(art_model): + # Test the category_choice method + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35, 0.5]) # Mock weight (centroid and radius) + params = { + "rho": 0.7, + "alpha": 1e-5, + "r_hat": 1.0 + } + + activation, cache = art_model.category_choice(i, w, params) + assert 'max_radius' in cache + assert 'i_radius' in cache + assert isinstance(activation, float) + +def test_match_criterion(art_model): + # Test the match_criterion method + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35, 0.5]) # Mock weight (centroid and radius) + params = {"rho": 0.7, "r_hat": 1.0} + cache = {"max_radius": 0.6} + + match_criterion, new_cache = art_model.match_criterion(i, w, params, cache=cache) + assert match_criterion == 1 - (max(0.5, 0.6)/1.0) + +def test_update(art_model): + # Test the update method + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35, 0.5]) # Mock weight (centroid and radius) + params = {"beta": 0.1, "r_hat": 1.0} + cache = {"max_radius": 0.6, "i_radius": 0.55} + + updated_weight = art_model.update(i, w, params, cache=cache) + assert len(updated_weight) == 3 # Check that the weight has a centroid and radius + assert updated_weight[-1] > 0.5 # Check that the radius has been updated + +def test_new_weight(art_model): + # Test the new_weight method + i = np.array([0.2, 0.3]) + params = {"r_hat": 1.0} + + new_weight = art_model.new_weight(i, params) + assert len(new_weight) == 3 # Centroid (2D) + radius + assert new_weight[-1] == 0.0 # Initial radius should be 0 + +def test_get_cluster_centers(art_model): + # Test getting cluster centers + art_model.W = [np.array([0.2, 0.3, 0.5])] + centers = art_model.get_cluster_centers() + assert len(centers) == 1 + assert np.array_equal(centers[0], np.array([0.2, 0.3])) + +def test_fit(art_model): + # Test fitting the model + X = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]) + X = art_model.prepare_data(X) + art_model.fit(X) + + assert len(art_model.W) > 0 # Ensure that clusters were created + +def test_partial_fit(art_model): + # Test partial_fit method + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + X = art_model.prepare_data(X) + art_model.partial_fit(X) + + assert len(art_model.W) > 0 # Ensure that clusters were partially fit + +def test_predict(art_model): + # Test predict method + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + X = art_model.prepare_data(X) + art_model.W = [np.array([0.1, 0.2, 0.5])] + + labels = art_model.predict(X) + assert len(labels) == 2 From bb3b5181a12ea022a55e46af48ea378c84681000 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Sun, 6 Oct 2024 14:30:12 -0500 Subject: [PATCH 012/139] add quadratic neuron ART tests --- unit_tests/test_QuadraticNeuronART.py | 132 ++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 unit_tests/test_QuadraticNeuronART.py diff --git a/unit_tests/test_QuadraticNeuronART.py b/unit_tests/test_QuadraticNeuronART.py new file mode 100644 index 0000000..d1147f0 --- /dev/null +++ b/unit_tests/test_QuadraticNeuronART.py @@ -0,0 +1,132 @@ +import pytest +import numpy as np +from artlib.elementary.QuadraticNeuronART import QuadraticNeuronART + +# Fixture to initialize a QuadraticNeuronART instance for testing +@pytest.fixture +def art_model(): + rho = 0.7 + s_init = 0.5 + lr_b = 0.1 + lr_w = 0.1 + lr_s = 0.05 + return QuadraticNeuronART(rho=rho, s_init=s_init, lr_b=lr_b, lr_w=lr_w, lr_s=lr_s) + +def test_initialization(art_model): + # Test that the model initializes correctly + assert art_model.params['rho'] == 0.7 + assert art_model.params['s_init'] == 0.5 + assert art_model.params['lr_b'] == 0.1 + assert art_model.params['lr_w'] == 0.1 + assert art_model.params['lr_s'] == 0.05 + +def test_validate_params(): + # Test the validate_params method + valid_params = { + "rho": 0.7, + "s_init": 0.5, + "lr_b": 0.1, + "lr_w": 0.1, + "lr_s": 0.05 + } + QuadraticNeuronART.validate_params(valid_params) + + invalid_params = { + "rho": 1.5, # Invalid vigilance parameter + "s_init": -0.5, # Invalid s_init + "lr_b": 1.1, # Invalid learning rate for cluster mean + "lr_w": -0.1, # Invalid learning rate for cluster weights + "lr_s": 1.5 # Invalid learning rate for activation parameter + } + with pytest.raises(AssertionError): + QuadraticNeuronART.validate_params(invalid_params) + +def test_category_choice(art_model): + # Test the category_choice method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + w = np.array([1.0, 0.0, 0.0, 1.0, 0.25, 0.35, 0.5]) # Mock weight (matrix, centroid, and activation parameter) + params = { + "rho": 0.7, + "s_init": 0.5 + } + + activation, cache = art_model.category_choice(i, w, params) + assert 'activation' in cache + assert 'l2norm2_z_b' in cache + assert isinstance(activation, float) + +def test_match_criterion(art_model): + # Test the match_criterion method + i = np.array([0.2, 0.3]) + w = np.array([1.0, 0.0, 0.0, 1.0, 0.25, 0.35, 0.5]) # Mock weight (matrix, centroid, and activation parameter) + params = {"rho": 0.7} + cache = {"activation": 0.8} + + match_criterion, new_cache = art_model.match_criterion(i, w, params, cache=cache) + assert match_criterion == cache["activation"] + +def test_update(art_model): + # Test the update method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + w = np.array([1.0, 0.0, 0.0, 1.0, 0.25, 0.35, 0.5]) # Mock weight (matrix, centroid, and activation parameter) + params = {"lr_b": 0.1, "lr_w": 0.1, "lr_s": 0.05} + cache = { + "s": 0.5, + "w": np.array([[1.0, 0.0], [0.0, 1.0]]), + "b": np.array([0.25, 0.35]), + "z": np.array([0.2, 0.3]), + "activation": 0.8, + "l2norm2_z_b": 0.02 + } + + updated_weight = art_model.update(i, w, params, cache=cache) + assert len(updated_weight) == 7 # Check that the weight has matrix, centroid, and activation parameter + assert updated_weight[-1] < 0.5 # Check that the activation parameter has been updated + +def test_new_weight(art_model): + # Test the new_weight method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + params = {"s_init": 0.5} + + new_weight = art_model.new_weight(i, params) + assert len(new_weight) == 7 # Weight matrix (4 values), centroid (2 values), and activation parameter (1 value) + assert new_weight[-1] == 0.5 # Initial activation parameter should be s_init + +def test_get_cluster_centers(art_model): + # Test getting cluster centers + art_model.dim_ = 2 + art_model.W = [np.array([1.0, 0.0, 0.0, 1.0, 0.2, 0.3, 0.5])] + centers = art_model.get_cluster_centers() + assert len(centers) == 1 + assert np.array_equal(centers[0], np.array([0.2, 0.3])) + +def test_fit(art_model): + # Test fitting the model + art_model.dim_ = 2 + X = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]) + X = art_model.prepare_data(X) + art_model.fit(X) + + assert len(art_model.W) > 0 # Ensure that clusters were created + +def test_partial_fit(art_model): + # Test partial_fit method + art_model.dim_ = 2 + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + X = art_model.prepare_data(X) + art_model.partial_fit(X) + + assert len(art_model.W) > 0 # Ensure that clusters were partially fit + +def test_predict(art_model): + # Test predict method + art_model.dim_ = 2 + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + X = art_model.prepare_data(X) + art_model.W = [np.array([1.0, 0.0, 0.0, 1.0, 0.1, 0.2, 0.5])] + + labels = art_model.predict(X) + assert len(labels) == 2 From 06b1de1274f890828ed5a8055ab164370d944ece Mon Sep 17 00:00:00 2001 From: niklas melton Date: Sun, 6 Oct 2024 14:37:34 -0500 Subject: [PATCH 013/139] add Ellipsoid ART tests --- unit_tests/test_EllipsoidART.py | 128 ++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 unit_tests/test_EllipsoidART.py diff --git a/unit_tests/test_EllipsoidART.py b/unit_tests/test_EllipsoidART.py new file mode 100644 index 0000000..570da9d --- /dev/null +++ b/unit_tests/test_EllipsoidART.py @@ -0,0 +1,128 @@ +import pytest +import numpy as np +from artlib.elementary.EllipsoidART import EllipsoidART + +# Fixture to initialize an EllipsoidART instance for testing +@pytest.fixture +def art_model(): + rho = 0.7 + alpha = 1e-5 + beta = 0.1 + mu = 0.5 + r_hat = 1.0 + return EllipsoidART(rho=rho, alpha=alpha, beta=beta, mu=mu, r_hat=r_hat) + +def test_initialization(art_model): + # Test that the model initializes correctly + assert art_model.params['rho'] == 0.7 + assert art_model.params['alpha'] == 1e-5 + assert art_model.params['beta'] == 0.1 + assert art_model.params['mu'] == 0.5 + assert art_model.params['r_hat'] == 1.0 + +def test_validate_params(): + # Test the validate_params method + valid_params = { + "rho": 0.7, + "alpha": 1e-5, + "beta": 0.1, + "mu": 0.5, + "r_hat": 1.0 + } + EllipsoidART.validate_params(valid_params) + + invalid_params = { + "rho": 1.5, # Invalid vigilance parameter + "alpha": -1e-5, # Invalid alpha + "beta": 1.1, # Invalid beta + "mu": -0.5, # Invalid mu + "r_hat": -1.0 # Invalid r_hat + } + with pytest.raises(AssertionError): + EllipsoidART.validate_params(invalid_params) + +def test_category_choice(art_model): + # Test the category_choice method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35, 0.5, 0.0, 0.0]) # Mock weight (centroid, major axis, and radius) + params = { + "rho": 0.7, + "alpha": 1e-5, + "mu": 0.5, + "r_hat": 1.0 + } + + activation, cache = art_model.category_choice(i, w, params) + assert 'dist' in cache + assert isinstance(activation, float) + +def test_match_criterion(art_model): + # Test the match_criterion method + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35, 0.5, 0.0, 0.0]) # Mock weight (centroid, major axis, and radius) + params = {"rho": 0.7, "r_hat": 1.0} + cache = {"dist": 0.6} + + match_criterion, new_cache = art_model.match_criterion(i, w, params, cache=cache) + expected_match_criterion = 1 - (0.0 + max(0.0, 0.6)) / 1.0 + assert match_criterion == pytest.approx(expected_match_criterion, rel=1e-6) + + +def test_update(art_model): + # Test the update method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35, 0.5, 0.0, 0.0]) # Mock weight (centroid, major axis, and radius) + params = {"beta": 0.1, "mu": 0.5, "r_hat": 1.0} + cache = {"dist": 0.6} + + updated_weight = art_model.update(i, w, params, cache=cache) + assert updated_weight[-1] >= w[-1] # Ensure that the radius has not decreased + + +def test_new_weight(art_model): + # Test the new_weight method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + params = {"r_hat": 1.0} + + new_weight = art_model.new_weight(i, params) + assert len(new_weight) == 5 # Centroid (2), major axis (2), and radius (1) + assert new_weight[-1] == 0.0 # Initial radius should be 0 + +def test_get_cluster_centers(art_model): + # Test getting cluster centers + art_model.dim_ = 2 + art_model.W = [np.array([0.2, 0.3, 0.0, 0.0, 0.5])] + centers = art_model.get_cluster_centers() + assert len(centers) == 1 + assert np.array_equal(centers[0], np.array([0.2, 0.3])) + +def test_fit(art_model): + # Test fitting the model + art_model.dim_ = 2 + X = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]) + X = art_model.prepare_data(X) + art_model.fit(X) + + assert len(art_model.W) > 0 # Ensure that clusters were created + +def test_partial_fit(art_model): + # Test partial_fit method + art_model.dim_ = 2 + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + X = art_model.prepare_data(X) + art_model.partial_fit(X) + + assert len(art_model.W) > 0 # Ensure that clusters were partially fit + +def test_predict(art_model): + # Test predict method + art_model.dim_ = 2 + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + X = art_model.prepare_data(X) + art_model.W = [np.array([0.1, 0.2, 0.0, 0.0, 0.5])] + + labels = art_model.predict(X) + assert len(labels) == 2 From d419c3b6dc73e6012d25991cda430526cfecd38f Mon Sep 17 00:00:00 2001 From: niklas melton Date: Sun, 6 Oct 2024 14:39:39 -0500 Subject: [PATCH 014/139] add BayesianART tests --- unit_tests/test_BayesianART.py | 130 +++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 unit_tests/test_BayesianART.py diff --git a/unit_tests/test_BayesianART.py b/unit_tests/test_BayesianART.py new file mode 100644 index 0000000..dbd825d --- /dev/null +++ b/unit_tests/test_BayesianART.py @@ -0,0 +1,130 @@ +import pytest +import numpy as np +from artlib.elementary.BayesianART import BayesianART + + +# Fixture to initialize a BayesianART instance for testing +@pytest.fixture +def art_model(): + rho = 0.7 + cov_init = np.array([[1.0, 0.0], [0.0, 1.0]]) # Initial covariance matrix + return BayesianART(rho=rho, cov_init=cov_init) + + +def test_initialization(art_model): + # Test that the model initializes correctly + assert art_model.params['rho'] == 0.7 + assert np.array_equal(art_model.params['cov_init'], np.array([[1.0, 0.0], [0.0, 1.0]])) + + +def test_validate_params(): + # Test the validate_params method + valid_params = { + "rho": 0.7, + "cov_init": np.array([[1.0, 0.0], [0.0, 1.0]]) + } + BayesianART.validate_params(valid_params) + + invalid_params = { + "rho": -0.5, # Invalid vigilance parameter + "cov_init": "not_a_matrix" # Invalid covariance matrix + } + with pytest.raises(AssertionError): + BayesianART.validate_params(invalid_params) + + +def test_check_dimensions(art_model): + # Test the check_dimensions method + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + art_model.check_dimensions(X) + + assert art_model.dim_ == 2 + + +def test_category_choice(art_model): + # Test the category_choice method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35, 1.0, 0.0, 0.0, 1.0, 5]) # Mock weight (mean, covariance, and sample count) + art_model.W = [w] + params = {"rho": 0.7} + + activation, cache = art_model.category_choice(i, w, params) + assert 'cov' in cache + assert 'det_cov' in cache + assert isinstance(activation, float) + + +def test_match_criterion(art_model): + # Test the match_criterion method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35, 1.0, 0.0, 0.0, 1.0, 5]) # Mock weight (mean, covariance, and sample count) + params = {"rho": 0.7} + cache = {} + + match_criterion, new_cache = art_model.match_criterion(i, w, params, cache=cache) + assert isinstance(match_criterion, float) + + +def test_update(art_model): + # Test the update method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35, 1.0, 0.0, 0.0, 1.0, 5]) # Mock weight (mean, covariance, and sample count) + params = {"rho": 0.7} + cache = {} + + updated_weight = art_model.update(i, w, params, cache=cache) + assert len(updated_weight) == 7 # Mean (2D), covariance (4 values), and sample count + + +def test_new_weight(art_model): + # Test the new_weight method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + params = {"cov_init": np.array([[1.0, 0.0], [0.0, 1.0]])} + + new_weight = art_model.new_weight(i, params) + assert len(new_weight) == 7 # Mean (2D), covariance (4 values), and sample count + assert new_weight[-1] == 1 # Initial sample count should be 1 + + +def test_get_cluster_centers(art_model): + # Test getting cluster centers + art_model.dim_ = 2 + art_model.W = [np.array([0.2, 0.3, 1.0, 0.0, 0.0, 1.0, 5])] + centers = art_model.get_cluster_centers() + assert len(centers) == 1 + assert np.array_equal(centers[0], np.array([0.2, 0.3])) + + +def test_fit(art_model): + # Test fitting the model + art_model.dim_ = 2 + X = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]) + art_model.check_dimensions(X) + art_model.fit(X) + + assert len(art_model.W) > 0 # Ensure that clusters were created + + +def test_partial_fit(art_model): + # Test partial_fit method + art_model.dim_ = 2 + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + art_model.check_dimensions(X) + art_model.partial_fit(X) + + assert len(art_model.W) > 0 # Ensure that clusters were partially fit + + +def test_predict(art_model): + # Test predict method + art_model.dim_ = 2 + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + art_model.check_dimensions(X) + art_model.W = [np.array([0.1, 0.2, 1.0, 0.0, 0.0, 1.0, 5])] + + labels = art_model.predict(X) + assert len(labels) == 2 From b1149dd806016d168f32ae8f76e903f0fc2c0257 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Sun, 6 Oct 2024 14:44:35 -0500 Subject: [PATCH 015/139] add dual vigilance tests --- unit_tests/test_DualVigilanceART.py | 115 ++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 unit_tests/test_DualVigilanceART.py diff --git a/unit_tests/test_DualVigilanceART.py b/unit_tests/test_DualVigilanceART.py new file mode 100644 index 0000000..2db253b --- /dev/null +++ b/unit_tests/test_DualVigilanceART.py @@ -0,0 +1,115 @@ +import pytest +import numpy as np +from typing import Optional +from artlib.elementary.DualVigilanceART import DualVigilanceART +from artlib.common.BaseART import BaseART + +# Mock BaseART class for testing purposes +class MockBaseART(BaseART): + def __init__(self): + params = {"rho": 0.7} + super().__init__(params) + self.W = [] + self.labels_ = np.array([]) + self.dim_ = 2 + + @staticmethod + def validate_params(params: dict): + pass + + def prepare_data(self, X: np.ndarray): + return X + + def restore_data(self, X: np.ndarray): + return X + + def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: + return i + + def add_weight(self, w: np.ndarray): + self.W.append(w) + + def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: + return np.random.random(), {} + + def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None, op=None) -> tuple[bool, dict]: + return True, {} + + def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: + return w + + def get_cluster_centers(self) -> list: + return [w for w in self.W] + + def check_dimensions(self, X: np.ndarray): + assert X.shape[1] == self.dim_ + +@pytest.fixture +def art_model(): + base_module = MockBaseART() + rho_lower_bound = 0.3 + return DualVigilanceART(base_module=base_module, rho_lower_bound=rho_lower_bound) + +def test_initialization(art_model): + # Test that the model initializes correctly + assert art_model.params['rho_lower_bound'] == 0.3 + assert isinstance(art_model.base_module, BaseART) + +def test_prepare_data(art_model): + # Test the prepare_data method + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + prepared_X = art_model.prepare_data(X) + assert np.array_equal(prepared_X, X) + +def test_restore_data(art_model): + # Test the restore_data method + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + restored_X = art_model.restore_data(X) + assert np.array_equal(restored_X, X) + +def test_get_params(art_model): + # Test the get_params method + params = art_model.get_params(deep=True) + assert "rho_lower_bound" in params + assert "base_module" in params + +def test_n_clusters(art_model): + # Test the n_clusters property + assert art_model.n_clusters == 0 # No clusters initially + art_model.map = {0: 0, 1: 1} + assert art_model.n_clusters == 2 # Two clusters + +def test_check_dimensions(art_model): + # Test the check_dimensions method + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + art_model.check_dimensions(X) # Should pass without assertion errors + +def test_validate_params(art_model): + # Test the validate_params method + valid_params = {"rho_lower_bound": 0.3} + art_model.validate_params(valid_params) + + invalid_params = {"rho_lower_bound": -0.3} # Invalid rho_lower_bound + with pytest.raises(AssertionError): + art_model.validate_params(invalid_params) + +def test_step_fit(art_model): + # Test the step_fit method + x = np.array([0.1, 0.2]) + cluster_label = art_model.step_fit(x) + assert cluster_label == 0 # First sample should create a new cluster + +def test_step_pred(art_model): + # Test the step_pred method + x = np.array([0.1, 0.2]) + art_model.step_fit(x) # Create the first cluster + cluster_label = art_model.step_pred(x) + assert cluster_label == 0 # Predict should return the correct cluster + +def test_get_cluster_centers(art_model): + # Test the get_cluster_centers method + art_model.step_fit(np.array([0.1, 0.2])) # Create the first cluster + centers = art_model.get_cluster_centers() + assert len(centers) == 1 + assert np.array_equal(centers[0], np.array([0.1, 0.2])) + From 38137205989d65cf4e2954afde58d9c2c4850827 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Sun, 6 Oct 2024 14:47:27 -0500 Subject: [PATCH 016/139] add ART2 tests --- unit_tests/test_ART2.py | 132 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 unit_tests/test_ART2.py diff --git a/unit_tests/test_ART2.py b/unit_tests/test_ART2.py new file mode 100644 index 0000000..711fc8c --- /dev/null +++ b/unit_tests/test_ART2.py @@ -0,0 +1,132 @@ +import pytest +import numpy as np +from artlib.elementary.ART2 import ART2A + + +# Fixture to initialize an ART2A instance for testing +@pytest.fixture +def art_model(): + rho = 0.7 + alpha = 0.1 + beta = 0.5 + return ART2A(rho=rho, alpha=alpha, beta=beta) + + +def test_initialization(art_model): + # Test that the model initializes correctly + assert art_model.params['rho'] == 0.7 + assert art_model.params['alpha'] == 0.1 + assert art_model.params['beta'] == 0.5 + + +def test_validate_params(): + # Test the validate_params method + valid_params = { + "rho": 0.7, + "alpha": 0.1, + "beta": 0.5 + } + ART2A.validate_params(valid_params) + + invalid_params = { + "rho": -0.7, # Invalid vigilance parameter + "alpha": -0.1, # Invalid alpha + "beta": 1.5 # Invalid beta + } + with pytest.raises(AssertionError): + ART2A.validate_params(invalid_params) + + +def test_check_dimensions(art_model): + # Test the check_dimensions method + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + art_model.check_dimensions(X) + + assert art_model.dim_ == 2 + + +def test_category_choice(art_model): + # Test the category_choice method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35]) # Mock weight + params = {"rho": 0.7} + + activation, cache = art_model.category_choice(i, w, params) + assert 'activation' in cache + assert isinstance(activation, float) + + +def test_match_criterion(art_model): + # Test the match_criterion method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35]) # Mock weight + params = {"alpha": 0.1} + cache = {"activation": 0.5} + + match_criterion, new_cache = art_model.match_criterion(i, w, params, cache=cache) + assert match_criterion == 0.5 # Since activation is higher than uncommitted activation + + +def test_update(art_model): + # Test the update method + art_model.dim_ = 2 + i = np.array([0.2, 0.3]) + w = np.array([0.25, 0.35]) # Mock weight + params = {"beta": 0.5} + cache = {"activation": 0.5} + + updated_weight = art_model.update(i, w, params, cache=cache) + assert len(updated_weight) == 2 # Check that the weight is updated + assert np.allclose(updated_weight, (0.5 * i + 0.5 * w)) # Check the update formula + + +def test_new_weight(art_model): + # Test the new_weight method + i = np.array([0.2, 0.3]) + params = {"beta": 0.5} + + new_weight = art_model.new_weight(i, params) + assert len(new_weight) == 2 # Check that the weight has two dimensions + assert np.array_equal(new_weight, i) + + +def test_get_cluster_centers(art_model): + # Test getting cluster centers + art_model.W = [np.array([0.2, 0.3]), np.array([0.4, 0.5])] + centers = art_model.get_cluster_centers() + assert len(centers) == 2 + assert np.array_equal(centers[0], np.array([0.2, 0.3])) + assert np.array_equal(centers[1], np.array([0.4, 0.5])) + + +def test_fit(art_model): + # Test fitting the model + art_model.dim_ = 2 + X = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]) + art_model.check_dimensions(X) + art_model.fit(X) + + assert len(art_model.W) > 0 # Ensure that clusters were created + + +def test_partial_fit(art_model): + # Test partial_fit method + art_model.dim_ = 2 + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + art_model.check_dimensions(X) + art_model.partial_fit(X) + + assert len(art_model.W) > 0 # Ensure that clusters were partially fit + + +def test_predict(art_model): + # Test predict method + art_model.dim_ = 2 + X = np.array([[0.1, 0.2], [0.3, 0.4]]) + art_model.check_dimensions(X) + art_model.W = [np.array([0.1, 0.2]), np.array([0.3, 0.4])] + + labels = art_model.predict(X) + assert len(labels) == 2 From cac4e1de6563224e2a076d5257c314d0cabeac27 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Sun, 6 Oct 2024 14:48:46 -0500 Subject: [PATCH 017/139] add ART1 tests --- unit_tests/test_ART1.py | 124 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 unit_tests/test_ART1.py diff --git a/unit_tests/test_ART1.py b/unit_tests/test_ART1.py new file mode 100644 index 0000000..0a5dd67 --- /dev/null +++ b/unit_tests/test_ART1.py @@ -0,0 +1,124 @@ +import pytest +import numpy as np +from artlib.elementary.ART1 import ART1 + +# Fixture to initialize an ART1 instance for testing +@pytest.fixture +def art_model(): + rho = 0.7 + beta = 0.5 + L = 2.0 + return ART1(rho=rho, beta=beta, L=L) + +def test_initialization(art_model): + # Test that the model initializes correctly + assert art_model.params['rho'] == 0.7 + assert art_model.params['beta'] == 0.5 + assert art_model.params['L'] == 2.0 + +def test_validate_params(): + # Test the validate_params method + valid_params = { + "rho": 0.7, + "beta": 0.5, + "L": 2.0 + } + ART1.validate_params(valid_params) + + invalid_params = { + "rho": -0.7, # Invalid vigilance parameter + "beta": -0.5, # Invalid beta + "L": 0.5 # Invalid L (must be >= 1) + } + with pytest.raises(AssertionError): + ART1.validate_params(invalid_params) + +def test_validate_data(art_model): + # Test the validate_data method + binary_data = np.array([[1, 0], [0, 1]]) + art_model.validate_data(binary_data) + + non_binary_data = np.array([[0.5, 1.0], [1.2, 0.3]]) + with pytest.raises(AssertionError): + art_model.validate_data(non_binary_data) + +def test_category_choice(art_model): + # Test the category_choice method + art_model.dim_ = 2 + i = np.array([1, 0]) + w = np.array([0.5, 0.5, 1, 0]) # Mock weight with both bottom-up and top-down weights + params = {"rho": 0.7} + + activation, _ = art_model.category_choice(i, w, params) + assert isinstance(activation, float) + assert activation == 0.5 # np.dot(i, [0.5, 0.5]) = 0.5 + +def test_match_criterion(art_model): + # Test the match_criterion method + art_model.dim_ = 2 + i = np.array([1, 0]) + w = np.array([0.5, 0.5, 1, 0]) # Mock weight with both bottom-up and top-down weights + params = {"rho": 0.7} + cache = {} + + match_criterion, _ = art_model.match_criterion(i, w, params, cache=cache) + assert isinstance(match_criterion, float) + assert match_criterion == 1.0 # Intersection of i and top-down weight w_td: [1, 0] matches exactly with i + +def test_update(art_model): + # Test the update method + art_model.dim_ = 2 + i = np.array([1, 0]) + w = np.array([0.5, 0.5, 1, 1]) # Mock weight with both bottom-up and top-down weights + params = {"L": 2.0, "beta": 0.5} + + updated_weight = art_model.update(i, w, params) + assert len(updated_weight) == 4 # Bottom-up and top-down weights + assert np.array_equal(updated_weight[2:], np.array([1, 0])) # Top-down weights should match input i + +def test_new_weight(art_model): + # Test the new_weight method + art_model.dim_ = 2 + i = np.array([1, 0]) + params = {"L": 2.0} + + new_weight = art_model.new_weight(i, params) + assert len(new_weight) == 4 # Bottom-up and top-down weights + assert np.array_equal(new_weight[2:], i) # Top-down weights should be equal to input i + +def test_get_cluster_centers(art_model): + # Test getting cluster centers + art_model.dim_ = 2 + art_model.W = [np.array([0.5, 0.5, 1, 0]), np.array([0.3, 0.7, 0, 1])] + centers = art_model.get_cluster_centers() + assert len(centers) == 2 + assert np.array_equal(centers[0], np.array([1, 0])) + assert np.array_equal(centers[1], np.array([0, 1])) + +def test_fit(art_model): + # Test fitting the model + art_model.dim_ = 2 + X = np.array([[1, 0], [0, 1], [1, 1]]) + art_model.check_dimensions(X) + art_model.fit(X) + + assert len(art_model.W) > 0 # Ensure that clusters were created + +def test_partial_fit(art_model): + # Test partial_fit method + art_model.dim_ = 2 + X = np.array([[1, 0], [0, 1]]) + art_model.check_dimensions(X) + art_model.partial_fit(X) + + assert len(art_model.W) > 0 # Ensure that clusters were partially fit + +def test_predict(art_model): + # Test predict method + art_model.dim_ = 2 + X = np.array([[1, 0], [0, 1]]) + art_model.check_dimensions(X) + art_model.W = [np.array([0.5, 0.5, 1, 0]), np.array([0.3, 0.7, 0, 1])] + + labels = art_model.predict(X) + assert len(labels) == 2 From dd80ed73bdd4f8a0ad2fe2ff929289dab18e83b4 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Sun, 6 Oct 2024 15:12:42 -0500 Subject: [PATCH 018/139] add cluster consistency tests --- examples/generate_model_results_snapshot.py | 72 ++++++++++++++++++++ unit_tests/cluster_results_snapshot.pkl | Bin 0 -> 5930 bytes unit_tests/test_clustering_consistency.py | 65 ++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 examples/generate_model_results_snapshot.py create mode 100644 unit_tests/cluster_results_snapshot.pkl create mode 100644 unit_tests/test_clustering_consistency.py diff --git a/examples/generate_model_results_snapshot.py b/examples/generate_model_results_snapshot.py new file mode 100644 index 0000000..67e60bf --- /dev/null +++ b/examples/generate_model_results_snapshot.py @@ -0,0 +1,72 @@ +import pickle +from pathlib import Path +from sklearn.datasets import make_blobs +import numpy as np +from artlib import ART1, ART2A, BayesianART, DualVigilanceART, EllipsoidART, FuzzyART, GaussianART, HypersphereART, QuadraticNeuronART + + +def model_factory(model_class, params): + """ + A factory function to initialize models, handling special cases. + """ + if model_class.__name__ == "DualVigilanceART": + # For DualVigilanceART, initialize with a base ART model + base_art = FuzzyART(params["rho"], params["alpha"], params["beta"]) + return model_class(base_art, params["rho_lower_bound"]) + + # Default case for other models + return model_class(**params) + + +def cluster_and_store_results(): + # Generate blob data for clustering + data, _ = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + print("Data has shape:", data.shape) + + # Define the models and their parameters in a list of tuples + models_with_params = [ + # (ART1, {"rho": 0.7, "beta": 1.0, "L": 1.0}), + (ART2A, {"rho": 0.2, "alpha": 0.0, "beta": 1.0}), + (BayesianART, {"rho": 7e-5, "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]])}), + (DualVigilanceART, {"rho": 0.85, "alpha": 0.8, "beta": 1.0, "rho_lower_bound": 0.78}), + (EllipsoidART, {"rho": 0.01, "alpha": 0.0, "beta": 1.0, "r_hat": 0.65, "mu": 0.8}), + (FuzzyART, {"rho": 0.5, "alpha": 0.0, "beta": 1.0}), + (GaussianART, {"rho": 0.05, "sigma_init": np.array([0.5, 0.5])}), + (HypersphereART, {"rho": 0.5, "alpha": 0.0, "beta": 1.0, "r_hat": 0.8}), + (QuadraticNeuronART, {"rho": 0.9, "s_init": 0.9, "lr_b": 0.1, "lr_w": 0.1, "lr_s": 0.1}), + ] + + results = {} + + for model_class, params in models_with_params: + # Instantiate the model + cls = model_factory(model_class, params) + model_name = model_class.__name__ + + # Prepare data + X = cls.prepare_data(data) + print(f"Prepared data for {model_name} has shape:", X.shape) + + # Fit the model and predict clusters + labels = cls.fit_predict(X) + + # Store the labels and params in a dictionary keyed by the model name + results[model_name] = {"params": params, "labels": labels} + print(f"{cls.n_clusters} clusters found for {model_name}") + + + # Save the results to a pickle file + current_file_path = Path(__file__).resolve().parent.parent + output_file = current_file_path / "unit_tests" / "cluster_results_snapshot.pkl" + + # Ensure the output directory exists + output_file.parent.mkdir(parents=True, exist_ok=True) + + # Save the results to the pickle file + with open(output_file, "wb") as f: + pickle.dump(results, f) + + print("Results saved to cluster_results.pkl") + +if __name__ == "__main__": + cluster_and_store_results() diff --git a/unit_tests/cluster_results_snapshot.pkl b/unit_tests/cluster_results_snapshot.pkl new file mode 100644 index 0000000000000000000000000000000000000000..be38a19904f38bc4f5020e5b4110867a4e559b58 GIT binary patch literal 5930 zcmeI0O^DM#6vx}N=@z%FWpVKXL2veA4=S>U#jsm7Vxiz7q9BxM8g~ZLBup|ctq2}m zwJ_jeM?nwXy?FQPK@jxf#cLH0%6jmk7Y{meGR0Wm`0n_EURElOBrq6BdtLz~( zXMu)kd5kXDq~*5DhJ(#=i&#Da7=t#Wg*rA3$M$?o44-W>ss(BPw;5F$HhOeZ%JhavVe^uFB03YdAiW2Z%sGxgVqL_CU)S@zs1Hw_vDZ!pQ$3JFZ&xI!L z)64(N$M965B{&U}s1^R_(f$2}#%^A`wcV&EgAW)kf5eFgav%HNzPv$L*6Jz0yt g>TyZ@Jj~(3UXR({ww)-DorU{2kQ~LF0p_Iq3knsOSpWb4 literal 0 HcmV?d00001 diff --git a/unit_tests/test_clustering_consistency.py b/unit_tests/test_clustering_consistency.py new file mode 100644 index 0000000..882aeaa --- /dev/null +++ b/unit_tests/test_clustering_consistency.py @@ -0,0 +1,65 @@ +import pickle +import pytest +import numpy as np +from pathlib import Path +from artlib import ART1, ART2A, BayesianART, DualVigilanceART, EllipsoidART, FuzzyART, GaussianART, HypersphereART, \ + QuadraticNeuronART + + +# Factory function to initialize models, handling special cases like DualVigilanceART +def model_factory(model_class, params): + if model_class.__name__ == "DualVigilanceART": + base_art = FuzzyART(params["rho"], params["alpha"], params["beta"]) + return model_class(base_art, params["rho_lower_bound"]) + return model_class(**params) + + +# Load the clustering results from the pickle file +@pytest.fixture(scope="module") +def cluster_results(): + # Define the path to the pickle file + current_file_path = Path(__file__).resolve().parent.parent + pickle_file = current_file_path / "unit_tests" / "cluster_results_snapshot.pkl" + + # Load the results + with open(pickle_file, "rb") as f: + return pickle.load(f) + + +# Dictionary of model classes to map model names to classes +model_classes = { + "ART2A": ART2A, + "BayesianART": BayesianART, + "DualVigilanceART": DualVigilanceART, + "EllipsoidART": EllipsoidART, + "FuzzyART": FuzzyART, + "GaussianART": GaussianART, + "HypersphereART": HypersphereART, + "QuadraticNeuronART": QuadraticNeuronART, +} + + +@pytest.mark.parametrize("model_name", model_classes.keys()) +def test_clustering_consistency(model_name, cluster_results): + # Get the stored params and labels for the model + stored_data = cluster_results[model_name] + stored_params = stored_data["params"] + stored_labels = stored_data["labels"] + + # Instantiate the model using the stored parameters + model_class = model_classes[model_name] + model_instance = model_factory(model_class, stored_params) + + # Generate blob data (same data used when saving the pickle file) + from sklearn.datasets import make_blobs + data, _ = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + + # Prepare the data + X = model_instance.prepare_data(data) + + # Fit the model and predict the clusters + predicted_labels = model_instance.fit_predict(X) + + # Check that the predicted labels match the stored labels + assert np.array_equal(predicted_labels, stored_labels), f"Labels for {model_name} do not match!" + From f066e28f80394f70d137e28df237053fe17b41e8 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 10 Oct 2024 01:57:37 -0500 Subject: [PATCH 019/139] add BARTMAP tests --- artlib/biclustering/BARTMAP.py | 8 +-- unit_tests/test_BARTMAP.py | 92 ++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 unit_tests/test_BARTMAP.py diff --git a/artlib/biclustering/BARTMAP.py b/artlib/biclustering/BARTMAP.py index c8801fe..e6f03c8 100644 --- a/artlib/biclustering/BARTMAP.py +++ b/artlib/biclustering/BARTMAP.py @@ -184,7 +184,7 @@ def _average_pearson_corr(self, X: np.ndarray, k: int, c_b: int) -> float: """ X_a = X[self.column_labels_ == c_b, :] if len(X_a) == 0: - raise ValueError("HERE") + raise ValueError("X_a has length 0") X_k_cb = self._get_x_cb(X[k,:], c_b) mean_r = np.mean( [ @@ -285,7 +285,7 @@ def fit(self, X: np.ndarray, max_iter=1): self.X = X n = X.shape[0] - X_a = self.module_b.prepare_data(X) + X_a = self.module_a.prepare_data(X) X_b = self.module_b.prepare_data(X.T) self.validate_data(X_a, X_b) @@ -298,10 +298,10 @@ def fit(self, X: np.ndarray, max_iter=1): for _ in range(max_iter): for k in range(n): - self.module_a.pre_step_fit(X) + self.module_a.pre_step_fit(X_a) c_a = self.step_fit(X_a, k) self.module_a.labels_[k] = c_a - self.module_a.post_step_fit(X) + self.module_a.post_step_fit(X_a) self.rows_ = np.vstack( [ diff --git a/unit_tests/test_BARTMAP.py b/unit_tests/test_BARTMAP.py new file mode 100644 index 0000000..919aa55 --- /dev/null +++ b/unit_tests/test_BARTMAP.py @@ -0,0 +1,92 @@ +import pytest +import numpy as np +from typing import Optional +from artlib.biclustering.BARTMAP import BARTMAP +from artlib.elementary.FuzzyART import FuzzyART +from artlib.common.BaseART import BaseART + +# Fixture to initialize a BARTMAP instance for testing +@pytest.fixture +def bartmap_model(): + module_a = FuzzyART(0.5, 0.01, 1.0) + module_b = FuzzyART(0.5, 0.01, 1.0) + return BARTMAP(module_a=module_a, module_b=module_b, eta=0.01) + +def test_initialization(bartmap_model): + # Test that the model initializes correctly + assert bartmap_model.params["eta"] == 0.01 + assert isinstance(bartmap_model.module_a, BaseART) + assert isinstance(bartmap_model.module_b, BaseART) + +def test_validate_params(): + # Test the validate_params method + valid_params = {"eta": 0.5} + BARTMAP.validate_params(valid_params) + + invalid_params = {"eta": "invalid"} # eta should be a float + with pytest.raises(AssertionError): + BARTMAP.validate_params(invalid_params) + +def test_get_params(bartmap_model): + # Test the get_params method + params = bartmap_model.get_params() + assert "eta" in params + assert "module_a" in params + assert "module_b" in params + +def test_set_params(bartmap_model): + # Test the set_params method + bartmap_model.set_params(eta=0.7) + assert bartmap_model.eta == 0.7 + +def test_step_fit(bartmap_model): + # Test the step_fit method + X = np.random.rand(10, 10) + + bartmap_model.X = X + + X_a = bartmap_model.module_a.prepare_data(X) + X_b = bartmap_model.module_b.prepare_data(X.T) + + bartmap_model.module_b = bartmap_model.module_b.fit(X_b, max_iter=1) + + # init module A + bartmap_model.module_a.W = [] + bartmap_model.module_a.labels_ = np.zeros((X.shape[0],), dtype=int) + + c_a = bartmap_model.step_fit(X_a, 0) + assert isinstance(c_a, int) # Ensure the result is an integer cluster label + +def test_match_criterion_bin(bartmap_model): + # Test the match_criterion_bin method + X = np.random.rand(10, 10) + + bartmap_model.X = X + + X_a = bartmap_model.module_a.prepare_data(X) + X_b = bartmap_model.module_b.prepare_data(X.T) + + bartmap_model.module_b = bartmap_model.module_b.fit(X_b, max_iter=1) + + # init module A + bartmap_model.module_a.W = [] + bartmap_model.module_a.labels_ = np.zeros((X.shape[0],), dtype=int) + c_a = bartmap_model.step_fit(X_a, 0) + + result = bartmap_model.match_criterion_bin(X, 9, 0, {"eta": 0.5}) + assert isinstance(result, bool) # Ensure the result is a boolean + +def test_fit(bartmap_model): + # Test the fit method + X = np.random.rand(10, 10) + + bartmap_model.fit(X, max_iter=1) + + # Check that rows_ and columns_ are set + assert hasattr(bartmap_model, "rows_") + assert hasattr(bartmap_model, "columns_") + + # Check that the rows and columns shapes match the expected size + assert bartmap_model.rows_.shape[0] == bartmap_model.module_a.n_clusters * bartmap_model.module_b.n_clusters + assert bartmap_model.columns_.shape[0] == bartmap_model.module_a.n_clusters * bartmap_model.module_b.n_clusters + From 03f5b733c0c60bf4537e56f701779251a0627763 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 10 Oct 2024 02:03:10 -0500 Subject: [PATCH 020/139] add BARTMAP tests --- unit_tests/test_BARTMAP.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit_tests/test_BARTMAP.py b/unit_tests/test_BARTMAP.py index 919aa55..4419b2c 100644 --- a/unit_tests/test_BARTMAP.py +++ b/unit_tests/test_BARTMAP.py @@ -59,7 +59,7 @@ def test_step_fit(bartmap_model): def test_match_criterion_bin(bartmap_model): # Test the match_criterion_bin method - X = np.random.rand(10, 10) + X = np.random.rand(100, 100) bartmap_model.X = X @@ -78,7 +78,7 @@ def test_match_criterion_bin(bartmap_model): def test_fit(bartmap_model): # Test the fit method - X = np.random.rand(10, 10) + X = np.random.rand(100, 100) bartmap_model.fit(X, max_iter=1) From c4a2d260c10c2f1f898035fb20c40b62781ba7ca Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 10 Oct 2024 02:18:43 -0500 Subject: [PATCH 021/139] add ARTMAP tests --- artlib/supervised/ARTMAP.py | 4 +- unit_tests/test_ARTMAP.py | 117 ++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 unit_tests/test_ARTMAP.py diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index 0e61810..066b209 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -52,11 +52,11 @@ def labels_a(self): @property def labels_b(self): - return self.labels_ + return self.module_b.labels_ @property def labels_ab(self): - return {"A": self.labels_a, "B": self.labels_} + return {"A": self.labels_a, "B": self.module_b.labels_} def validate_data(self, X: np.ndarray, y: np.ndarray): """ diff --git a/unit_tests/test_ARTMAP.py b/unit_tests/test_ARTMAP.py new file mode 100644 index 0000000..10d8a1b --- /dev/null +++ b/unit_tests/test_ARTMAP.py @@ -0,0 +1,117 @@ +import pytest +import numpy as np +from artlib.supervised.ARTMAP import ARTMAP +from artlib.elementary.FuzzyART import FuzzyART +from artlib.common.BaseART import BaseART + +# Fixture to initialize an ARTMAP instance for testing +@pytest.fixture +def artmap_model(): + module_a = FuzzyART(0.5, 0.01, 1.0) + module_b = FuzzyART(0.5, 0.01, 1.0) + return ARTMAP(module_a=module_a, module_b=module_b) + +def test_initialization(artmap_model): + # Test that the model initializes correctly + assert isinstance(artmap_model.module_a, BaseART) + assert isinstance(artmap_model.module_b, BaseART) + +def test_get_params(artmap_model): + # Test the get_params method + params = artmap_model.get_params() + assert "module_a" in params + assert "module_b" in params + +def test_labels_properties(artmap_model): + # Test the labels properties + artmap_model.module_a.labels_ = np.array([0, 1, 1]) + artmap_model.module_b.labels_ = np.array([1, 0, 0]) + + assert np.array_equal(artmap_model.labels_a, artmap_model.module_a.labels_) + assert np.array_equal(artmap_model.labels_b, artmap_model.module_b.labels_) + assert artmap_model.labels_ab == {"A": artmap_model.module_a.labels_, "B": artmap_model.module_b.labels_} + +def test_validate_data(artmap_model): + # Test the validate_data method + X = np.random.rand(10, 5) + y = np.random.rand(10, 5) + X_prep, y_prep = artmap_model.prepare_data(X, y) + + artmap_model.validate_data(X_prep, y_prep) + + # Test invalid input data + X_invalid = np.random.rand(0, 5) + with pytest.raises(AssertionError): + artmap_model.validate_data(X_invalid, y) + +def test_prepare_and_restore_data(artmap_model): + # Test prepare_data and restore_data methods + X = np.random.rand(5, 5) + y = np.random.rand(5, 5) + + X_prep, y_prep = artmap_model.prepare_data(X, y) + + X_restored, y_restored = artmap_model.restore_data(X_prep, y_prep) + assert np.allclose(X_restored, X) + assert np.allclose(y_restored, y) + +def test_fit(artmap_model): + # Test the fit method + X = np.random.rand(10, 5) + y = np.random.rand(10, 5) + + # Prepare data before fitting + X_prep, y_prep = artmap_model.prepare_data(X, y) + artmap_model.fit(X_prep, y_prep, max_iter=1) + + assert artmap_model.module_a.labels_.shape[0] == X.shape[0] + assert artmap_model.module_b.labels_.shape[0] == y.shape[0] + +def test_partial_fit(artmap_model): + # Test the partial_fit method + X = np.random.rand(10, 5) + y = np.random.rand(10, 5) + + # Prepare data before partial fitting + X_prep, y_prep = artmap_model.prepare_data(X, y) + artmap_model.partial_fit(X_prep, y_prep) + + assert artmap_model.module_a.labels_.shape[0] == X.shape[0] + assert artmap_model.module_b.labels_.shape[0] == y.shape[0] + +def test_predict(artmap_model): + # Test the predict method + X = np.random.rand(10, 5) + y = np.random.rand(10, 5) + + # Prepare data before fitting and predicting + X_prep, y_prep = artmap_model.prepare_data(X, y) + artmap_model.fit(X_prep, y_prep, max_iter=1) + + predictions = artmap_model.predict(X_prep) + assert predictions.shape[0] == X.shape[0] + +def test_predict_ab(artmap_model): + # Test the predict_ab method + X = np.random.rand(10, 5) + y = np.random.rand(10, 5) + + # Prepare data before fitting and predicting + X_prep, y_prep = artmap_model.prepare_data(X, y) + artmap_model.fit(X_prep, y_prep, max_iter=1) + + predictions_a, predictions_b = artmap_model.predict_ab(X_prep) + assert predictions_a.shape[0] == X.shape[0] + assert predictions_b.shape[0] == X.shape[0] + +def test_predict_regression(artmap_model): + # Test the predict_regression method + X = np.random.rand(10, 5) + y = np.random.rand(10, 5) + + # Prepare data before fitting and predicting regression + X_prep, y_prep = artmap_model.prepare_data(X, y) + artmap_model.fit(X_prep, y_prep, max_iter=1) + + regression_preds = artmap_model.predict_regression(X_prep) + assert regression_preds.shape[0] == X.shape[0] From 68dc5819c318f5041447f18c3f655b669ecd0c73 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 10 Oct 2024 18:01:41 -0500 Subject: [PATCH 022/139] simple artmap tests --- unit_tests/test_ARTMAP.py | 7 ++ unit_tests/test_SimpleARTMAP.py | 125 ++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 unit_tests/test_SimpleARTMAP.py diff --git a/unit_tests/test_ARTMAP.py b/unit_tests/test_ARTMAP.py index 10d8a1b..b731869 100644 --- a/unit_tests/test_ARTMAP.py +++ b/unit_tests/test_ARTMAP.py @@ -33,6 +33,7 @@ def test_labels_properties(artmap_model): def test_validate_data(artmap_model): # Test the validate_data method + np.random.seed(42) X = np.random.rand(10, 5) y = np.random.rand(10, 5) X_prep, y_prep = artmap_model.prepare_data(X, y) @@ -46,6 +47,7 @@ def test_validate_data(artmap_model): def test_prepare_and_restore_data(artmap_model): # Test prepare_data and restore_data methods + np.random.seed(42) X = np.random.rand(5, 5) y = np.random.rand(5, 5) @@ -57,6 +59,7 @@ def test_prepare_and_restore_data(artmap_model): def test_fit(artmap_model): # Test the fit method + np.random.seed(42) X = np.random.rand(10, 5) y = np.random.rand(10, 5) @@ -69,6 +72,7 @@ def test_fit(artmap_model): def test_partial_fit(artmap_model): # Test the partial_fit method + np.random.seed(42) X = np.random.rand(10, 5) y = np.random.rand(10, 5) @@ -81,6 +85,7 @@ def test_partial_fit(artmap_model): def test_predict(artmap_model): # Test the predict method + np.random.seed(42) X = np.random.rand(10, 5) y = np.random.rand(10, 5) @@ -93,6 +98,7 @@ def test_predict(artmap_model): def test_predict_ab(artmap_model): # Test the predict_ab method + np.random.seed(42) X = np.random.rand(10, 5) y = np.random.rand(10, 5) @@ -106,6 +112,7 @@ def test_predict_ab(artmap_model): def test_predict_regression(artmap_model): # Test the predict_regression method + np.random.seed(42) X = np.random.rand(10, 5) y = np.random.rand(10, 5) diff --git a/unit_tests/test_SimpleARTMAP.py b/unit_tests/test_SimpleARTMAP.py new file mode 100644 index 0000000..2a53b6c --- /dev/null +++ b/unit_tests/test_SimpleARTMAP.py @@ -0,0 +1,125 @@ +import pytest +import numpy as np +from artlib.supervised.SimpleARTMAP import SimpleARTMAP +from artlib.elementary.FuzzyART import FuzzyART +from artlib.common.BaseART import BaseART +from sklearn.utils.validation import NotFittedError + +# Fixture to initialize a SimpleARTMAP instance for testing +@pytest.fixture +def simple_artmap_model(): + module_a = FuzzyART(0.5, 0.01, 1.0) + return SimpleARTMAP(module_a=module_a) + +def test_initialization(simple_artmap_model): + # Test that the model initializes correctly + assert isinstance(simple_artmap_model.module_a, BaseART) + +def test_get_params(simple_artmap_model): + # Test the get_params method + params = simple_artmap_model.get_params() + assert "module_a" in params + +def test_validate_data(simple_artmap_model): + # Test the validate_data method + X = np.random.rand(10, 5) + y = np.random.randint(0, 2, size=10) + X_prep = simple_artmap_model.prepare_data(X) + + X_valid, y_valid = simple_artmap_model.validate_data(X_prep, y) + assert X_valid.shape == X_prep.shape + assert y_valid.shape == y.shape + + # Test invalid input data + X_invalid = np.random.rand(0, 5) + with pytest.raises(ValueError): + simple_artmap_model.validate_data(X_invalid, y) + +def test_prepare_and_restore_data(simple_artmap_model): + # Test prepare_data and restore_data methods + X = np.random.rand(10, 5) + + X_prep = simple_artmap_model.prepare_data(X) + + X_restored = simple_artmap_model.restore_data(X_prep) + assert np.allclose(X_restored, X) + +def test_fit(simple_artmap_model): + # Test the fit method + X = np.random.rand(10, 5) + y = np.random.randint(0, 2, size=10) + + # Prepare data before fitting + X_prep = simple_artmap_model.prepare_data(X) + simple_artmap_model.fit(X_prep, y, max_iter=1) + + assert simple_artmap_model.module_a.labels_.shape[0] == X.shape[0] + +def test_partial_fit(simple_artmap_model): + # Test the partial_fit method + X = np.random.rand(10, 5) + y = np.random.randint(0, 2, size=10) + + # Prepare data before partial fitting + X_prep = simple_artmap_model.prepare_data(X) + simple_artmap_model.partial_fit(X_prep, y) + + assert simple_artmap_model.module_a.labels_.shape[0] == X.shape[0] + +def test_predict(simple_artmap_model): + # Test the predict method + X = np.random.rand(10, 5) + y = np.random.randint(0, 2, size=10) + + # Prepare data before fitting and predicting + X_prep = simple_artmap_model.prepare_data(X) + simple_artmap_model.fit(X_prep, y, max_iter=1) + + predictions = simple_artmap_model.predict(X_prep) + assert predictions.shape[0] == X.shape[0] + +def test_predict_ab(simple_artmap_model): + # Test the predict_ab method + X = np.random.rand(10, 5) + y = np.random.randint(0, 2, size=10) + + # Prepare data before fitting and predicting + X_prep = simple_artmap_model.prepare_data(X) + simple_artmap_model.fit(X_prep, y, max_iter=1) + + predictions_a, predictions_b = simple_artmap_model.predict_ab(X_prep) + assert predictions_a.shape[0] == X.shape[0] + assert predictions_b.shape[0] == X.shape[0] + +def test_predict_not_fitted(simple_artmap_model): + # Test that predict raises an error if the model is not fitted + X = np.random.rand(10, 5) + + with pytest.raises(NotFittedError): + simple_artmap_model.predict(X) + +def test_step_fit(simple_artmap_model): + # Test the step_fit method + X = np.random.rand(10, 5) + y = np.random.randint(0, 2, size=10) + + # Prepare data before fitting + X_prep = simple_artmap_model.prepare_data(X) + simple_artmap_model.module_a.W = [] + + # Run step_fit for the first sample + c_a = simple_artmap_model.step_fit(X_prep[0], y[0]) + assert isinstance(c_a, int) # Ensure the result is an integer cluster label + +def test_step_pred(simple_artmap_model): + # Test the step_pred method + X = np.random.rand(10, 5) + y = np.random.randint(0, 2, size=10) + + # Prepare data before fitting + X_prep = simple_artmap_model.prepare_data(X) + simple_artmap_model.fit(X_prep, y, max_iter=1) + + c_a, c_b = simple_artmap_model.step_pred(X_prep[0]) + assert isinstance(c_a, int) + assert isinstance(c_b, np.int32) From dc77f419439cf4a63b81613942dc8930b755b041 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 10 Oct 2024 18:25:14 -0500 Subject: [PATCH 023/139] add SMART tests --- artlib/hierarchical/DeepARTMAP.py | 6 ++-- artlib/hierarchical/SMART.py | 10 +++--- unit_tests/test_SMART.py | 51 +++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 unit_tests/test_SMART.py diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index 61694c8..de99323 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -23,7 +23,7 @@ def __init__(self, modules: list[BaseART]): """ assert len(modules) >= 1, "Must provide at least one ART module" self.modules = modules - self.layers: list[BaseARTMAP] + self.layers: list[BaseARTMAP] = [] self.is_supervised: Optional[bool] = None def get_params(self, deep: bool = True) -> dict: @@ -239,12 +239,12 @@ def partial_fit(self, X: list[np.ndarray], y: Optional[np.ndarray] = None, match self.layers = cast(list[BaseARTMAP], [ARTMAP(self.modules[1], self.modules[0])]) + \ cast(list[BaseARTMAP], [SimpleARTMAP(self.modules[i]) for i in range(2, self.n_modules)]) assert not self.is_supervised, "Labels were not previously provided. Do not provide labels to continue partial fit." + self.layers[0] = self.layers[0].partial_fit(X[1], X[0], match_reset_method=match_reset_method, epsilon=epsilon) x_i = 2 n_samples = X[0].shape[0] - - for art_i in range(1, self.n_modules): + for art_i in range(1, self.n_layers): y_i = self.layers[art_i-1].labels_a[-n_samples:] self.layers[art_i] = self.layers[art_i].partial_fit(X[x_i], y_i, match_reset_method=match_reset_method, epsilon=epsilon) x_i += 1 diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index a820618..b3f4e39 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -27,10 +27,10 @@ def __init__(self, base_ART_class: Type, rho_values: Union[list[float], np.ndarr self.rho_values = rho_values layer_params = [dict(base_params, **{"rho": rho}) for rho in self.rho_values] - layers = [base_ART_class(**params, **kwargs) for params in layer_params] - for layer in layers: - assert isinstance(layer, BaseART), "Only elementary ART-like objects are supported" - super().__init__(layers) + modules = [base_ART_class(**params, **kwargs) for params in layer_params] + for module in modules: + assert isinstance(module, BaseART), "Only elementary ART-like objects are supported" + super().__init__(modules) def prepare_data(self, X: np.ndarray) -> np.ndarray: """ @@ -79,7 +79,7 @@ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, max_iter=1, match_r 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): X_list = [X] * self.n_modules - return self.partial_fit(X_list, match_reset_method=match_reset_method, epsilon=epsilon) + 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): """ diff --git a/unit_tests/test_SMART.py b/unit_tests/test_SMART.py new file mode 100644 index 0000000..b6a3126 --- /dev/null +++ b/unit_tests/test_SMART.py @@ -0,0 +1,51 @@ +import pytest +import numpy as np +from artlib.hierarchical.SMART import SMART +from artlib.elementary.FuzzyART import FuzzyART +from artlib.common.BaseART import BaseART +from matplotlib.axes import Axes + +# Fixture to initialize a SMART instance for testing +@pytest.fixture +def smart_model(): + base_params = {"alpha": 0.01, "beta": 1.0} + rho_values = [0.2, 0.5, 0.7] + return SMART(FuzzyART, rho_values, base_params) + +def test_initialization(smart_model): + # Test that the model initializes correctly + assert len(smart_model.rho_values) == 3 + assert isinstance(smart_model.modules[0], BaseART) + assert isinstance(smart_model.modules[1], BaseART) + assert isinstance(smart_model.modules[2], BaseART) + +def test_prepare_and_restore_data(smart_model): + # Test prepare_data and restore_data methods + X = np.random.rand(10, 5) + + X_prep = smart_model.prepare_data(X) + + X_restored = smart_model.restore_data(X_prep) + assert np.allclose(X_restored, X) + +def test_fit(smart_model): + # Test the fit method + X = np.random.rand(10, 5) + + # Prepare data before fitting + X_prep = smart_model.prepare_data(X) + smart_model.fit(X_prep, max_iter=1) + + assert smart_model.modules[0].labels_.shape[0] == X.shape[0] + +def test_partial_fit(smart_model): + # Test the partial_fit method + X = np.random.rand(10, 5) + + # Prepare data before partial fitting + X_prep = smart_model.prepare_data(X) + print(smart_model.n_modules) + smart_model.partial_fit(X_prep) + + assert smart_model.modules[0].labels_.shape[0] == X.shape[0] + From 3dd9904ca646c76cc74825899c2f0e66b579cfad Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 10 Oct 2024 18:42:29 -0500 Subject: [PATCH 024/139] add deep artmap tests --- artlib/common/BaseART.py | 2 +- artlib/common/BaseARTMAP.py | 2 +- unit_tests/test_DeepARTMAP.py | 142 ++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 unit_tests/test_DeepARTMAP.py diff --git a/artlib/common/BaseART.py b/artlib/common/BaseART.py index 3e11ce4..91f19a1 100644 --- a/artlib/common/BaseART.py +++ b/artlib/common/BaseART.py @@ -69,7 +69,7 @@ def set_params(self, **params): # Simple optimization to gain speed (inspect is slow) return self valid_params = self.get_params(deep=True) - local_params = dict() + local_params = dict(valid_params) nested_params = defaultdict(dict) # grouped by prefix for key, value in params.items(): diff --git a/artlib/common/BaseARTMAP.py b/artlib/common/BaseARTMAP.py index d54f4b1..cf2f694 100644 --- a/artlib/common/BaseARTMAP.py +++ b/artlib/common/BaseARTMAP.py @@ -25,7 +25,7 @@ def set_params(self, **params): # Simple optimization to gain speed (inspect is slow) return self valid_params = self.get_params(deep=True) - local_params = dict() + local_params = dict(valid_params) nested_params = defaultdict(dict) # grouped by prefix for key, value in params.items(): diff --git a/unit_tests/test_DeepARTMAP.py b/unit_tests/test_DeepARTMAP.py new file mode 100644 index 0000000..03df0ee --- /dev/null +++ b/unit_tests/test_DeepARTMAP.py @@ -0,0 +1,142 @@ +import pytest +import numpy as np +from artlib.hierarchical.DeepARTMAP import DeepARTMAP +from artlib.supervised.SimpleARTMAP import SimpleARTMAP +from artlib.supervised.ARTMAP import ARTMAP +from artlib.elementary.FuzzyART import FuzzyART +from artlib.common.BaseART import BaseART + + +# Fixture to initialize a DeepARTMAP instance for testing +@pytest.fixture +def deep_artmap_model(): + module_a = FuzzyART(0.5, 0.01, 1.0) + module_b = FuzzyART(0.7, 0.01, 1.0) + return DeepARTMAP(modules=[module_a, module_b]) + + +def test_initialization(deep_artmap_model): + # Test that the model initializes correctly + assert isinstance(deep_artmap_model.modules[0], BaseART) + assert isinstance(deep_artmap_model.modules[1], BaseART) + assert len(deep_artmap_model.modules) == 2 + + +def test_get_params(deep_artmap_model): + # Test the get_params method + params = deep_artmap_model.get_params() + assert "module_0" in params + assert "module_1" in params + + +def test_set_params(deep_artmap_model): + # Test the set_params method + deep_artmap_model.set_params(module_0__rho=0.6) + assert deep_artmap_model.modules[0].params["rho"] == 0.6 + + +def test_validate_data(deep_artmap_model): + # Test the validate_data method + X = [np.random.rand(10, 5), np.random.rand(10, 5)] + y = np.random.randint(0, 2, size=10) + deep_artmap_model.validate_data(X, y) + + # Test invalid input data + X_invalid = [np.random.rand(9, 5), np.random.rand(10, 5)] + with pytest.raises(AssertionError): + deep_artmap_model.validate_data(X_invalid, y) + + +def test_prepare_and_restore_data(deep_artmap_model): + # Test prepare_data and restore_data methods + X = [np.random.rand(10, 5), np.random.rand(10, 5)] + + X_prep, _ = deep_artmap_model.prepare_data(X) + + X_restored, _ = deep_artmap_model.restore_data(X_prep) + assert np.allclose(X_restored[0], X[0]) + assert np.allclose(X_restored[1], X[1]) + + +def test_fit_supervised(deep_artmap_model): + # Test the supervised fit method + X = [np.random.rand(10, 5), np.random.rand(10, 5)] + y = np.random.randint(0, 2, size=10) + + # Prepare data before fitting + X_prep, _ = deep_artmap_model.prepare_data(X) + deep_artmap_model.fit(X_prep, y, max_iter=1) + + assert deep_artmap_model.layers[0].labels_.shape[0] == X[0].shape[0] + + +def test_fit_unsupervised(deep_artmap_model): + # Test the unsupervised fit method + X = [np.random.rand(10, 5), np.random.rand(10, 5)] + + # Prepare data before fitting + X_prep, _ = deep_artmap_model.prepare_data(X) + deep_artmap_model.fit(X_prep, max_iter=1) + + assert deep_artmap_model.layers[0].labels_a.shape[0] == X[0].shape[0] + + +def test_partial_fit_supervised(deep_artmap_model): + # Test the supervised partial_fit method + X = [np.random.rand(10, 5), np.random.rand(10, 5)] + y = np.random.randint(0, 2, size=10) + + # Prepare data before partial fitting + X_prep, _ = deep_artmap_model.prepare_data(X) + deep_artmap_model.partial_fit(X_prep, y) + + assert deep_artmap_model.layers[0].labels_.shape[0] == X[0].shape[0] + + +def test_partial_fit_unsupervised(deep_artmap_model): + # Test the unsupervised partial_fit method + X = [np.random.rand(10, 5), np.random.rand(10, 5)] + + # Prepare data before partial fitting + X_prep, _ = deep_artmap_model.prepare_data(X) + deep_artmap_model.partial_fit(X_prep) + + assert deep_artmap_model.layers[0].labels_a.shape[0] == X[0].shape[0] + + +def test_predict(deep_artmap_model): + # Test the predict method + X = [np.random.rand(10, 5), np.random.rand(10, 5)] + + # Prepare data before fitting and predicting + X_prep, _ = deep_artmap_model.prepare_data(X) + deep_artmap_model.fit(X_prep, max_iter=1) + + predictions = deep_artmap_model.predict(X_prep) + assert predictions[-1].shape[0] == X[-1].shape[0] + + +def test_labels_deep(deep_artmap_model): + # Test the labels_deep_ method + X = [np.random.rand(10, 5), np.random.rand(10, 5)] + y = np.random.randint(0, 2, size=10) + + # Prepare data before fitting and predicting + X_prep, _ = deep_artmap_model.prepare_data(X) + deep_artmap_model.fit(X_prep, y, max_iter=1) + + labels_deep = deep_artmap_model.labels_deep_ + assert labels_deep.shape == (10, 3) + + +def test_map_deep(deep_artmap_model): + # Test the map_deep method + X = [np.random.rand(10, 5), np.random.rand(10, 5)] + y = np.random.randint(0, 2, size=10) + + # Prepare data before fitting + X_prep, _ = deep_artmap_model.prepare_data(X) + deep_artmap_model.fit(X_prep, y, max_iter=1) + + mapped_label = deep_artmap_model.map_deep(0, deep_artmap_model.layers[0].labels_a[0]) + assert isinstance(mapped_label.tolist(), int) From 0e8ddcca876433c55b4cbeb6a3d5f0b8b616a9a1 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 10 Oct 2024 19:20:30 -0500 Subject: [PATCH 025/139] add topo art tests --- artlib/topological/TopoART.py | 10 ++-- unit_tests/test_TopoART.py | 91 +++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 unit_tests/test_TopoART.py diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index c54994a..461156e 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -19,14 +19,14 @@ class TopoART(BaseART): - def __init__(self, base_module: BaseART, betta_lower: float, tau: int, phi: int): + def __init__(self, base_module: BaseART, beta_lower: float, tau: int, phi: int): assert isinstance(base_module, BaseART) if hasattr(base_module, "base_module"): warn( f"{base_module.__class__.__name__} is an abstraction of the BaseART class. " f"This module will only make use of the base_module {base_module.base_module.__class__.__name__}" ) - params = dict(base_module.params, **{"beta_lower": betta_lower, "tau": tau, "phi": phi}) + params = dict(base_module.params, **{"beta_lower": beta_lower, "tau": tau, "phi": phi}) super().__init__(params) self.base_module = base_module self.adjacency = np.zeros([], dtype=int) @@ -194,7 +194,11 @@ def add_weight(self, new_w: np.ndarray): def prune(self, X: np.ndarray): - self._permanent_mask += (np.array(self.weight_sample_counter_) >= self.phi) + a = np.array(self.weight_sample_counter_).reshape(-1,) >= self.phi + b = self._permanent_mask + print(a.shape, b.shape) + + self._permanent_mask += np.array(self.weight_sample_counter_).reshape(-1,) >= self.phi perm_labels = np.where(self._permanent_mask)[0] self.W = [w for w, pm in zip(self.W, self._permanent_mask) if pm] diff --git a/unit_tests/test_TopoART.py b/unit_tests/test_TopoART.py new file mode 100644 index 0000000..8fb9678 --- /dev/null +++ b/unit_tests/test_TopoART.py @@ -0,0 +1,91 @@ +import pytest +import numpy as np +from artlib.topological.TopoART import TopoART +from artlib.elementary.FuzzyART import FuzzyART + +@pytest.fixture +def topoart_model(): + base_module = FuzzyART(0.5, 0.01, 1.0) + return TopoART(base_module, beta_lower=0.5, tau=10, phi=5) + +def test_initialization(topoart_model): + # Test that the model initializes correctly + assert isinstance(topoart_model.base_module, FuzzyART) + assert topoart_model.params["beta_lower"] == 0.5 + assert topoart_model.params["tau"] == 10 + assert topoart_model.params["phi"] == 5 + +def test_validate_params(): + # Test the validate_params method + valid_params = {"beta": 0.8, "beta_lower": 0.5, "tau": 10, "phi": 5} + TopoART.validate_params(valid_params) + + invalid_params = {"beta": 0.4, "beta_lower": 0.5, "tau": 10, "phi": 5} # beta must be >= beta_lower + with pytest.raises(AssertionError): + TopoART.validate_params(invalid_params) + +def test_get_cluster_centers(topoart_model): + # Test the get_cluster_centers method + topoart_model.base_module.W = [np.array([0.5, 1.0, 0.5, 1.0]), np.array([0.1, 0.4, 0.5, 0.4])] + centers = topoart_model.get_cluster_centers() + print(centers) + assert len(centers) == 2 + assert np.allclose(centers[0], np.array([0.5, 0.5])) + assert np.allclose(centers[1], np.array([0.3, 0.5])) + +def test_prepare_and_restore_data(topoart_model): + # Test prepare_data and restore_data methods + X = np.random.rand(10, 2) + + X_prep = topoart_model.prepare_data(X) + + X_restored = topoart_model.restore_data(X_prep) + assert np.allclose(X_restored, X) + +def test_step_fit(topoart_model): + # Test the step_fit method with base_module's internal methods + X = np.random.rand(10, 2) + X_prep = topoart_model.prepare_data(X) + topoart_model.validate_data(X_prep) + + topoart_model.base_module.W = [] + label = topoart_model.step_fit(X_prep[0,:]) + + assert isinstance(label, int) # Ensure the result is an integer cluster label + assert label == 0 # First label should be 0 + + # Add more data and check the adjacency matrix and labels + for i in range(1, 10): + label = topoart_model.step_fit(X_prep[i,:]) + assert isinstance(label, int) + +def test_adjacency_matrix(topoart_model): + # Test that the adjacency matrix updates correctly + np.random.seed(42) + X = np.random.rand(10, 2) + X_prep = topoart_model.prepare_data(X) + topoart_model.validate_data(X_prep) + + topoart_model.base_module.W = [] + topoart_model.step_fit(X_prep[0,:]) + assert topoart_model.adjacency.shape == (1, 1) + + topoart_model.step_fit(X_prep[1,:]) + assert topoart_model.adjacency.shape == (1, 1) + + # Add more data and check the adjacency matrix + topoart_model.step_fit(X_prep[2,:]) + assert topoart_model.adjacency.shape == (2, 2) + +def test_prune(topoart_model): + # Test the pruning mechanism + np.random.seed(42) + X = np.random.rand(10, 2) + topoart_model.base_module.W = [np.random.rand(2) for _ in range(5)] + topoart_model.weight_sample_counter_ = [2, 6, 6, 20, 25] # Sample counter for pruning + topoart_model._permanent_mask = np.zeros((5,), dtype=bool) + topoart_model.adjacency = np.random.randint(0,10, (5,5)) + topoart_model.labels_ = np.random.randint(0,5,(10,)) + + topoart_model.prune(X) + assert len(topoart_model.W) == 4 # W should have 4 remaining weights after pruning From 4e87e44f5a5189f274f1a05347c2f5c8fec32619 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 10 Oct 2024 20:52:29 -0500 Subject: [PATCH 026/139] add fusion art tests --- artlib/fusion/FusionART.py | 50 +++++++++++- unit_tests/test_FusionART.py | 153 +++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 unit_tests/test_FusionART.py diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index 0728680..e314a6d 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -121,6 +121,34 @@ def check_dimensions(self, X: np.ndarray): """ assert X.shape[1] == self.dim_, "Invalid data shape" + def prepare_data(self, X: np.ndarray) -> np.ndarray: + """ + prepare data for clustering + + Parameters: + - X: data set + + Returns: + normalized data + """ + channel_data = self.split_channel_data(X) + 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: + """ + restore data to state prior to preparation + + Parameters: + - X: data set + + Returns: + restored data + """ + channel_data = self.split_channel_data(X) + restored_channel_data = [self.modules[i].restore_data(channel_data[i]) for i in range(self.n)] + return self.join_channel_data(restored_channel_data) + 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 @@ -412,4 +440,24 @@ def join_channel_data(self, channel_data: List[np.ndarray], skip_channels: List[ formatted_channel_data.append(0.5*np.ones((n_samples, self._channel_indices[k][1]-self._channel_indices[k][0]))) X = np.hstack(formatted_channel_data) - return X \ No newline at end of file + return X + + def split_channel_data(self, joined_data: np.ndarray, skip_channels: List[int] = []) -> List[np.ndarray]: + skip_channels = [self.n + k if k < 0 else k for k in skip_channels] + + channel_data = [] + current_col = 0 + + for k in range(self.n): + start_idx, end_idx = self._channel_indices[k] + channel_width = end_idx - start_idx + + if k not in skip_channels: + # Extract the original channel data + channel_data.append(joined_data[:, current_col:current_col + channel_width]) + current_col += channel_width + else: + # If this channel was skipped, we know it was filled with 0.5, so we skip those columns + current_col += channel_width + + return channel_data diff --git a/unit_tests/test_FusionART.py b/unit_tests/test_FusionART.py new file mode 100644 index 0000000..b44e81b --- /dev/null +++ b/unit_tests/test_FusionART.py @@ -0,0 +1,153 @@ +import pytest +import numpy as np +from artlib.common.BaseART import BaseART +from artlib.fusion.FusionART import FusionART +from artlib.elementary.FuzzyART import FuzzyART + + +@pytest.fixture +def fusionart_model(): + # Initialize FusionART with two FuzzyART modules + module_a = FuzzyART(0.5, 0.01, 1.0) + module_b = FuzzyART(0.7, 0.01, 1.0) + gamma_values = np.array([0.5, 0.5]) + channel_dims = [5, 5] + return FusionART(modules=[module_a, module_b], gamma_values=gamma_values, channel_dims=channel_dims) + + +def test_initialization(fusionart_model): + # Test that the model initializes correctly + assert isinstance(fusionart_model.modules[0], BaseART) + assert isinstance(fusionart_model.modules[1], BaseART) + assert np.all(fusionart_model.params["gamma_values"] == np.array([0.5, 0.5])) + assert fusionart_model.channel_dims == [5, 5] + + +def test_validate_params(): + # Test the validate_params method + valid_params = {"gamma_values": np.array([0.5, 0.5])} + FusionART.validate_params(valid_params) + + invalid_params = {"gamma_values": np.array([0.6, 0.6])} # sum of gamma_values must be 1.0 + with pytest.raises(AssertionError): + FusionART.validate_params(invalid_params) + + +def test_get_cluster_centers(fusionart_model): + # Test the get_cluster_centers method + fusionart_model.modules[0].W = [np.array([1.0, 2.0, 3.0, 4.0, 5.0])] + fusionart_model.modules[1].W = [np.array([6.0, 7.0, 8.0, 9.0, 10.0])] + + centers = fusionart_model.get_cluster_centers() + + assert len(centers) == 1 + assert np.allclose(centers[0], np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0])) + + +def test_prepare_and_restore_data(fusionart_model): + # Test prepare_data and restore_data methods + X = np.random.rand(10, 10) + + X_prep = fusionart_model.prepare_data(X) + assert X_prep.shape == (10, 20) + + X_restored = fusionart_model.restore_data(X_prep) + assert np.allclose(X_restored, X) + + +def test_fit(fusionart_model): + # Test the fit method + X = np.random.rand(10, 10) + X_prep = fusionart_model.prepare_data(X) + + # Prepare data before fitting + fusionart_model.modules[0].W = [] + fusionart_model.modules[1].W = [] + + fusionart_model.fit(X_prep, max_iter=1) + + assert fusionart_model.modules[0].labels_.shape[0] == X_prep.shape[0] + assert fusionart_model.modules[1].labels_.shape[0] == X_prep.shape[0] + + +def test_partial_fit(fusionart_model): + # Test the partial_fit method + X = np.random.rand(10, 10) + X_prep = fusionart_model.prepare_data(X) + + # Prepare data before partial fitting + fusionart_model.modules[0].W = [] + fusionart_model.modules[1].W = [] + + fusionart_model.partial_fit(X_prep) + + assert fusionart_model.modules[0].labels_.shape[0] == X_prep.shape[0] + assert fusionart_model.modules[1].labels_.shape[0] == X_prep.shape[0] + + +def test_predict(fusionart_model): + # Test the predict method + X = np.random.rand(10, 10) + X_prep = fusionart_model.prepare_data(X) + + # Prepare data before fitting and predicting + fusionart_model.modules[0].W = [] + fusionart_model.modules[1].W = [] + + fusionart_model.fit(X_prep, max_iter=1) + + predictions = fusionart_model.predict(X_prep) + assert predictions.shape[0] == X_prep.shape[0] + + +def test_step_fit(fusionart_model): + # Test the step_fit method with base_module's internal methods + X = np.random.rand(10, 10) + X_prep = fusionart_model.prepare_data(X) + + # Prepare data before fitting + fusionart_model.modules[0].W = [] + fusionart_model.modules[1].W = [] + + # Run step_fit for the first sample + label = fusionart_model.step_fit(X_prep[0]) + assert isinstance(label, int) # Ensure the result is an integer cluster label + + +def test_step_pred(fusionart_model): + # Test the step_pred method + X = np.random.rand(10, 10) + X_prep = fusionart_model.prepare_data(X) + + # Prepare data before fitting + fusionart_model.modules[0].W = [] + fusionart_model.modules[1].W = [] + + fusionart_model.fit(X, max_iter=1) + + label = fusionart_model.step_pred(X_prep[0]) + assert isinstance(label, int) # Ensure the result is an integer + + +def test_predict_regression(fusionart_model): + # Test the predict_regression method + X = np.random.rand(10, 10) + X_prep = fusionart_model.prepare_data(X) + + # Prepare data before fitting and predicting regression + fusionart_model.modules[0].W = [] + fusionart_model.modules[1].W = [] + + fusionart_model.fit(X, max_iter=1) + + predicted_regression = fusionart_model.predict_regression(X_prep) + assert predicted_regression.shape[0] == X_prep.shape[0] + + +def test_join_channel_data(fusionart_model): + # Test the join_channel_data method + channel_1 = np.random.rand(10, 5) + channel_2 = np.random.rand(10, 5) + + X = fusionart_model.join_channel_data([channel_1, channel_2]) + assert X.shape == (10, 10) From 4ffb2f6b7ae3e5b25d2ccec0d02c5396fc8ba57c Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 10 Oct 2024 21:37:21 -0500 Subject: [PATCH 027/139] add fusion art tests --- artlib/fusion/FusionART.py | 7 ++-- unit_tests/test_FALCON.py | 0 unit_tests/test_FusionART.py | 65 ++++++++++++------------------------ 3 files changed, 25 insertions(+), 47 deletions(-) create mode 100644 unit_tests/test_FALCON.py diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index e314a6d..0749fb8 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -121,17 +121,16 @@ def check_dimensions(self, X: np.ndarray): """ assert X.shape[1] == self.dim_, "Invalid data shape" - def prepare_data(self, X: np.ndarray) -> np.ndarray: + def prepare_data(self, channel_data: List[np.ndarray]) -> np.ndarray: """ prepare data for clustering Parameters: - - X: data set + - channel_data: list of channel arrays Returns: normalized data """ - channel_data = self.split_channel_data(X) 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) @@ -147,7 +146,7 @@ def restore_data(self, X: np.ndarray) -> np.ndarray: """ channel_data = self.split_channel_data(X) restored_channel_data = [self.modules[i].restore_data(channel_data[i]) for i in range(self.n)] - return self.join_channel_data(restored_channel_data) + return restored_channel_data def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict, skip_channels: List[int] = []) -> tuple[float, Optional[dict]]: """ diff --git a/unit_tests/test_FALCON.py b/unit_tests/test_FALCON.py new file mode 100644 index 0000000..e69de29 diff --git a/unit_tests/test_FusionART.py b/unit_tests/test_FusionART.py index b44e81b..93e3940 100644 --- a/unit_tests/test_FusionART.py +++ b/unit_tests/test_FusionART.py @@ -11,7 +11,7 @@ def fusionart_model(): module_a = FuzzyART(0.5, 0.01, 1.0) module_b = FuzzyART(0.7, 0.01, 1.0) gamma_values = np.array([0.5, 0.5]) - channel_dims = [5, 5] + channel_dims = [4, 4] return FusionART(modules=[module_a, module_b], gamma_values=gamma_values, channel_dims=channel_dims) @@ -20,7 +20,7 @@ def test_initialization(fusionart_model): assert isinstance(fusionart_model.modules[0], BaseART) assert isinstance(fusionart_model.modules[1], BaseART) assert np.all(fusionart_model.params["gamma_values"] == np.array([0.5, 0.5])) - assert fusionart_model.channel_dims == [5, 5] + assert fusionart_model.channel_dims == [4, 4] def test_validate_params(): @@ -35,65 +35,52 @@ def test_validate_params(): def test_get_cluster_centers(fusionart_model): # Test the get_cluster_centers method - fusionart_model.modules[0].W = [np.array([1.0, 2.0, 3.0, 4.0, 5.0])] - fusionart_model.modules[1].W = [np.array([6.0, 7.0, 8.0, 9.0, 10.0])] + fusionart_model.modules[0].W = [np.array([0.1, 0.4, 0.5, 0.4])] + fusionart_model.modules[1].W = [np.array([0.2, 0.2, 0.2, 0.2])] centers = fusionart_model.get_cluster_centers() assert len(centers) == 1 - assert np.allclose(centers[0], np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0])) + assert np.allclose(centers[0], np.array([0.3, 0.5, 0.5, 0.5])) def test_prepare_and_restore_data(fusionart_model): # Test prepare_data and restore_data methods - X = np.random.rand(10, 10) + X = [np.random.rand(10, 2), np.random.rand(10, 2)] X_prep = fusionart_model.prepare_data(X) - assert X_prep.shape == (10, 20) + assert X_prep.shape == (10, 8) X_restored = fusionart_model.restore_data(X_prep) - assert np.allclose(X_restored, X) + assert np.allclose(X_restored[0], X[0]) + assert np.allclose(X_restored[1], X[1]) def test_fit(fusionart_model): # Test the fit method - X = np.random.rand(10, 10) + X = [np.random.rand(10, 2), np.random.rand(10, 2)] X_prep = fusionart_model.prepare_data(X) - # Prepare data before fitting - fusionart_model.modules[0].W = [] - fusionart_model.modules[1].W = [] - fusionart_model.fit(X_prep, max_iter=1) - assert fusionart_model.modules[0].labels_.shape[0] == X_prep.shape[0] - assert fusionart_model.modules[1].labels_.shape[0] == X_prep.shape[0] + assert fusionart_model.labels_.shape[0] == X_prep.shape[0] def test_partial_fit(fusionart_model): # Test the partial_fit method - X = np.random.rand(10, 10) + X = [np.random.rand(10, 2), np.random.rand(10, 2)] X_prep = fusionart_model.prepare_data(X) - # Prepare data before partial fitting - fusionart_model.modules[0].W = [] - fusionart_model.modules[1].W = [] - fusionart_model.partial_fit(X_prep) - assert fusionart_model.modules[0].labels_.shape[0] == X_prep.shape[0] - assert fusionart_model.modules[1].labels_.shape[0] == X_prep.shape[0] + assert fusionart_model.labels_.shape[0] == X_prep.shape[0] def test_predict(fusionart_model): # Test the predict method - X = np.random.rand(10, 10) + X = [np.random.rand(10, 2), np.random.rand(10, 2)] X_prep = fusionart_model.prepare_data(X) - # Prepare data before fitting and predicting - fusionart_model.modules[0].W = [] - fusionart_model.modules[1].W = [] - fusionart_model.fit(X_prep, max_iter=1) predictions = fusionart_model.predict(X_prep) @@ -102,7 +89,7 @@ def test_predict(fusionart_model): def test_step_fit(fusionart_model): # Test the step_fit method with base_module's internal methods - X = np.random.rand(10, 10) + X = [np.random.rand(10, 2), np.random.rand(10, 2)] X_prep = fusionart_model.prepare_data(X) # Prepare data before fitting @@ -116,14 +103,10 @@ def test_step_fit(fusionart_model): def test_step_pred(fusionart_model): # Test the step_pred method - X = np.random.rand(10, 10) + X = [np.random.rand(10, 2), np.random.rand(10, 2)] X_prep = fusionart_model.prepare_data(X) - # Prepare data before fitting - fusionart_model.modules[0].W = [] - fusionart_model.modules[1].W = [] - - fusionart_model.fit(X, max_iter=1) + fusionart_model.fit(X_prep, max_iter=1) label = fusionart_model.step_pred(X_prep[0]) assert isinstance(label, int) # Ensure the result is an integer @@ -131,14 +114,10 @@ def test_step_pred(fusionart_model): def test_predict_regression(fusionart_model): # Test the predict_regression method - X = np.random.rand(10, 10) + X = [np.random.rand(10, 2), np.random.rand(10, 2)] X_prep = fusionart_model.prepare_data(X) - # Prepare data before fitting and predicting regression - fusionart_model.modules[0].W = [] - fusionart_model.modules[1].W = [] - - fusionart_model.fit(X, max_iter=1) + fusionart_model.fit(X_prep, max_iter=1) predicted_regression = fusionart_model.predict_regression(X_prep) assert predicted_regression.shape[0] == X_prep.shape[0] @@ -146,8 +125,8 @@ def test_predict_regression(fusionart_model): def test_join_channel_data(fusionart_model): # Test the join_channel_data method - channel_1 = np.random.rand(10, 5) - channel_2 = np.random.rand(10, 5) + channel_1 = np.random.rand(10, 2) + channel_2 = np.random.rand(10, 2) X = fusionart_model.join_channel_data([channel_1, channel_2]) - assert X.shape == (10, 10) + assert X.shape == (10, 4) From 92623dc3fcd15ef2ca5de65ea839683a90a0b9f6 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 10 Oct 2024 23:22:30 -0500 Subject: [PATCH 028/139] add falcon tests --- artlib/reinforcement/FALCON.py | 29 +++++++- unit_tests/test_FALCON.py | 125 +++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 2 deletions(-) diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index 389db87..1a5378d 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -1,5 +1,5 @@ import numpy as np -from typing import Optional, Literal +from typing import Optional, Literal, Tuple from artlib import FusionART, BaseART, compliment_code, de_compliment_code @@ -18,6 +18,30 @@ def __init__( channel_dims=channel_dims ) + 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 + """ + 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 + """ + 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): data = self.fusion_art.join_channel_data([states, actions, rewards]) self.fusion_art = self.fusion_art.fit(data) @@ -33,9 +57,10 @@ def get_actions_and_rewards(self, state: np.ndarray, action_space: Optional[np.n if action_space is None: action_space = self.fusion_art.get_channel_centers(1) action_space = np.array(action_space) - action_space_prepared = self.fusion_art.modules[0].prepare_data(action_space) + action_space_prepared = self.fusion_art.modules[1].prepare_data(action_space) viable_clusters = [] for action in action_space_prepared: + print("action", action) data = self.fusion_art.join_channel_data([state.reshape(1, -1), action.reshape(1, -1)], skip_channels=[2]) c = self.fusion_art.predict(data, skip_channels=[2]) viable_clusters.append(c[0]) diff --git a/unit_tests/test_FALCON.py b/unit_tests/test_FALCON.py index e69de29..3d70362 100644 --- a/unit_tests/test_FALCON.py +++ b/unit_tests/test_FALCON.py @@ -0,0 +1,125 @@ +import pytest +import numpy as np +from artlib.elementary.FuzzyART import FuzzyART +from artlib.reinforcement.FALCON import FALCON + + +@pytest.fixture +def falcon_model(): + # Initialize FALCON with three FuzzyART modules + state_art = FuzzyART(0.5, 0.01, 1.0) + action_art = FuzzyART(0.7, 0.01, 1.0) + reward_art = FuzzyART(0.9, 0.01, 1.0) + channel_dims = [4, 4, 2] + return FALCON(state_art=state_art, action_art=action_art, reward_art=reward_art, channel_dims=channel_dims) + + +def test_falcon_initialization(falcon_model): + # Test that the model initializes correctly + assert isinstance(falcon_model.fusion_art.modules[0], FuzzyART) + assert isinstance(falcon_model.fusion_art.modules[1], FuzzyART) + assert isinstance(falcon_model.fusion_art.modules[2], FuzzyART) + assert falcon_model.fusion_art.channel_dims == [4, 4, 2] + + +def test_falcon_fit(falcon_model): + # Test the fit method of FALCON + states = np.random.rand(10, 2) + actions = np.random.rand(10, 2) + rewards = np.random.rand(10, 1) + + # Prepare data + states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) + + falcon_model.fit(states_prep, actions_prep, rewards_prep) + + assert len(falcon_model.fusion_art.W) > 0 + assert falcon_model.fusion_art.labels_.shape[0] == states_prep.shape[0] + + +def test_falcon_partial_fit(falcon_model): + # Test the partial_fit method of FALCON + states = np.random.rand(10, 2) + actions = np.random.rand(10, 2) + rewards = np.random.rand(10, 1) + + # Prepare data + states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) + + falcon_model.partial_fit(states_prep, actions_prep, rewards_prep) + + assert len(falcon_model.fusion_art.W) > 0 + assert falcon_model.fusion_art.labels_.shape[0] == states_prep.shape[0] + + +def test_falcon_get_actions_and_rewards(falcon_model): + # Test the get_actions_and_rewards method + states = np.random.rand(10, 2) + actions = np.random.rand(10, 2) + rewards = np.random.rand(10, 1) + + # Prepare data + print("actions\n", actions) + print("actions_min", np.min(actions,axis=0)) + states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) + print("actions_prep\n", actions_prep) + + falcon_model.fit(states_prep, actions_prep, rewards_prep) + print(states_prep[0,:]) + + action_space, rewards = falcon_model.get_actions_and_rewards(states_prep[0,:]) + + assert action_space.shape[0] > 0 + assert rewards.shape[0] > 0 + + +# def test_falcon_get_action(falcon_model): +# # Test the get_action method of FALCON +# states = np.random.rand(10, 2) +# actions = np.random.rand(10, 2) +# rewards = np.random.rand(10, 1) +# +# # Prepare data +# states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) +# +# falcon_model.fit(states_prep, actions_prep, rewards_prep) +# +# action = falcon_model.get_action(states_prep[0,:]) +# +# assert action.shape[0] == actions.shape[1] + +# +# def test_falcon_get_probabilistic_action(falcon_model): +# # Test the get_probabilistic_action method of FALCON +# states = np.random.rand(10, 2) +# actions = np.random.rand(10, 2) +# rewards = np.random.rand(10, 1) +# +# # Prepare data +# states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) +# +# falcon_model.fit(states_prep, actions_prep, rewards_prep) +# +# action = falcon_model.get_probabilistic_action(states_prep[0,:]) +# +# assert action.shape[0] == actions.shape[1] + + +def test_falcon_get_rewards(falcon_model): + # Test the get_rewards method of FALCON + states = np.random.rand(10, 2) + actions = np.random.rand(10, 2) + rewards = np.random.rand(10, 1) + + # Prepare data + states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) + + print(states_prep.shape) + print(actions_prep.shape) + print(rewards_prep.shape) + + falcon_model.fit(states_prep, actions_prep, rewards_prep) + + predicted_rewards = falcon_model.get_rewards(states_prep, actions_prep) + + assert predicted_rewards.shape == rewards.shape From 8bf61c0d2389b68fae2d49ef344faeabda898282 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 11 Oct 2024 15:58:05 -0500 Subject: [PATCH 029/139] add falcon tests --- artlib/reinforcement/FALCON.py | 1 - unit_tests/test_FALCON.py | 67 +++++++++++++++------------------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index 1a5378d..84e9dd4 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -60,7 +60,6 @@ def get_actions_and_rewards(self, state: np.ndarray, action_space: Optional[np.n action_space_prepared = self.fusion_art.modules[1].prepare_data(action_space) viable_clusters = [] for action in action_space_prepared: - print("action", action) data = self.fusion_art.join_channel_data([state.reshape(1, -1), action.reshape(1, -1)], skip_channels=[2]) c = self.fusion_art.predict(data, skip_channels=[2]) viable_clusters.append(c[0]) diff --git a/unit_tests/test_FALCON.py b/unit_tests/test_FALCON.py index 3d70362..813f431 100644 --- a/unit_tests/test_FALCON.py +++ b/unit_tests/test_FALCON.py @@ -59,10 +59,7 @@ def test_falcon_get_actions_and_rewards(falcon_model): rewards = np.random.rand(10, 1) # Prepare data - print("actions\n", actions) - print("actions_min", np.min(actions,axis=0)) states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) - print("actions_prep\n", actions_prep) falcon_model.fit(states_prep, actions_prep, rewards_prep) print(states_prep[0,:]) @@ -73,36 +70,36 @@ def test_falcon_get_actions_and_rewards(falcon_model): assert rewards.shape[0] > 0 -# def test_falcon_get_action(falcon_model): -# # Test the get_action method of FALCON -# states = np.random.rand(10, 2) -# actions = np.random.rand(10, 2) -# rewards = np.random.rand(10, 1) -# -# # Prepare data -# states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) -# -# falcon_model.fit(states_prep, actions_prep, rewards_prep) -# -# action = falcon_model.get_action(states_prep[0,:]) -# -# assert action.shape[0] == actions.shape[1] - -# -# def test_falcon_get_probabilistic_action(falcon_model): -# # Test the get_probabilistic_action method of FALCON -# states = np.random.rand(10, 2) -# actions = np.random.rand(10, 2) -# rewards = np.random.rand(10, 1) -# -# # Prepare data -# states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) -# -# falcon_model.fit(states_prep, actions_prep, rewards_prep) -# -# action = falcon_model.get_probabilistic_action(states_prep[0,:]) -# -# assert action.shape[0] == actions.shape[1] +def test_falcon_get_action(falcon_model): + # Test the get_action method of FALCON + states = np.random.rand(10, 2) + actions = np.random.rand(10, 2) + rewards = np.random.rand(10, 1) + + # Prepare data + states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) + + falcon_model.fit(states_prep, actions_prep, rewards_prep) + + action = falcon_model.get_action(states_prep[0,:]) + + assert action.shape[0] == actions.shape[1] + + +def test_falcon_get_probabilistic_action(falcon_model): + # Test the get_probabilistic_action method of FALCON + states = np.random.rand(10, 2) + actions = np.random.rand(10, 2) + rewards = np.random.rand(10, 1) + + # Prepare data + states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) + + falcon_model.fit(states_prep, actions_prep, rewards_prep) + + action = falcon_model.get_probabilistic_action(states_prep[0,:]) + + assert isinstance(action.tolist(), float) def test_falcon_get_rewards(falcon_model): @@ -114,10 +111,6 @@ def test_falcon_get_rewards(falcon_model): # Prepare data states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) - print(states_prep.shape) - print(actions_prep.shape) - print(rewards_prep.shape) - falcon_model.fit(states_prep, actions_prep, rewards_prep) predicted_rewards = falcon_model.get_rewards(states_prep, actions_prep) From dda10b2faba4c39e41b423993e6cc44a9be4affe Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 11 Oct 2024 16:03:36 -0500 Subject: [PATCH 030/139] add td-falcon tests --- unit_tests/test_TD_FALCON.py | 137 +++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 unit_tests/test_TD_FALCON.py diff --git a/unit_tests/test_TD_FALCON.py b/unit_tests/test_TD_FALCON.py new file mode 100644 index 0000000..b5f01d1 --- /dev/null +++ b/unit_tests/test_TD_FALCON.py @@ -0,0 +1,137 @@ +import pytest +import numpy as np +from artlib.elementary.FuzzyART import FuzzyART +from artlib.reinforcement.FALCON import TD_FALCON + + +@pytest.fixture +def td_falcon_model(): + # Initialize TD_FALCON with three FuzzyART modules + state_art = FuzzyART(0.5, 0.01, 1.0) + action_art = FuzzyART(0.7, 0.01, 1.0) + reward_art = FuzzyART(0.9, 0.01, 1.0) + channel_dims = [4, 4, 2] + return TD_FALCON(state_art=state_art, action_art=action_art, reward_art=reward_art, channel_dims=channel_dims) + + +def test_td_falcon_initialization(td_falcon_model): + # Test that the TD_FALCON model initializes correctly + assert isinstance(td_falcon_model.fusion_art.modules[0], FuzzyART) + assert isinstance(td_falcon_model.fusion_art.modules[1], FuzzyART) + assert isinstance(td_falcon_model.fusion_art.modules[2], FuzzyART) + assert td_falcon_model.fusion_art.channel_dims == [4, 4, 2] + assert td_falcon_model.td_alpha == 1.0 + assert td_falcon_model.td_lambda == 1.0 + + +def test_td_falcon_fit_raises(td_falcon_model): + # Test that calling the fit method raises NotImplementedError + states = np.random.rand(10, 2) + actions = np.random.rand(10, 2) + rewards = np.random.rand(10, 1) + + with pytest.raises(NotImplementedError): + td_falcon_model.fit(states, actions, rewards) + + +def test_td_falcon_partial_fit(td_falcon_model): + # Test the partial_fit method of TD_FALCON + states = np.random.rand(10, 2) + actions = np.random.rand(10, 2) + rewards = np.random.rand(10, 1) + + # Prepare data + states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data(states, actions, rewards) + + td_falcon_model.partial_fit(states_prep, actions_prep, rewards_prep) + + assert len(td_falcon_model.fusion_art.W) > 0 + assert td_falcon_model.fusion_art.labels_.shape[0] == states_prep.shape[0]-1 + + +def test_td_falcon_calculate_SARSA(td_falcon_model): + # Test the calculate_SARSA method of TD_FALCON + states = np.random.rand(10, 2) + actions = np.random.rand(10, 2) + rewards = np.random.rand(10, 1) + + states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data(states, actions, rewards) + + # Test with multiple samples + states_fit, actions_fit, sarsa_rewards_fit = td_falcon_model.calculate_SARSA(states_prep, actions_prep, rewards_prep) + + assert states_fit.shape == (9, 4) # Last sample is discarded for SARSA + assert actions_fit.shape == (9, 4) + assert sarsa_rewards_fit.shape == (9, 2) + + # Test with single sample + states_fit, actions_fit, sarsa_rewards_fit = td_falcon_model.calculate_SARSA(states_prep[:1], actions_prep[:1], rewards_prep[:1]) + + assert states_fit.shape == (1, 4) + assert actions_fit.shape == (1, 4) + assert sarsa_rewards_fit.shape == (1, 2) + + +def test_td_falcon_get_actions_and_rewards(td_falcon_model): + # Test the get_actions_and_rewards method of TD_FALCON + states = np.random.rand(10, 2) + actions = np.random.rand(10, 2) + rewards = np.random.rand(10, 1) + + # Prepare data + states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data(states, actions, rewards) + + td_falcon_model.partial_fit(states_prep, actions_prep, rewards_prep) + + action_space, rewards = td_falcon_model.get_actions_and_rewards(states_prep[0, :]) + + assert action_space.shape[0] > 0 + assert rewards.shape[0] > 0 + + +def test_td_falcon_get_action(td_falcon_model): + # Test the get_action method of TD_FALCON + states = np.random.rand(10, 2) + actions = np.random.rand(10, 2) + rewards = np.random.rand(10, 1) + + # Prepare data + states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data(states, actions, rewards) + + td_falcon_model.partial_fit(states_prep, actions_prep, rewards_prep) + + action = td_falcon_model.get_action(states_prep[0, :]) + + assert action.shape[0] == actions.shape[1] + + +def test_td_falcon_get_probabilistic_action(td_falcon_model): + # Test the get_probabilistic_action method of TD_FALCON + states = np.random.rand(10, 2) + actions = np.random.rand(10, 2) + rewards = np.random.rand(10, 1) + + # Prepare data + states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data(states, actions, rewards) + + td_falcon_model.partial_fit(states_prep, actions_prep, rewards_prep) + + action = td_falcon_model.get_probabilistic_action(states_prep[0, :]) + + assert isinstance(action.tolist(), float) + + +def test_td_falcon_get_rewards(td_falcon_model): + # Test the get_rewards method of TD_FALCON + states = np.random.rand(10, 2) + actions = np.random.rand(10, 2) + rewards = np.random.rand(10, 1) + + # Prepare data + states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data(states, actions, rewards) + + td_falcon_model.partial_fit(states_prep, actions_prep, rewards_prep) + + predicted_rewards = td_falcon_model.get_rewards(states_prep, actions_prep) + + assert predicted_rewards.shape == rewards.shape From 65004e1ce5745b95298651106fdc8f31982adf36 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 11 Oct 2024 16:19:35 -0500 Subject: [PATCH 031/139] add CVI ART tests --- artlib/elementary/FuzzyART.py | 11 +++-- unit_tests/test_CVIART.py | 89 +++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 unit_tests/test_CVIART.py diff --git a/artlib/elementary/FuzzyART.py b/artlib/elementary/FuzzyART.py index 7ecef9b..3a14044 100644 --- a/artlib/elementary/FuzzyART.py +++ b/artlib/elementary/FuzzyART.py @@ -205,11 +205,12 @@ def get_cluster_centers(self) -> List[np.ndarray]: Returns: cluster centroid """ - 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 centers + # 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): new_W = [] diff --git a/unit_tests/test_CVIART.py b/unit_tests/test_CVIART.py new file mode 100644 index 0000000..f0710a0 --- /dev/null +++ b/unit_tests/test_CVIART.py @@ -0,0 +1,89 @@ +import pytest +import numpy as np +from sklearn.metrics import calinski_harabasz_score, davies_bouldin_score, silhouette_score +from artlib.elementary.FuzzyART import FuzzyART +from artlib.common.BaseART import BaseART +from artlib.cvi.CVIART import CVIART + + +@pytest.fixture +def cviart_model(): + # Initialize CVIART with a FuzzyART base module and Calinski-Harabasz validity index + base_module = FuzzyART(0.5, 0.01, 1.0) + return CVIART(base_module=base_module, validity=CVIART.CALINSKIHARABASZ) + + +def test_cviart_initialization(cviart_model): + # Test that the CVIART model initializes correctly + assert isinstance(cviart_model.base_module, BaseART) + assert cviart_model.params["validity"] == CVIART.CALINSKIHARABASZ + + +def test_cviart_validate_params(): + # Test the validate_params method + base_module = FuzzyART(0.5, 0.01, 1.0) + cviart_model = CVIART(base_module=base_module, validity=CVIART.SILHOUETTE) + valid_params = dict(base_module.params) + valid_params.update({"validity": CVIART.SILHOUETTE}) + cviart_model.validate_params(valid_params) + + invalid_params = {"validity": 999} # Invalid validity index + with pytest.raises(AssertionError): + cviart_model.validate_params(invalid_params) + + +def test_cviart_prepare_and_restore_data(cviart_model): + # Test prepare_data and restore_data methods + X = np.random.rand(10, 4) + + X_prep = cviart_model.prepare_data(X) + + X_restored = cviart_model.restore_data(X_prep) + assert np.allclose(X_restored, X) + + +def test_cviart_fit(cviart_model): + # Test the fit method of CVIART + X = np.random.rand(10, 4) + X_prep = cviart_model.prepare_data(X) + + cviart_model.fit(X_prep, max_iter=1) + + assert len(cviart_model.W) > 0 + assert cviart_model.labels_.shape[0] == X.shape[0] + + +def test_cviart_step_fit_not_implemented(cviart_model): + # Test that the step_fit method raises NotImplementedError + X = np.random.rand(10, 4) + + with pytest.raises(NotImplementedError): + cviart_model.step_fit(X[0]) + + +def test_cviart_CVI_match(cviart_model): + # Test the CVI_match method with Calinski-Harabasz index + X = np.random.rand(10, 4) + cviart_model.data = X + cviart_model.labels_ = np.random.randint(0, 2, size=(10,)) + + x = X[0] + w = np.random.rand(4) + cviart_model.base_module.W = w + + # Test that CVI_match correctly works with Calinski-Harabasz + result = cviart_model.CVI_match(x, w, 1, cviart_model.params, {"validity": CVIART.CALINSKIHARABASZ, "index": 0}, {}) + assert isinstance(result, np.bool_) + + +def test_cviart_get_cluster_centers(cviart_model): + # Test the get_cluster_centers method + cviart_model.base_module.d_min_ = np.array([0.0, 0.0]) + cviart_model.base_module.d_max_ = np.array([1.0, 1.0]) + cviart_model.base_module.W = [np.array([0.1, 0.4, 0.5, 0.4])] + + centers = cviart_model.get_cluster_centers() + + assert len(centers) == 1 + assert np.allclose(centers[0], np.array([0.3, 0.5])) + From 881b34006d2e4649809546d505c3d91052280e5c Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 11 Oct 2024 16:46:27 -0500 Subject: [PATCH 032/139] add icvi fuzzy art tests --- artlib/cvi/iCVIFuzzyArt.py | 4 +- unit_tests/test_iCVI_FuzzyART.py | 88 ++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 unit_tests/test_iCVI_FuzzyART.py diff --git a/artlib/cvi/iCVIFuzzyArt.py b/artlib/cvi/iCVIFuzzyArt.py index e16bf3e..c54aed1 100644 --- a/artlib/cvi/iCVIFuzzyArt.py +++ b/artlib/cvi/iCVIFuzzyArt.py @@ -81,8 +81,8 @@ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, match_reset_func: O if match_reset_func is None: c = self.step_fit(x, match_reset_func=self.iCVI_match, match_reset_method=match_reset_method, epsilon=epsilon) else: - match_reset_func = lambda x, w, c_, params, cache: (match_reset_func(x, w, c_, params, cache) & self.iCVI_match(x, w, c_, params, cache)) - c = self.step_fit(x, match_reset_func=match_reset_func, match_reset_method=match_reset_method, epsilon=epsilon) + match_reset_func_ = lambda x, w, c_, params, cache: (match_reset_func(x, w, c_, params, cache) & self.iCVI_match(x, w, c_, params, cache)) + c = self.step_fit(x, match_reset_func=match_reset_func_, match_reset_method=match_reset_method, epsilon=epsilon) if self.offline: params = self.iCVI.switch_label(x, self.labels_[i], c) diff --git a/unit_tests/test_iCVI_FuzzyART.py b/unit_tests/test_iCVI_FuzzyART.py new file mode 100644 index 0000000..c31e642 --- /dev/null +++ b/unit_tests/test_iCVI_FuzzyART.py @@ -0,0 +1,88 @@ +import pytest +import numpy as np +from artlib.elementary.FuzzyART import FuzzyART +from artlib.cvi.iCVIFuzzyArt import iCVIFuzzyART +from artlib.cvi.iCVIs.CalinkskiHarabasz import iCVI_CH + + +@pytest.fixture +def icvi_fuzzyart_model(): + # Initialize iCVIFuzzyART with Calinski-Harabasz validity index and offline mode + return iCVIFuzzyART(rho=0.5, alpha=0.01, beta=1.0, validity=iCVIFuzzyART.CALINSKIHARABASZ, offline=True) + + +def test_icvi_fuzzyart_initialization(icvi_fuzzyart_model): + # Test that the model initializes correctly + assert isinstance(icvi_fuzzyart_model, iCVIFuzzyART) + assert icvi_fuzzyart_model.params['validity'] == iCVIFuzzyART.CALINSKIHARABASZ + assert icvi_fuzzyart_model.offline is True + + +def test_icvi_fuzzyart_validate_params(icvi_fuzzyart_model): + # Test if validity parameter is validated correctly + assert 'validity' in icvi_fuzzyart_model.params + assert isinstance(icvi_fuzzyart_model.params['validity'], int) + assert icvi_fuzzyart_model.params['validity'] == iCVIFuzzyART.CALINSKIHARABASZ + + +def test_icvi_fuzzyart_prepare_and_restore_data(icvi_fuzzyart_model): + # Test prepare_data and restore_data methods + X = np.random.rand(10, 4) + + X_prep = icvi_fuzzyart_model.prepare_data(X) + X_restored = icvi_fuzzyart_model.restore_data(X_prep) + + assert np.allclose(X_restored, X) + + +def test_icvi_fuzzyart_fit_offline(icvi_fuzzyart_model): + # Test the fit method in offline mode + X = np.random.rand(10, 4) + X_prep = icvi_fuzzyart_model.prepare_data(X) + + icvi_fuzzyart_model.fit(X_prep) + + assert len(icvi_fuzzyart_model.W) > 0 + assert icvi_fuzzyart_model.labels_.shape[0] == X.shape[0] + + +def test_icvi_fuzzyart_iCVI_match(icvi_fuzzyart_model): + # Test the iCVI_match method with Calinski-Harabasz index + X = np.random.rand(10, 4) + X_prep = icvi_fuzzyart_model.prepare_data(X) + icvi_fuzzyart_model.data = X_prep + icvi_fuzzyart_model.labels_ = np.random.randint(0, 2, size=(10,)) + icvi_fuzzyart_model.iCVI = iCVI_CH(X[0]) + + icvi_fuzzyart_model.fit(X_prep) + + x = X_prep[0] + w = icvi_fuzzyart_model.W[0] + + # Test iCVI_match functionality + result = icvi_fuzzyart_model.iCVI_match(x, w, 0, icvi_fuzzyart_model.params, {}) + assert isinstance(result, np.bool_) + + +def test_icvi_fuzzyart_step_fit(icvi_fuzzyart_model): + # Test step_fit method raises NotImplementedError from parent FuzzyART + X = np.random.rand(10, 4) + X_prep = icvi_fuzzyart_model.prepare_data(X) + icvi_fuzzyart_model.W = [] + + label = icvi_fuzzyart_model.step_fit(X_prep[0]) + assert label == 0 + + +def test_icvi_fuzzyart_fit_with_custom_reset_function(icvi_fuzzyart_model): + # Test the fit method with a custom match_reset_func + X = np.random.rand(10, 4) + X_prep = icvi_fuzzyart_model.prepare_data(X) + + def custom_reset_func(x, w, c_, params, cache): + return True + + icvi_fuzzyart_model.fit(X_prep, match_reset_func=custom_reset_func) + + assert len(icvi_fuzzyart_model.W) > 0 + assert icvi_fuzzyart_model.labels_.shape[0] == X.shape[0] From b093522aded2414b594b9296ece290c4b8ad3ebf Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 11 Oct 2024 16:54:49 -0500 Subject: [PATCH 033/139] correct test inconsistencies --- unit_tests/test_FusionART.py | 4 ++++ unit_tests/test_SimpleARTMAP.py | 5 +++-- unit_tests/test_TopoART.py | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/unit_tests/test_FusionART.py b/unit_tests/test_FusionART.py index 93e3940..0621823 100644 --- a/unit_tests/test_FusionART.py +++ b/unit_tests/test_FusionART.py @@ -37,6 +37,10 @@ def test_get_cluster_centers(fusionart_model): # Test the get_cluster_centers method fusionart_model.modules[0].W = [np.array([0.1, 0.4, 0.5, 0.4])] fusionart_model.modules[1].W = [np.array([0.2, 0.2, 0.2, 0.2])] + fusionart_model.modules[0].d_min_ = np.array([0.0, 0.0]) + fusionart_model.modules[1].d_min_ = np.array([0.0, 0.0]) + fusionart_model.modules[0].d_max_ = np.array([1.0, 1.0]) + fusionart_model.modules[1].d_max_ = np.array([1.0, 1.0]) centers = fusionart_model.get_cluster_centers() diff --git a/unit_tests/test_SimpleARTMAP.py b/unit_tests/test_SimpleARTMAP.py index 2a53b6c..43be3c8 100644 --- a/unit_tests/test_SimpleARTMAP.py +++ b/unit_tests/test_SimpleARTMAP.py @@ -121,5 +121,6 @@ def test_step_pred(simple_artmap_model): simple_artmap_model.fit(X_prep, y, max_iter=1) c_a, c_b = simple_artmap_model.step_pred(X_prep[0]) - assert isinstance(c_a, int) - assert isinstance(c_b, np.int32) + print(type(c_a), type(c_b)) + assert isinstance(c_a, (int, np.integer)) + assert isinstance(c_b, (int, np.integer)) diff --git a/unit_tests/test_TopoART.py b/unit_tests/test_TopoART.py index 8fb9678..c80dede 100644 --- a/unit_tests/test_TopoART.py +++ b/unit_tests/test_TopoART.py @@ -27,6 +27,9 @@ def test_validate_params(): def test_get_cluster_centers(topoart_model): # Test the get_cluster_centers method topoart_model.base_module.W = [np.array([0.5, 1.0, 0.5, 1.0]), np.array([0.1, 0.4, 0.5, 0.4])] + topoart_model.base_module.d_min_ = np.array([0.0, 0.0]) + topoart_model.base_module.d_max_ = np.array([1.0, 1.0]) + centers = topoart_model.get_cluster_centers() print(centers) assert len(centers) == 2 From 7e94f369b5f201d13b4d0066f8e3998f85fa0504 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 11 Oct 2024 17:07:25 -0500 Subject: [PATCH 034/139] read the docs init --- docs/Makefile | 20 ++++++++++++++++++++ docs/make.bat | 35 +++++++++++++++++++++++++++++++++++ docs/source/conf.py | 30 ++++++++++++++++++++++++++++++ docs/source/index.rst | 17 +++++++++++++++++ pyproject.toml | 2 ++ readthedocs.yml | 14 ++++++++++++++ 6 files changed, 118 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/conf.py create mode 100644 docs/source/index.rst create mode 100644 readthedocs.yml diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..747ffb7 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..d85b753 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,30 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'AdaptiveResonanceLib' +copyright = '2024, Niklas Melton' +author = 'Niklas Melton' +release = '0.1.2' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [] + +templates_path = ['_templates'] +exclude_patterns = [] + + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'alabaster' +html_static_path = ['_static'] + +extensions = ['sphinx.ext.autodoc'] diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..eec7a6b --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,17 @@ +.. AdaptiveResonanceLib documentation master file, created by + sphinx-quickstart on Fri Oct 11 17:02:51 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +AdaptiveResonanceLib documentation +================================== + +Add your content using ``reStructuredText`` syntax. See the +`reStructuredText `_ +documentation for details. + + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + diff --git a/pyproject.toml b/pyproject.toml index 9815bd0..c723f00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,8 @@ matplotlib = ">=3.3.3" [tool.poetry.dev-dependencies] pytest = "^6.2.2" +sphinx = "^5.0" +sphinx-rtd-theme = "^1.0.0" # If you're using the Read the Docs theme [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/readthedocs.yml b/readthedocs.yml new file mode 100644 index 0000000..dc4c065 --- /dev/null +++ b/readthedocs.yml @@ -0,0 +1,14 @@ +# readthedocs.yml +version: 2 +build: + os: ubuntu-22.04 + tools: + python: "3.9" + commands: + - pip install sphinx + - sphinx-build -b html docs/ _build/html + +python: + install: + - method: pip + path: . From dfeab7fe8a3af49c78c6be3c01a96358c3099b0b Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 11 Oct 2024 17:09:16 -0500 Subject: [PATCH 035/139] read the docs init --- readthedocs.yml => readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename readthedocs.yml => readthedocs.yaml (91%) diff --git a/readthedocs.yml b/readthedocs.yaml similarity index 91% rename from readthedocs.yml rename to readthedocs.yaml index dc4c065..dfaaae5 100644 --- a/readthedocs.yml +++ b/readthedocs.yaml @@ -1,4 +1,4 @@ -# readthedocs.yml +# readthedocs.yaml version: 2 build: os: ubuntu-22.04 From 57fd7a52520290a4f2b0b1b12668c8dd44fde70b Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 11 Oct 2024 17:16:16 -0500 Subject: [PATCH 036/139] update readthedocs.yaml --- readthedocs.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readthedocs.yaml b/readthedocs.yaml index dfaaae5..b4eb474 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -6,7 +6,10 @@ build: python: "3.9" commands: - pip install sphinx - - sphinx-build -b html docs/ _build/html + - sphinx-build -b html docs/source/ _build/html # Update source directory + +sphinx: + configuration: docs/source/conf.py # Specify the path to conf.py python: install: From a58f07a41ea9c20094718634c9ae479e6e2c63f0 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 11 Oct 2024 17:20:22 -0500 Subject: [PATCH 037/139] update readthedocs.yaml --- readthedocs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readthedocs.yaml b/readthedocs.yaml index b4eb474..e18551b 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -6,10 +6,10 @@ build: python: "3.9" commands: - pip install sphinx - - sphinx-build -b html docs/source/ _build/html # Update source directory + - sphinx-build -b html docs/source/ docs/build/html sphinx: - configuration: docs/source/conf.py # Specify the path to conf.py + configuration: docs/source/conf.py python: install: From 7db7627736744d8935e4cf3bc3439855a4149555 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 11 Oct 2024 17:21:17 -0500 Subject: [PATCH 038/139] update readthedocs.yaml --- readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs.yaml b/readthedocs.yaml index e18551b..fc54ddf 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -6,7 +6,7 @@ build: python: "3.9" commands: - pip install sphinx - - sphinx-build -b html docs/source/ docs/build/html + - sphinx-build -b html docs/source/ docs/build/ sphinx: configuration: docs/source/conf.py From 10bea8bdd38d37ff111cb822e55f257d87623f9c Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 11 Oct 2024 17:24:19 -0500 Subject: [PATCH 039/139] update readthedocs.yaml --- docs/source/conf.py | 4 ++-- readthedocs.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index d85b753..4f58160 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 = [] +extensions = ['sphinx.ext.autodoc'] templates_path = ['_templates'] exclude_patterns = [] @@ -27,4 +27,4 @@ html_theme = 'alabaster' html_static_path = ['_static'] -extensions = ['sphinx.ext.autodoc'] + diff --git a/readthedocs.yaml b/readthedocs.yaml index fc54ddf..20f2017 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -6,10 +6,10 @@ build: python: "3.9" commands: - pip install sphinx - - sphinx-build -b html docs/source/ docs/build/ + - sphinx-build -b html docs/source/ $READTHEDOCS_OUTPUT/html # Correct output path sphinx: - configuration: docs/source/conf.py + configuration: docs/source/conf.py # Correctly pointing to conf.py python: install: From 3c1c80a6a22abadea313694aeb58c5e5b5b1d468 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Mon, 14 Oct 2024 21:42:59 -0500 Subject: [PATCH 040/139] 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 041/139] 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 042/139] 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 043/139] 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 044/139] 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 045/139] 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 046/139] 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 047/139] 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 From 77d2c1f291c8bfc87456790781e21a8d37661564 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 01:22:38 -0500 Subject: [PATCH 048/139] update readthedocs.yaml --- readthedocs.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readthedocs.yaml b/readthedocs.yaml index 20f2017..879f4e8 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -4,6 +4,9 @@ build: os: ubuntu-22.04 tools: python: "3.9" + jobs: + install: + - poetry install --with dev commands: - pip install sphinx - sphinx-build -b html docs/source/ $READTHEDOCS_OUTPUT/html # Correct output path @@ -13,5 +16,5 @@ sphinx: python: install: - - method: pip + - method: poetry path: . From 6e5aec5f37c2ce97b9f0b8954c2fc23b60a7df01 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 01:31:08 -0500 Subject: [PATCH 049/139] update readthedocs.yaml --- readthedocs.yaml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/readthedocs.yaml b/readthedocs.yaml index 879f4e8..0d70b3f 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -5,10 +5,17 @@ build: tools: python: "3.9" jobs: - install: - - poetry install --with dev + post_create_environment: + # Install poetry + # https://python-poetry.org/docs/#installing-manually + - pip install poetry + post_install: + # Install dependencies with 'docs' dependency group + # https://python-poetry.org/docs/managing-dependencies/#dependency-groups + # VIRTUAL_ENV needs to be set manually for now. + # See https://github.com/readthedocs/readthedocs.org/pull/11152/ + - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with dev commands: - - pip install sphinx - sphinx-build -b html docs/source/ $READTHEDOCS_OUTPUT/html # Correct output path sphinx: @@ -16,5 +23,5 @@ sphinx: python: install: - - method: poetry + - method: pip path: . From 0727ee330c44edb35ea22b734e3155d93c81e343 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 01:32:24 -0500 Subject: [PATCH 050/139] update readthedocs.yaml --- readthedocs.yaml | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/readthedocs.yaml b/readthedocs.yaml index 0d70b3f..5272e75 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -4,24 +4,28 @@ build: os: ubuntu-22.04 tools: python: "3.9" - jobs: - post_create_environment: - # Install poetry - # https://python-poetry.org/docs/#installing-manually - - pip install poetry - post_install: - # Install dependencies with 'docs' dependency group - # https://python-poetry.org/docs/managing-dependencies/#dependency-groups - # VIRTUAL_ENV needs to be set manually for now. - # See https://github.com/readthedocs/readthedocs.org/pull/11152/ - - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with dev - commands: - - sphinx-build -b html docs/source/ $READTHEDOCS_OUTPUT/html # Correct output path + # Installing Poetry after the environment is created + post_create_environment: + - pip install poetry + + # Installing your dependencies using Poetry with the dev flag + post_install: + # VIRTUAL_ENV needs to be set manually as per the current workaround + # https://github.com/readthedocs/readthedocs.org/pull/11152/ + - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with dev + +# Build documentation using Sphinx sphinx: - configuration: docs/source/conf.py # Correctly pointing to conf.py + configuration: docs/source/conf.py python: + # Installing project dependencies using Poetry, not pip install: - - method: pip + - method: poetry path: . + +# Commands that should run after dependencies are installed, in post_build +commands: + post_build: + - sphinx-build -b html docs/source/ $READTHEDOCS_OUTPUT/html From 30bfb1ddd8475c247363b27e0c0e6e8514c50452 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 01:33:57 -0500 Subject: [PATCH 051/139] update readthedocs.yaml --- readthedocs.yaml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/readthedocs.yaml b/readthedocs.yaml index 5272e75..a83c53a 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -15,17 +15,10 @@ build: # https://github.com/readthedocs/readthedocs.org/pull/11152/ - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with dev -# Build documentation using Sphinx +# Sphinx configuration sphinx: - configuration: docs/source/conf.py + configuration: docs/source/conf.py # Correctly pointing to conf.py python: - # Installing project dependencies using Poetry, not pip install: - - method: poetry - path: . - -# Commands that should run after dependencies are installed, in post_build -commands: - post_build: - - sphinx-build -b html docs/source/ $READTHEDOCS_OUTPUT/html + - method: poetry # Use Poetry to install dependencies From c8cbde15d771de0848d901db3660ff9d1e728e63 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 01:35:37 -0500 Subject: [PATCH 052/139] update readthedocs.yaml --- readthedocs.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/readthedocs.yaml b/readthedocs.yaml index a83c53a..a433718 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -19,6 +19,3 @@ build: sphinx: configuration: docs/source/conf.py # Correctly pointing to conf.py -python: - install: - - method: poetry # Use Poetry to install dependencies From db15e883377f6ab84ec1a13eea83b74560d4bd05 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 01:36:45 -0500 Subject: [PATCH 053/139] update readthedocs.yaml --- readthedocs.yaml | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/readthedocs.yaml b/readthedocs.yaml index a433718..27bde4e 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -1,21 +1,20 @@ -# readthedocs.yaml version: 2 + build: - os: ubuntu-22.04 + os: "ubuntu-22.04" tools: - python: "3.9" - - # Installing Poetry after the environment is created - post_create_environment: - - pip install poetry + python: "3.10" + jobs: + post_create_environment: + # Install poetry + # https://python-poetry.org/docs/#installing-manually + - pip install poetry + post_install: + # Install dependencies with 'docs' dependency group + # https://python-poetry.org/docs/managing-dependencies/#dependency-groups + # VIRTUAL_ENV needs to be set manually for now. + # See https://github.com/readthedocs/readthedocs.org/pull/11152/ + - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with dev - # Installing your dependencies using Poetry with the dev flag - post_install: - # VIRTUAL_ENV needs to be set manually as per the current workaround - # https://github.com/readthedocs/readthedocs.org/pull/11152/ - - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with dev - -# Sphinx configuration sphinx: - configuration: docs/source/conf.py # Correctly pointing to conf.py - + configuration: docs/source/conf.py \ No newline at end of file From 7c2e1be4bf6802acf1e56e86d1d713387c6e4a03 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 01:36:58 -0500 Subject: [PATCH 054/139] update readthedocs.yaml --- readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs.yaml b/readthedocs.yaml index 27bde4e..2531ae2 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -3,7 +3,7 @@ version: 2 build: os: "ubuntu-22.04" tools: - python: "3.10" + python: "3.9" jobs: post_create_environment: # Install poetry From e25e88fc8db8ecfecbe79b4e51a0278040a1c130 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 01:50:29 -0500 Subject: [PATCH 055/139] update readthedocs.yaml --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ffe0d0d..a643bef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,9 +33,9 @@ matplotlib = ">=3.3.3" [tool.poetry.dev-dependencies] pytest = "^6.2.2" -sphinx = "^5.0" +sphinx = "^6.2.1" sphinx-rtd-theme = "^1.0.0" # If you're using the Read the Docs theme -sphinx-autoapi = ">=1.8.1" +sphinx-autoapi = ">=3.0.0" myst-parser = "^1.0" [build-system] From 60518a248430a42d67ae9f56da667e1f44e6435e Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 01:53:54 -0500 Subject: [PATCH 056/139] update readthedocs.yaml --- docs/source/conf.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 6505a53..a6994c2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -35,22 +35,6 @@ 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 = { 'python': ('https://docs.python.org/3', None), From 1fef737c5d60f2d1c9fc7a720453e9870953ff2b Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 01:58:35 -0500 Subject: [PATCH 057/139] update readthedocs.yaml --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f8f5dd6..c4db7f7 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ predictions = model.predict(test_X) ## Documentation -For more detailed documentation, including the full list of parameters for each model, visit our [documentation page](https://github.com/NiklasMelton/AdaptiveResonanceLib). +For more detailed documentation, including the full list of parameters for each model, visit our [documentation page](https://adaptiveresonancelib.readthedocs.io/en/latest/index.html). From 88df0a3bd8237e4e369960667c8ac1fd6ddfd2e9 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 02:04:53 -0500 Subject: [PATCH 058/139] update readthedocs.yaml --- README.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index c4db7f7..42e9d1a 100644 --- a/README.md +++ b/README.md @@ -9,35 +9,35 @@ Welcome to AdaptiveResonanceLib, a comprehensive and modular Python library for AdaptiveResonanceLib includes implementations for the following ART models: - #### Elementary Clustering - - ART1 - - ART2 - - Bayesian ART - - Gaussian ART - - Hypersphere ART - - Ellipsoidal ART - - Fuzzy ART - - Quadratic Neuron ART - - Dual Vigilance ART + - [ART1](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.ART1) + - [ART2](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.ART2A) + - [Bayesian ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.BayesianART) + - [Gaussian ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.GaussianART) + - [Hypersphere ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.HypersphereART) + - [Ellipsoid ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.EllipsoidART) + - [Fuzzy ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.FuzzyART) + - [Quadratic Neuron ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.QuadraticNeuronART) - #### Metric Informed - - CVI ART - - iCVI Fuzzy ART + - [CVI ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.CVIART) + - [iCVI Fuzzy ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.iCVIFuzzyART) - #### Topological - - Topo ART + - [Topo ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.TopoART) + - [Dual Vigilance ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.DualVigilanceART) - #### Classification - - Simple ARTMAP + - [Simple ARTMAP](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.SimpleARTMAP) - #### Regression - - ARTMAP - - Fusion ART + - [ARTMAP](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.ARTMAP) + - [Fusion ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.FusionART) - #### Hierarchical - - DeepARTMAP - - SMART + - [DeepARTMAP](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.DeepARTMAP) + - [SMART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.SMART) - #### Data Fusion - - Fusion ART + - [Fusion ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.FusionART) - #### Reinforcement Learning - - FALCON - - TD-FALCON + - [FALCON](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.FALCON) + - [TD-FALCON](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.TD_FALCON) - #### Biclustering - - Biclustering ARTMAP + - [Biclustering ARTMAP](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.biclustering.html#artlib.biclustering.BARTMAP.BARTMAP) From 9e0a832d6994c2028c9458fc8d9b1b7ac645590d Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 02:05:48 -0500 Subject: [PATCH 059/139] update readthedocs.yaml --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 42e9d1a..2799381 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ predictions = model.predict(test_X) ## Documentation -For more detailed documentation, including the full list of parameters for each model, visit our [documentation page](https://adaptiveresonancelib.readthedocs.io/en/latest/index.html). +For more detailed documentation, including the full list of parameters for each model, visit our [Read the Docs page](https://adaptiveresonancelib.readthedocs.io/en/latest/index.html). From 98f38f6ffd26f336feb4653d84666fad3095eeec Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 02:27:55 -0500 Subject: [PATCH 060/139] move module contents to top of page --- docs/source/artlib.biclustering.rst | 14 ++++++++------ docs/source/artlib.common.rst | 15 ++++++++------- docs/source/artlib.cvi.iCVIs.rst | 14 ++++++++------ docs/source/artlib.cvi.rst | 14 ++++++++------ docs/source/artlib.elementary.rst | 14 ++++++++------ docs/source/artlib.experimental.rst | 14 ++++++++------ docs/source/artlib.fusion.rst | 14 ++++++++------ docs/source/artlib.hierarchical.rst | 14 ++++++++------ docs/source/artlib.reinforcement.rst | 14 ++++++++------ docs/source/artlib.rst | 14 ++++++++------ docs/source/artlib.supervised.rst | 14 ++++++++------ docs/source/artlib.topological.rst | 14 ++++++++------ 12 files changed, 96 insertions(+), 73 deletions(-) diff --git a/docs/source/artlib.biclustering.rst b/docs/source/artlib.biclustering.rst index a7b0ddb..38d7512 100644 --- a/docs/source/artlib.biclustering.rst +++ b/docs/source/artlib.biclustering.rst @@ -1,6 +1,14 @@ artlib.biclustering package =========================== +Module contents +--------------- + +.. automodule:: artlib.biclustering + :members: + :undoc-members: + :show-inheritance: + Submodules ---------- @@ -12,10 +20,4 @@ artlib.biclustering.BARTMAP module :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 index 041e0b2..77a39ca 100644 --- a/docs/source/artlib.common.rst +++ b/docs/source/artlib.common.rst @@ -1,6 +1,14 @@ artlib.common package ===================== +Module contents +--------------- + +.. automodule:: artlib.common + :members: + :undoc-members: + :show-inheritance: + Submodules ---------- @@ -44,10 +52,3 @@ artlib.common.visualization module :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 index 78c93dc..023924e 100644 --- a/docs/source/artlib.cvi.iCVIs.rst +++ b/docs/source/artlib.cvi.iCVIs.rst @@ -1,6 +1,14 @@ artlib.cvi.iCVIs package ======================== +Module contents +--------------- + +.. automodule:: artlib.cvi.iCVIs + :members: + :undoc-members: + :show-inheritance: + Submodules ---------- @@ -12,10 +20,4 @@ artlib.cvi.iCVIs.CalinkskiHarabasz module :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 index 2d5a886..8b22857 100644 --- a/docs/source/artlib.cvi.rst +++ b/docs/source/artlib.cvi.rst @@ -1,6 +1,14 @@ artlib.cvi package ================== +Module contents +--------------- + +.. automodule:: artlib.cvi + :members: + :undoc-members: + :show-inheritance: + Subpackages ----------- @@ -28,10 +36,4 @@ artlib.cvi.iCVIFuzzyArt module :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 index c281ed4..87ec705 100644 --- a/docs/source/artlib.elementary.rst +++ b/docs/source/artlib.elementary.rst @@ -1,6 +1,14 @@ artlib.elementary package ========================= +Module contents +--------------- + +.. automodule:: artlib.elementary + :members: + :undoc-members: + :show-inheritance: + Submodules ---------- @@ -68,10 +76,4 @@ artlib.elementary.QuadraticNeuronART module :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 index b4c1a5d..3e991ae 100644 --- a/docs/source/artlib.experimental.rst +++ b/docs/source/artlib.experimental.rst @@ -1,6 +1,14 @@ artlib.experimental package =========================== +Module contents +--------------- + +.. automodule:: artlib.experimental + :members: + :undoc-members: + :show-inheritance: + Submodules ---------- @@ -28,10 +36,4 @@ artlib.experimental.merging module :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 index b7a8f93..354cb6f 100644 --- a/docs/source/artlib.fusion.rst +++ b/docs/source/artlib.fusion.rst @@ -1,6 +1,14 @@ artlib.fusion package ===================== +Module contents +--------------- + +.. automodule:: artlib.fusion + :members: + :undoc-members: + :show-inheritance: + Submodules ---------- @@ -12,10 +20,4 @@ artlib.fusion.FusionART module :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 index 5e6bfb3..1eb9acd 100644 --- a/docs/source/artlib.hierarchical.rst +++ b/docs/source/artlib.hierarchical.rst @@ -1,6 +1,14 @@ artlib.hierarchical package =========================== +Module contents +--------------- + +.. automodule:: artlib.hierarchical + :members: + :undoc-members: + :show-inheritance: + Submodules ---------- @@ -20,10 +28,4 @@ artlib.hierarchical.SMART module :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 index c051ffc..c9aaa86 100644 --- a/docs/source/artlib.reinforcement.rst +++ b/docs/source/artlib.reinforcement.rst @@ -1,6 +1,14 @@ artlib.reinforcement package ============================ +Module contents +--------------- + +.. automodule:: artlib.reinforcement + :members: + :undoc-members: + :show-inheritance: + Submodules ---------- @@ -12,10 +20,4 @@ artlib.reinforcement.FALCON module :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 index 0bb6975..8a27f50 100644 --- a/docs/source/artlib.rst +++ b/docs/source/artlib.rst @@ -1,6 +1,14 @@ artlib package ============== +Module contents +--------------- + +.. automodule:: artlib + :members: + :undoc-members: + :show-inheritance: + Subpackages ----------- @@ -18,10 +26,4 @@ Subpackages 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 index fb16080..882670e 100644 --- a/docs/source/artlib.supervised.rst +++ b/docs/source/artlib.supervised.rst @@ -1,6 +1,14 @@ artlib.supervised package ========================= +Module contents +--------------- + +.. automodule:: artlib.supervised + :members: + :undoc-members: + :show-inheritance: + Submodules ---------- @@ -20,10 +28,4 @@ artlib.supervised.SimpleARTMAP module :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 index 0e2424a..f10b26a 100644 --- a/docs/source/artlib.topological.rst +++ b/docs/source/artlib.topological.rst @@ -1,6 +1,14 @@ artlib.topological package ========================== +Module contents +--------------- + +.. automodule:: artlib.topological + :members: + :undoc-members: + :show-inheritance: + Submodules ---------- @@ -20,10 +28,4 @@ artlib.topological.TopoART module :undoc-members: :show-inheritance: -Module contents ---------------- -.. automodule:: artlib.topological - :members: - :undoc-members: - :show-inheritance: From 0a0b8e5e1cddb79f06ecac3446bb9900ddb0621d Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 02:39:38 -0500 Subject: [PATCH 061/139] remove typos from init descriptions --- artlib/supervised/__init__.py | 3 +-- artlib/topological/__init__.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/artlib/supervised/__init__.py b/artlib/supervised/__init__.py index dafe66d..0b7f347 100644 --- a/artlib/supervised/__init__.py +++ b/artlib/supervised/__init__.py @@ -5,8 +5,7 @@ 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. +Regression, on the other hand, deals with predicting continuous values, like stock prices or temperature. `Supervised learning `_ diff --git a/artlib/topological/__init__.py b/artlib/topological/__init__.py index bafc184..c156898 100644 --- a/artlib/topological/__init__.py +++ b/artlib/topological/__init__.py @@ -5,7 +5,7 @@ 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 +The two modules herein 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. From 7c2b744cc4c6acee5f55e346ae82a8d3097f09a0 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Wed, 16 Oct 2024 13:21:45 -0500 Subject: [PATCH 062/139] add readme links --- README.md | 4 ++-- docs/source/license.rst | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2799381..3903698 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ For more detailed documentation, including the full list of parameters for each ## Examples -For examples of how to use each model in AdaptiveResonanceLib, check out the `/examples` directory in our repository. +For examples of how to use each model in AdaptiveResonanceLib, check out the [`/examples`](https://github.com/NiklasMelton/AdaptiveResonanceLib/tree/develop/examples) directory in our repository. @@ -114,7 +114,7 @@ You can also join our [Discord server](https://discord.gg/E465HBwEuN) and partic ## License -AdaptiveResonanceLib is open source and available under the MIT license. See the `LICENSE` file for more info. +AdaptiveResonanceLib is open source and available under the MIT license. See the [`LICENSE`](https://github.com/NiklasMelton/AdaptiveResonanceLib/blob/develop/LICENSE) file for more info. diff --git a/docs/source/license.rst b/docs/source/license.rst index 9ef5fea..d8aa21e 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -1,5 +1,6 @@ -.. include:: ../../README.md +License +================================== + +.. include:: ../../LICENSE :parser: myst_parser.sphinx_ - :start-after: - :end-before: From 184532e1e3212d6ce73687c112cdde18ba396130 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Wed, 16 Oct 2024 14:11:20 -0500 Subject: [PATCH 063/139] add citations page --- CITATION.cff | 2 +- README.md | 17 ++++++++++++----- docs/source/citation.rst | 6 ++++++ docs/source/conf.py | 22 ++++++++++++++++++++++ docs/source/index.rst | 1 + pyproject.toml | 2 ++ 6 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 docs/source/citation.rst diff --git a/CITATION.cff b/CITATION.cff index e506f8b..8daf6a8 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -6,7 +6,7 @@ doi: "10.5281/zenodo.9999999" authors: - family-names: "Melton" given-names: "Niklas" - orcid: "0000-0001-9625-7086" + orcid: "https://orcid.org/0000-0001-9625-7086" date-released: 2024-10-03 url: "https://github.com/NiklasMelton/AdaptiveResonanceLib" repository-code: "https://github.com/NiklasMelton/AdaptiveResonanceLib" diff --git a/README.md b/README.md index 3903698..2b1b0d2 100644 --- a/README.md +++ b/README.md @@ -53,14 +53,14 @@ AdaptiveResonanceLib includes implementations for the following ART models: To install AdaptiveResonanceLib, simply use pip: -[comment]: <> (```bash) - -[comment]: <> (pip install AdaptiveResonanceLib) +```bash +pip install artlib +``` -[comment]: <> (```) +Or to install directly from the most recent source: ```bash -pip install artlib +pip install git+https://github.com/NiklasMelton/AdaptiveResonanceLib.git@develop ``` Ensure you have Python 3.9 or newer installed. @@ -124,3 +124,10 @@ For questions and support, please open an issue in the GitHub issue tracker or m Happy Modeling with AdaptiveResonanceLib! + + +## Citing this Repository +If you use this project in your research, please cite it as: + +Melton, N. (2024). AdaptiveResonanceLib (Version 0.1.2) + \ No newline at end of file diff --git a/docs/source/citation.rst b/docs/source/citation.rst new file mode 100644 index 0000000..ef9681c --- /dev/null +++ b/docs/source/citation.rst @@ -0,0 +1,6 @@ +Citation +================================== +If you use this project in your research, please cite it as: + +.. bibliography:: references.bib + :style: unsrt \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index a6994c2..fe107ba 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,6 +6,26 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +import os +import subprocess + +def run_cffconvert(app): + try: + result = subprocess.run([ + 'cffconvert', + '--infile', '../../CITATION.cff', + '--outfile', 'references.bib', + '--format', 'bibtex' + ], check=True, cwd=app.srcdir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + print(result.stdout) + except subprocess.CalledProcessError as e: + print(f"An error occurred while running cffconvert: {e}") + print(f"stdout: {e.stdout}") + print(f"stderr: {e.stderr}") + +def setup(app): + app.connect('builder-inited', run_cffconvert) + project = 'AdaptiveResonanceLib' copyright = '2024, Niklas Melton' author = 'Niklas Melton' @@ -20,6 +40,7 @@ 'sphinx.ext.napoleon', 'myst_parser', 'sphinx.ext.intersphinx', + 'sphinxcontrib.bibtex', ] source_suffix = { @@ -35,6 +56,7 @@ autoapi_ignore = ['*/experimental', '*/experimental/*'] autoapi_python_class_content = 'both' +bibtex_bibfiles = ['references.bib'] intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), diff --git a/docs/source/index.rst b/docs/source/index.rst index de3a984..0fabef5 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -22,6 +22,7 @@ AdaptiveResonanceLib Home contributing contact license + citation .. toctree:: :maxdepth: 2 diff --git a/pyproject.toml b/pyproject.toml index a643bef..cf9906e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,8 @@ sphinx = "^6.2.1" sphinx-rtd-theme = "^1.0.0" # If you're using the Read the Docs theme sphinx-autoapi = ">=3.0.0" myst-parser = "^1.0" +cffconvert = "^2.0.0" +sphinxcontrib-bibtex = "^2.6.3" [build-system] requires = ["poetry-core>=1.0.0"] From 8561f7eb9644cfac58381d9bbb05e1eb7c364685 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Wed, 16 Oct 2024 14:15:50 -0500 Subject: [PATCH 064/139] add citations page --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cf9906e..81dc029 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ sphinx-rtd-theme = "^1.0.0" # If you're using the Read the Docs theme sphinx-autoapi = ">=3.0.0" myst-parser = "^1.0" cffconvert = "^2.0.0" -sphinxcontrib-bibtex = "^2.6.3" +sphinxcontrib-bibtex = "^2.0.0" [build-system] requires = ["poetry-core>=1.0.0"] From 8aa7cc3f01a464573360c537fc542af5bc769621 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Wed, 16 Oct 2024 14:24:24 -0500 Subject: [PATCH 065/139] add citations page --- docs/source/citation.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/citation.rst b/docs/source/citation.rst index ef9681c..0a0b4e9 100644 --- a/docs/source/citation.rst +++ b/docs/source/citation.rst @@ -3,4 +3,5 @@ Citation If you use this project in your research, please cite it as: .. bibliography:: references.bib - :style: unsrt \ No newline at end of file + :style: unsrt + :all: From b0906c8e63ef016654fd504672e5b9fbf2dc260e Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 14:57:55 -0500 Subject: [PATCH 066/139] citation --- docs/source/citation.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/citation.rst b/docs/source/citation.rst index 0a0b4e9..7c5b2bb 100644 --- a/docs/source/citation.rst +++ b/docs/source/citation.rst @@ -1,6 +1,7 @@ Citation ================================== If you use this project in your research, please cite it as: +:cite:`YourReferenceHere` .. bibliography:: references.bib :style: unsrt From 8c984d8824d311dd6c1fdd06f3229914548f94f4 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 15:08:21 -0500 Subject: [PATCH 067/139] update cffconvert --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 81dc029..2ec4a6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ sphinx = "^6.2.1" sphinx-rtd-theme = "^1.0.0" # If you're using the Read the Docs theme sphinx-autoapi = ">=3.0.0" myst-parser = "^1.0" -cffconvert = "^2.0.0" +cffconvert = "^2.1.0" sphinxcontrib-bibtex = "^2.0.0" [build-system] From 40832d498018ceab610edd0150123ddb0dda1a3b Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 15:12:34 -0500 Subject: [PATCH 068/139] update cffconvert --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2ec4a6f..81dc029 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ sphinx = "^6.2.1" sphinx-rtd-theme = "^1.0.0" # If you're using the Read the Docs theme sphinx-autoapi = ">=3.0.0" myst-parser = "^1.0" -cffconvert = "^2.1.0" +cffconvert = "^2.0.0" sphinxcontrib-bibtex = "^2.0.0" [build-system] From 56ebd250a54ad29e5d7ea8da677021ef2e1cba56 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 15:13:41 -0500 Subject: [PATCH 069/139] update cffconvert --- docs/source/conf.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index fe107ba..c4841d8 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -8,20 +8,28 @@ import os import subprocess +from sphinx.util import logging def run_cffconvert(app): + logger = logging.getLogger(__name__) try: result = subprocess.run([ 'cffconvert', - '--infile', '../../CITATION.cff', + '--infile', '../../CITATION.cff', # Adjust the path if necessary '--outfile', 'references.bib', '--format', 'bibtex' ], check=True, cwd=app.srcdir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - print(result.stdout) + logger.info("cffconvert stdout:\n%s", result.stdout) + if result.stderr: + logger.warning("cffconvert stderr:\n%s", result.stderr) except subprocess.CalledProcessError as e: - print(f"An error occurred while running cffconvert: {e}") - print(f"stdout: {e.stdout}") - print(f"stderr: {e.stderr}") + logger.error("An error occurred while running cffconvert: %s", e) + if e.stdout: + logger.error("cffconvert stdout:\n%s", e.stdout) + if e.stderr: + logger.error("cffconvert stderr:\n%s", e.stderr) + raise e # Ensure that the build fails on error + def setup(app): app.connect('builder-inited', run_cffconvert) From e4f633266b9297c544c43c2f412365cbf4cebd50 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 15:21:17 -0500 Subject: [PATCH 070/139] run cffconvert earlier --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index c4841d8..41ce85a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -32,7 +32,7 @@ def run_cffconvert(app): def setup(app): - app.connect('builder-inited', run_cffconvert) + app.connect('env-before-read-docs', run_cffconvert) project = 'AdaptiveResonanceLib' copyright = '2024, Niklas Melton' From 91bca56f62cf21cdbb184abb40bf33af146214d5 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 15:24:22 -0500 Subject: [PATCH 071/139] update cffconvert --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 41ce85a..2ac2674 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,7 +10,7 @@ import subprocess from sphinx.util import logging -def run_cffconvert(app): +def run_cffconvert(app, env, docnames): logger = logging.getLogger(__name__) try: result = subprocess.run([ From d678286cd42d2c8e2e48d4c11147f9c19d3b765f Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 15:50:04 -0500 Subject: [PATCH 072/139] move cffconvert to yaml --- docs/source/conf.py | 28 ---------------------------- readthedocs.yaml | 3 +++ 2 files changed, 3 insertions(+), 28 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 2ac2674..1335c8b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,34 +6,6 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -import os -import subprocess -from sphinx.util import logging - -def run_cffconvert(app, env, docnames): - logger = logging.getLogger(__name__) - try: - result = subprocess.run([ - 'cffconvert', - '--infile', '../../CITATION.cff', # Adjust the path if necessary - '--outfile', 'references.bib', - '--format', 'bibtex' - ], check=True, cwd=app.srcdir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - logger.info("cffconvert stdout:\n%s", result.stdout) - if result.stderr: - logger.warning("cffconvert stderr:\n%s", result.stderr) - except subprocess.CalledProcessError as e: - logger.error("An error occurred while running cffconvert: %s", e) - if e.stdout: - logger.error("cffconvert stdout:\n%s", e.stdout) - if e.stderr: - logger.error("cffconvert stderr:\n%s", e.stderr) - raise e # Ensure that the build fails on error - - -def setup(app): - app.connect('env-before-read-docs', run_cffconvert) - project = 'AdaptiveResonanceLib' copyright = '2024, Niklas Melton' author = 'Niklas Melton' diff --git a/readthedocs.yaml b/readthedocs.yaml index 2531ae2..f382ef8 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -15,6 +15,9 @@ build: # VIRTUAL_ENV needs to be set manually for now. # See https://github.com/readthedocs/readthedocs.org/pull/11152/ - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with dev + # Generate the references.bib file from CITATION.cff using cffconvert + - cffconvert --infile CITATION.cff --outfile docs/source/references.bib --format bibtex + sphinx: configuration: docs/source/conf.py \ No newline at end of file From 08b67c39ed9f870b7efe48d7166b118309f62ab4 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 15:56:10 -0500 Subject: [PATCH 073/139] add raw bibtex --- docs/source/citation.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/source/citation.rst b/docs/source/citation.rst index 7c5b2bb..5c8a577 100644 --- a/docs/source/citation.rst +++ b/docs/source/citation.rst @@ -1,8 +1,12 @@ Citation ================================== -If you use this project in your research, please cite it as: -:cite:`YourReferenceHere` +If you use this project in your research, please cite it as :cite:`YourReferenceHere` .. bibliography:: references.bib :style: unsrt :all: + +Alternatively, you can cite the project using the following BibTeX entry: + +.. literalinclude:: references.bib + :language: bibtex \ No newline at end of file From 2fe698968e3d3bbd59da44647a57f8432a517bb9 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 16:06:15 -0500 Subject: [PATCH 074/139] change ref alias --- readthedocs.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readthedocs.yaml b/readthedocs.yaml index f382ef8..afe4ccc 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -17,6 +17,10 @@ build: - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with dev # Generate the references.bib file from CITATION.cff using cffconvert - cffconvert --infile CITATION.cff --outfile docs/source/references.bib --format bibtex + # Adjust the citation key and entry type in references.bib + - sed -i 's/^@misc{YourReferenceHere,@software{Melton_AdaptiveResonanceLib_2024,/' docs/source/references.bib + + sphinx: From 119847b249de7e317e4a38b16099f11768d815e5 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 16:09:06 -0500 Subject: [PATCH 075/139] change ref alias --- readthedocs.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readthedocs.yaml b/readthedocs.yaml index afe4ccc..7689685 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -18,7 +18,8 @@ build: # Generate the references.bib file from CITATION.cff using cffconvert - cffconvert --infile CITATION.cff --outfile docs/source/references.bib --format bibtex # Adjust the citation key and entry type in references.bib - - sed -i 's/^@misc{YourReferenceHere,@software{Melton_AdaptiveResonanceLib_2024,/' docs/source/references.bib + - sed -i 's|^@misc{YourReferenceHere,|@software{Melton_AdaptiveResonanceLib_2024,|' docs/source/references.bib + From a66096e3c53542498b0292a5c1668a2f7e5fa5c1 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 16:12:22 -0500 Subject: [PATCH 076/139] change ref alias --- readthedocs.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readthedocs.yaml b/readthedocs.yaml index 7689685..1a86fb2 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -18,7 +18,8 @@ build: # Generate the references.bib file from CITATION.cff using cffconvert - cffconvert --infile CITATION.cff --outfile docs/source/references.bib --format bibtex # Adjust the citation key and entry type in references.bib - - sed -i 's|^@misc{YourReferenceHere,|@software{Melton_AdaptiveResonanceLib_2024,|' docs/source/references.bib + - "sed -i 's|^@misc{YourReferenceHere,|@software{Melton_AdaptiveResonanceLib_2024,|' docs/source/references.bib" + From b0184edc2507043c64fe3d9275938ed50211efae Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 16:18:51 -0500 Subject: [PATCH 077/139] use python script to generate bib --- readthedocs.yaml | 6 ++--- scripts/generate_references.py | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 scripts/generate_references.py diff --git a/readthedocs.yaml b/readthedocs.yaml index 1a86fb2..fedc077 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -15,10 +15,8 @@ build: # VIRTUAL_ENV needs to be set manually for now. # See https://github.com/readthedocs/readthedocs.org/pull/11152/ - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with dev - # Generate the references.bib file from CITATION.cff using cffconvert - - cffconvert --infile CITATION.cff --outfile docs/source/references.bib --format bibtex - # Adjust the citation key and entry type in references.bib - - "sed -i 's|^@misc{YourReferenceHere,|@software{Melton_AdaptiveResonanceLib_2024,|' docs/source/references.bib" + # Generate references.bib using the Python script + - python generate_references.py diff --git a/scripts/generate_references.py b/scripts/generate_references.py new file mode 100644 index 0000000..83d9e22 --- /dev/null +++ b/scripts/generate_references.py @@ -0,0 +1,43 @@ +import subprocess +import os + +def generate_references(): + try: + # Run cffconvert and capture the output + result = subprocess.run( + [ + 'cffconvert', + '--validate', + '--ignore-suspect-keys', + '--format', 'bibtex', + '--infile', 'CITATION.cff' + ], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + bibtex_content = result.stdout + + # Modify the BibTeX entry + # Replace the first line with the desired entry type and citation key + bibtex_lines = bibtex_content.splitlines() + if bibtex_lines: + # Replace the first line + bibtex_lines[0] = '@software{Melton_AdaptiveResonanceLib_2024,' + modified_bibtex = '\n'.join(bibtex_lines) + + # Write the modified content to references.bib + output_path = os.path.join('docs', 'source', 'references.bib') + with open(output_path, 'w', encoding='utf-8') as f: + f.write(modified_bibtex) + else: + print('Error: Empty BibTeX content.') + except subprocess.CalledProcessError as e: + print(f"An error occurred while running cffconvert: {e}") + print(f"stdout: {e.stdout}") + print(f"stderr: {e.stderr}") + raise e + +if __name__ == '__main__': + generate_references() From 085d48182be09726c86ff010996b48a742c3c584 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 16:20:39 -0500 Subject: [PATCH 078/139] change ref alias --- readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs.yaml b/readthedocs.yaml index fedc077..57442c0 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -16,7 +16,7 @@ build: # See https://github.com/readthedocs/readthedocs.org/pull/11152/ - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with dev # Generate references.bib using the Python script - - python generate_references.py + - python scripts/generate_references.py From c324c093e7a58dac863a2f0b21cc9146f1f5e995 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 16:22:34 -0500 Subject: [PATCH 079/139] use python script to generate bib --- scripts/generate_references.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/generate_references.py b/scripts/generate_references.py index 83d9e22..ee2763d 100644 --- a/scripts/generate_references.py +++ b/scripts/generate_references.py @@ -7,8 +7,6 @@ def generate_references(): result = subprocess.run( [ 'cffconvert', - '--validate', - '--ignore-suspect-keys', '--format', 'bibtex', '--infile', 'CITATION.cff' ], From 8e7079eae89feb92fd8ada01e9845c95ee8dd715 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 16:29:43 -0500 Subject: [PATCH 080/139] use python script to generate bib --- scripts/generate_references.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_references.py b/scripts/generate_references.py index ee2763d..216cd5e 100644 --- a/scripts/generate_references.py +++ b/scripts/generate_references.py @@ -22,7 +22,7 @@ def generate_references(): bibtex_lines = bibtex_content.splitlines() if bibtex_lines: # Replace the first line - bibtex_lines[0] = '@software{Melton_AdaptiveResonanceLib_2024,' + bibtex_lines[0] = '@misc{Melton_AdaptiveResonanceLib_2024,' modified_bibtex = '\n'.join(bibtex_lines) # Write the modified content to references.bib From 0a95e08898c209b01986e4857e75fdff939cc247 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 16:35:11 -0500 Subject: [PATCH 081/139] migrate files to scripts dir --- {examples => scripts}/comparison_of_methods.py | 0 {examples => scripts}/generate_model_results_snapshot.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {examples => scripts}/comparison_of_methods.py (100%) rename {examples => scripts}/generate_model_results_snapshot.py (100%) diff --git a/examples/comparison_of_methods.py b/scripts/comparison_of_methods.py similarity index 100% rename from examples/comparison_of_methods.py rename to scripts/comparison_of_methods.py diff --git a/examples/generate_model_results_snapshot.py b/scripts/generate_model_results_snapshot.py similarity index 100% rename from examples/generate_model_results_snapshot.py rename to scripts/generate_model_results_snapshot.py From e22727c4744ab13ef6784c8fce95b39a7a40d966 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 16:36:18 -0500 Subject: [PATCH 082/139] migrate files to scripts dir --- {examples => scripts}/optimization_of_methods.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {examples => scripts}/optimization_of_methods.py (100%) diff --git a/examples/optimization_of_methods.py b/scripts/optimization_of_methods.py similarity index 100% rename from examples/optimization_of_methods.py rename to scripts/optimization_of_methods.py From c275412cc2fb5fd9100ce35cfe6ffac0acc6516d Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 16:41:55 -0500 Subject: [PATCH 083/139] test to demo in examples dir --- examples/{test_VAT.py => demo_VAT.py} | 0 examples/{test_art1.py => demo_art1.py} | 0 examples/{test_art2.py => demo_art2.py} | 0 examples/{test_artmap.py => demo_artmap.py} | 0 examples/{test_bartmap.py => demo_bartmap.py} | 0 examples/{test_bayesian_art.py => demo_bayesian_art.py} | 0 examples/{test_convex_hull_art.py => demo_convex_hull_art.py} | 0 examples/{test_cvi_art.py => demo_cvi_art.py} | 0 .../{test_dual_vigilance_art.py => demo_dual_vigilance_art.py} | 0 examples/{test_ellipsoid_art.py => demo_ellipsoid_art.py} | 0 examples/{test_fusion_art.py => demo_fusion_art.py} | 0 examples/{test_fuzzy_art.py => demo_fuzzy_art.py} | 0 examples/{test_gaussian_art.py => demo_gaussian_art.py} | 0 examples/{test_grid_search_cv.py => demo_grid_search_cv.py} | 0 ..._search_cv_elementary.py => demo_grid_search_cv_elementary.py} | 0 examples/{test_hypersphere_art.py => demo_hypersphere_art.py} | 0 examples/{test_icvi_art.py => demo_icvi_art.py} | 0 ...{test_quadratic_neuron_art.py => demo_quadratic_neuron_art.py} | 0 examples/{test_regression.py => demo_regression.py} | 0 ...t_reinforcement_learning.py => demo_reinforcement_learning.py} | 0 examples/{test_seq_art.py => demo_seq_art.py} | 0 examples/{test_smart.py => demo_smart.py} | 0 examples/{test_topo_art.py => demo_topo_art.py} | 0 23 files changed, 0 insertions(+), 0 deletions(-) rename examples/{test_VAT.py => demo_VAT.py} (100%) rename examples/{test_art1.py => demo_art1.py} (100%) rename examples/{test_art2.py => demo_art2.py} (100%) rename examples/{test_artmap.py => demo_artmap.py} (100%) rename examples/{test_bartmap.py => demo_bartmap.py} (100%) rename examples/{test_bayesian_art.py => demo_bayesian_art.py} (100%) rename examples/{test_convex_hull_art.py => demo_convex_hull_art.py} (100%) rename examples/{test_cvi_art.py => demo_cvi_art.py} (100%) rename examples/{test_dual_vigilance_art.py => demo_dual_vigilance_art.py} (100%) rename examples/{test_ellipsoid_art.py => demo_ellipsoid_art.py} (100%) rename examples/{test_fusion_art.py => demo_fusion_art.py} (100%) rename examples/{test_fuzzy_art.py => demo_fuzzy_art.py} (100%) rename examples/{test_gaussian_art.py => demo_gaussian_art.py} (100%) rename examples/{test_grid_search_cv.py => demo_grid_search_cv.py} (100%) rename examples/{test_grid_search_cv_elementary.py => demo_grid_search_cv_elementary.py} (100%) rename examples/{test_hypersphere_art.py => demo_hypersphere_art.py} (100%) rename examples/{test_icvi_art.py => demo_icvi_art.py} (100%) rename examples/{test_quadratic_neuron_art.py => demo_quadratic_neuron_art.py} (100%) rename examples/{test_regression.py => demo_regression.py} (100%) rename examples/{test_reinforcement_learning.py => demo_reinforcement_learning.py} (100%) rename examples/{test_seq_art.py => demo_seq_art.py} (100%) rename examples/{test_smart.py => demo_smart.py} (100%) rename examples/{test_topo_art.py => demo_topo_art.py} (100%) diff --git a/examples/test_VAT.py b/examples/demo_VAT.py similarity index 100% rename from examples/test_VAT.py rename to examples/demo_VAT.py diff --git a/examples/test_art1.py b/examples/demo_art1.py similarity index 100% rename from examples/test_art1.py rename to examples/demo_art1.py diff --git a/examples/test_art2.py b/examples/demo_art2.py similarity index 100% rename from examples/test_art2.py rename to examples/demo_art2.py diff --git a/examples/test_artmap.py b/examples/demo_artmap.py similarity index 100% rename from examples/test_artmap.py rename to examples/demo_artmap.py diff --git a/examples/test_bartmap.py b/examples/demo_bartmap.py similarity index 100% rename from examples/test_bartmap.py rename to examples/demo_bartmap.py diff --git a/examples/test_bayesian_art.py b/examples/demo_bayesian_art.py similarity index 100% rename from examples/test_bayesian_art.py rename to examples/demo_bayesian_art.py diff --git a/examples/test_convex_hull_art.py b/examples/demo_convex_hull_art.py similarity index 100% rename from examples/test_convex_hull_art.py rename to examples/demo_convex_hull_art.py diff --git a/examples/test_cvi_art.py b/examples/demo_cvi_art.py similarity index 100% rename from examples/test_cvi_art.py rename to examples/demo_cvi_art.py diff --git a/examples/test_dual_vigilance_art.py b/examples/demo_dual_vigilance_art.py similarity index 100% rename from examples/test_dual_vigilance_art.py rename to examples/demo_dual_vigilance_art.py diff --git a/examples/test_ellipsoid_art.py b/examples/demo_ellipsoid_art.py similarity index 100% rename from examples/test_ellipsoid_art.py rename to examples/demo_ellipsoid_art.py diff --git a/examples/test_fusion_art.py b/examples/demo_fusion_art.py similarity index 100% rename from examples/test_fusion_art.py rename to examples/demo_fusion_art.py diff --git a/examples/test_fuzzy_art.py b/examples/demo_fuzzy_art.py similarity index 100% rename from examples/test_fuzzy_art.py rename to examples/demo_fuzzy_art.py diff --git a/examples/test_gaussian_art.py b/examples/demo_gaussian_art.py similarity index 100% rename from examples/test_gaussian_art.py rename to examples/demo_gaussian_art.py diff --git a/examples/test_grid_search_cv.py b/examples/demo_grid_search_cv.py similarity index 100% rename from examples/test_grid_search_cv.py rename to examples/demo_grid_search_cv.py diff --git a/examples/test_grid_search_cv_elementary.py b/examples/demo_grid_search_cv_elementary.py similarity index 100% rename from examples/test_grid_search_cv_elementary.py rename to examples/demo_grid_search_cv_elementary.py diff --git a/examples/test_hypersphere_art.py b/examples/demo_hypersphere_art.py similarity index 100% rename from examples/test_hypersphere_art.py rename to examples/demo_hypersphere_art.py diff --git a/examples/test_icvi_art.py b/examples/demo_icvi_art.py similarity index 100% rename from examples/test_icvi_art.py rename to examples/demo_icvi_art.py diff --git a/examples/test_quadratic_neuron_art.py b/examples/demo_quadratic_neuron_art.py similarity index 100% rename from examples/test_quadratic_neuron_art.py rename to examples/demo_quadratic_neuron_art.py diff --git a/examples/test_regression.py b/examples/demo_regression.py similarity index 100% rename from examples/test_regression.py rename to examples/demo_regression.py diff --git a/examples/test_reinforcement_learning.py b/examples/demo_reinforcement_learning.py similarity index 100% rename from examples/test_reinforcement_learning.py rename to examples/demo_reinforcement_learning.py diff --git a/examples/test_seq_art.py b/examples/demo_seq_art.py similarity index 100% rename from examples/test_seq_art.py rename to examples/demo_seq_art.py diff --git a/examples/test_smart.py b/examples/demo_smart.py similarity index 100% rename from examples/test_smart.py rename to examples/demo_smart.py diff --git a/examples/test_topo_art.py b/examples/demo_topo_art.py similarity index 100% rename from examples/test_topo_art.py rename to examples/demo_topo_art.py From 4f7dd14aa0f5ecb7c1c16454bd3c062752f3fe20 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 20:16:42 -0500 Subject: [PATCH 084/139] init pre-commit --- .pre-commit-config.yaml | 36 ++++++++++++++++++++++++++++++++++++ pyproject.toml | 3 +++ readthedocs.yaml | 4 ++-- 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a38b06d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,36 @@ +# .pre-commit-config.yaml + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + - id: trailing-whitespace + exclude: ^unit_tests/ + + - repo: https://github.com/psf/black + rev: 23.9.1 # Use the latest stable version + hooks: + - id: black + args: [--line-length=80] + + - repo: https://github.com/PyCQA/flake8 + rev: 6.1.0 # Use the latest stable version + hooks: + - id: flake8 + args: [--max-line-length=80] + additional_dependencies: [flake8-docstrings] + + - repo: https://github.com/PyCQA/pydocstyle + rev: 6.3.0 + hooks: + - id: pydocstyle + args: [--convention=google] + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.5.1 + hooks: + - id: mypy + args: [--strict] diff --git a/pyproject.toml b/pyproject.toml index 81dc029..343bf14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,9 @@ matplotlib = ">=3.3.3" [tool.poetry.dev-dependencies] pytest = "^6.2.2" +pre-commit = "^4.0.0" + +[tool.poetry.docs-dependencies] sphinx = "^6.2.1" sphinx-rtd-theme = "^1.0.0" # If you're using the Read the Docs theme sphinx-autoapi = ">=3.0.0" diff --git a/readthedocs.yaml b/readthedocs.yaml index 57442c0..91db34b 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -14,7 +14,7 @@ build: # https://python-poetry.org/docs/managing-dependencies/#dependency-groups # VIRTUAL_ENV needs to be set manually for now. # See https://github.com/readthedocs/readthedocs.org/pull/11152/ - - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with dev + - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs # Generate references.bib using the Python script - python scripts/generate_references.py @@ -24,4 +24,4 @@ build: sphinx: - configuration: docs/source/conf.py \ No newline at end of file + configuration: docs/source/conf.py From c1ea20b643ec1b9a195e765ccffbd14f376f676a Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 20:19:47 -0500 Subject: [PATCH 085/139] init pre-commit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 343bf14..c42a278 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ matplotlib = ">=3.3.3" pytest = "^6.2.2" pre-commit = "^4.0.0" -[tool.poetry.docs-dependencies] +[tool.poetry.group.docs.dependencies] sphinx = "^6.2.1" sphinx-rtd-theme = "^1.0.0" # If you're using the Read the Docs theme sphinx-autoapi = ">=3.0.0" From fbe7cbd1cd911fa989c2a2c602aa0b75cf694594 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 20:21:31 -0500 Subject: [PATCH 086/139] init pre-commit --- docs/source/citation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/citation.rst b/docs/source/citation.rst index 5c8a577..1b5e4dd 100644 --- a/docs/source/citation.rst +++ b/docs/source/citation.rst @@ -1,6 +1,6 @@ Citation ================================== -If you use this project in your research, please cite it as :cite:`YourReferenceHere` +If you use this project in your research, please cite it as: .. bibliography:: references.bib :style: unsrt @@ -9,4 +9,4 @@ If you use this project in your research, please cite it as :cite:`YourReference Alternatively, you can cite the project using the following BibTeX entry: .. literalinclude:: references.bib - :language: bibtex \ No newline at end of file + :language: bibtex From f9fedb4bb8fcdca406017ae0e3c5347987095de7 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 21:59:44 -0500 Subject: [PATCH 087/139] test pre-commit --- .pre-commit-config.yaml | 53 +- CITATIONS.txt | 2 +- CONTRIBUTING.md | 4 +- README.md | 6 +- artlib/__init__.py | 14 +- artlib/biclustering/BARTMAP.py | 47 +- artlib/biclustering/__init__.py | 2 +- artlib/common/BaseART.py | 175 ++++--- artlib/common/BaseARTMAP.py | 35 +- artlib/common/VAT.py | 7 +- artlib/common/__init__.py | 2 +- artlib/common/utils.py | 16 +- artlib/common/visualization.py | 92 ++-- artlib/cvi/CVIART.py | 92 +++- artlib/cvi/__init__.py | 2 +- artlib/cvi/iCVIFuzzyArt.py | 56 ++- artlib/cvi/iCVIs/CalinkskiHarabasz.py | 255 ++++++---- artlib/elementary/ART1.py | 47 +- artlib/elementary/ART2.py | 44 +- artlib/elementary/BayesianART.py | 78 ++- artlib/elementary/EllipsoidART.py | 90 ++-- artlib/elementary/FuzzyART.py | 72 ++- artlib/elementary/GaussianART.py | 77 +-- artlib/elementary/HypersphereART.py | 64 +-- artlib/elementary/QuadraticNeuronART.py | 51 +- artlib/elementary/__init__.py | 2 +- artlib/experimental/ConvexHullART.py | 92 ++-- artlib/experimental/SeqART.py | 94 ++-- artlib/experimental/__init__.py | 2 +- artlib/experimental/merging.py | 1 + artlib/fusion/FusionART.py | 200 +++++--- artlib/fusion/__init__.py | 2 +- artlib/hierarchical/DeepARTMAP.py | 150 ++++-- artlib/hierarchical/SMART.py | 83 +++- artlib/hierarchical/__init__.py | 2 +- artlib/reinforcement/FALCON.py | 111 +++-- artlib/reinforcement/__init__.py | 2 +- artlib/supervised/ARTMAP.py | 55 ++- artlib/supervised/SimpleARTMAP.py | 105 ++-- artlib/supervised/__init__.py | 2 +- artlib/topological/DualVigilanceART.py | 80 +++- artlib/topological/TopoART.py | 140 ++++-- artlib/topological/__init__.py | 2 +- docs/source/artlib.biclustering.rst | 2 - docs/source/artlib.common.rst | 1 - docs/source/artlib.cvi.iCVIs.rst | 2 - docs/source/artlib.cvi.rst | 2 - docs/source/artlib.elementary.rst | 2 - docs/source/artlib.experimental.rst | 2 - docs/source/artlib.fusion.rst | 2 - docs/source/artlib.hierarchical.rst | 2 - docs/source/artlib.reinforcement.rst | 2 - docs/source/artlib.rst | 2 - docs/source/artlib.supervised.rst | 2 - docs/source/artlib.topological.rst | 2 - docs/source/available_models.rst | 1 - docs/source/comparison.rst | 1 - docs/source/conf.py | 58 +-- docs/source/contact.rst | 1 - docs/source/contributing.rst | 1 - docs/source/examples.rst | 1 - docs/source/index.rst | 2 +- docs/source/installation.rst | 1 - docs/source/license.rst | 1 - docs/source/quick_start.rst | 1 - examples/demo_VAT.py | 13 +- examples/demo_art1.py | 19 +- examples/demo_art2.py | 9 +- examples/demo_artmap.py | 33 +- examples/demo_bartmap.py | 20 +- examples/demo_bayesian_art.py | 11 +- examples/demo_convex_hull_art.py | 14 +- examples/demo_cvi_art.py | 10 +- examples/demo_dual_vigilance_art.py | 16 +- examples/demo_ellipsoid_art.py | 18 +- examples/demo_fusion_art.py | 24 +- examples/demo_fuzzy_art.py | 16 +- examples/demo_gaussian_art.py | 10 +- examples/demo_grid_search_cv.py | 13 +- examples/demo_grid_search_cv_elementary.py | 19 +- examples/demo_hypersphere_art.py | 17 +- examples/demo_icvi_art.py | 12 +- examples/demo_quadratic_neuron_art.py | 18 +- examples/demo_regression.py | 34 +- examples/demo_reinforcement_learning.py | 136 ++++-- examples/demo_seq_art.py | 7 +- examples/demo_smart.py | 16 +- examples/demo_topo_art.py | 16 +- scripts/comparison_of_methods.py | 531 +++++++++------------ scripts/generate_model_results_snapshot.py | 46 +- scripts/generate_references.py | 22 +- scripts/optimization_of_methods.py | 209 ++++---- templates/ART_template.py | 29 +- unit_tests/test_ART1.py | 50 +- unit_tests/test_ART2.py | 28 +- unit_tests/test_ARTMAP.py | 16 +- unit_tests/test_BARTMAP.py | 19 +- unit_tests/test_BayesianART.py | 41 +- unit_tests/test_CVIART.py | 16 +- unit_tests/test_DeepARTMAP.py | 4 +- unit_tests/test_DualVigilanceART.py | 40 +- unit_tests/test_EllipsoidART.py | 52 +- unit_tests/test_FALCON.py | 41 +- unit_tests/test_FusionART.py | 18 +- unit_tests/test_FuzzyART.py | 54 ++- unit_tests/test_GaussianART.py | 51 +- unit_tests/test_HypersphereART.py | 48 +- unit_tests/test_QuadraticNeuronART.py | 72 ++- unit_tests/test_SMART.py | 6 +- unit_tests/test_SimpleARTMAP.py | 12 + unit_tests/test_TD_FALCON.py | 53 +- unit_tests/test_TopoART.py | 50 +- unit_tests/test_clustering_consistency.py | 31 +- unit_tests/test_iCVI_FuzzyART.py | 24 +- 114 files changed, 2895 insertions(+), 1768 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a38b06d..bebb43b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,29 +8,42 @@ repos: - id: debug-statements - id: end-of-file-fixer - id: trailing-whitespace - exclude: ^unit_tests/ + +# - repo: local +# hooks: +# - id: docformatter +# name: docformatter +# entry: docformatter +# language: python +# types: [python] +# additional_dependencies: [docformatter==1.7.5] +# args: +# - --black +# - --style=numpy +# - --blank - repo: https://github.com/psf/black rev: 23.9.1 # Use the latest stable version hooks: - id: black - args: [--line-length=80] - - - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 # Use the latest stable version - hooks: - - id: flake8 - args: [--max-line-length=80] - additional_dependencies: [flake8-docstrings] +# +# - repo: https://github.com/PyCQA/flake8 +# rev: 6.1.0 # Use the latest stable version +# hooks: +# - id: flake8 +# args: [--max-line-length=80] +# additional_dependencies: [flake8-docstrings] +# +# - repo: https://github.com/PyCQA/pydocstyle +# rev: 6.3.0 +# hooks: +# - id: pydocstyle +# args: [--convention=numpy] +# +# - repo: https://github.com/pre-commit/mirrors-mypy +# rev: v1.5.1 +# hooks: +# - id: mypy +# args: [--strict] - - repo: https://github.com/PyCQA/pydocstyle - rev: 6.3.0 - hooks: - - id: pydocstyle - args: [--convention=google] - - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.1 - hooks: - - id: mypy - args: [--strict] +exclude: ^(unit_tests/|scripts/|artlib/experimental/|examples/|templates/) diff --git a/CITATIONS.txt b/CITATIONS.txt index 92a0339..ff9efa6 100644 --- a/CITATIONS.txt +++ b/CITATIONS.txt @@ -366,4 +366,4 @@ Yava ̧s, M., & Alpaslan, F. N. (2012). Hierarchical behavior categorization usi Zadeh, L. A. (1965). Fuzzy sets. Information and Control, 8, 338 – 353. doi:10.1016/S0019-9958(65) 90241-X. -Brito da Silva LE, Rayapati N, Wunsch DC. iCVI-ARTMAP: Using Incremental Cluster Validity Indices and Adaptive Resonance Theory Reset Mechanism to Accelerate Validation and Achieve Multiprototype Unsupervised Representations. IEEE Trans Neural Netw Learn Syst. 2023 Dec;34(12):9757-9770. doi: 10.1109/TNNLS.2022.3160381. Epub 2023 Nov 30. PMID: 35353707. \ No newline at end of file +Brito da Silva LE, Rayapati N, Wunsch DC. iCVI-ARTMAP: Using Incremental Cluster Validity Indices and Adaptive Resonance Theory Reset Mechanism to Accelerate Validation and Achieve Multiprototype Unsupervised Representations. IEEE Trans Neural Netw Learn Syst. 2023 Dec;34(12):9757-9770. doi: 10.1109/TNNLS.2022.3160381. Epub 2023 Nov 30. PMID: 35353707. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 65056db..6158420 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,9 +38,9 @@ If you're ready to contribute, here's how you can do it: 5. **Run the tests** to ensure your changes don't break existing functionality. -6. **Update the documentation** if you're adding new features or changing existing behavior. +6. **Update the documentation** if you're adding new features or changing existing behavior. -7. **Submit a pull request**. Include a clear description of the changes and reference any related issues. +7. **Submit a pull request**. Include a clear description of the changes and reference any related issues. ## Pull Request Process diff --git a/README.md b/README.md index 2b1b0d2..00cbaa9 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ AdaptiveResonanceLib includes implementations for the following ART models: - [Ellipsoid ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.EllipsoidART) - [Fuzzy ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.FuzzyART) - [Quadratic Neuron ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.QuadraticNeuronART) -- #### Metric Informed +- #### Metric Informed - [CVI ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.CVIART) - [iCVI Fuzzy ART](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.iCVIFuzzyART) - #### Topological @@ -38,7 +38,7 @@ AdaptiveResonanceLib includes implementations for the following ART models: - [TD-FALCON](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.html#artlib.TD_FALCON) - #### Biclustering - [Biclustering ARTMAP](https://adaptiveresonancelib.readthedocs.io/en/latest/artlib.biclustering.html#artlib.biclustering.BARTMAP.BARTMAP) - + @@ -130,4 +130,4 @@ Happy Modeling with AdaptiveResonanceLib! If you use this project in your research, please cite it as: Melton, N. (2024). AdaptiveResonanceLib (Version 0.1.2) - \ No newline at end of file + diff --git a/artlib/__init__.py b/artlib/__init__.py index 59eab5d..36342e7 100644 --- a/artlib/__init__.py +++ b/artlib/__init__.py @@ -15,7 +15,12 @@ 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 +from artlib.common.utils import ( + normalize, + compliment_code, + de_compliment_code, + de_normalize, +) from artlib.common.VAT import VAT from artlib.elementary.ART1 import ART1 @@ -30,7 +35,8 @@ from artlib.cvi.iCVIFuzzyArt import iCVIFuzzyART from artlib.cvi.CVIART import CVIART -from artlib.supervised.ARTMAP import ARTMAP, SimpleARTMAP +from artlib.supervised.ARTMAP import ARTMAP +from artlib.supervised.SimpleARTMAP import SimpleARTMAP from artlib.hierarchical.SMART import SMART from artlib.hierarchical.DeepARTMAP import DeepARTMAP @@ -68,5 +74,5 @@ "iCVIFuzzyART", "CVIART", "FALCON", - "TD_FALCON" -] \ No newline at end of file + "TD_FALCON", +] diff --git a/artlib/biclustering/BARTMAP.py b/artlib/biclustering/BARTMAP.py index bf27e46..90d76b2 100644 --- a/artlib/biclustering/BARTMAP.py +++ b/artlib/biclustering/BARTMAP.py @@ -18,6 +18,7 @@ from sklearn.base import BaseEstimator, BiclusterMixin from scipy.stats import pearsonr + class BARTMAP(BaseEstimator, BiclusterMixin): """ BARTMAP for Biclustering @@ -35,8 +36,9 @@ class BARTMAP(BaseEstimator, BiclusterMixin): at least one of the feature clusters. """ - rows_: np.ndarray #bool - columns_: np.ndarray #bool + + rows_: np.ndarray # bool + columns_: np.ndarray # bool def __init__(self, module_a: BaseART, module_b: BaseART, eta: float): """ @@ -63,10 +65,12 @@ def __getattr__(self, key): return self.params[key] else: # If the key is not in params, raise an AttributeError - raise AttributeError(f"'{type(self).__name__}' object has no attribute '{key}'") + raise AttributeError( + f"'{type(self).__name__}' object has no attribute '{key}'" + ) def __setattr__(self, key, value): - if key in self.__dict__.get('params', {}): + if key in self.__dict__.get("params", {}): # If key is in params, set its value self.params[key] = value else: @@ -241,12 +245,9 @@ def _average_pearson_corr(self, X: np.ndarray, k: int, c_b: int) -> float: X_a = X[self.column_labels_ == c_b, :] if len(X_a) == 0: raise ValueError("X_a has length 0") - X_k_cb = self._get_x_cb(X[k,:], c_b) + X_k_cb = self._get_x_cb(X[k, :], c_b) mean_r = np.mean( - [ - self._pearsonr(X_k_cb, self._get_x_cb(x_a_l, c_b)) - for x_a_l in X_a - ] + [self._pearsonr(X_k_cb, self._get_x_cb(x_a_l, c_b)) for x_a_l in X_a] ) return float(mean_r) @@ -266,7 +267,9 @@ def validate_data(self, X_a: np.ndarray, X_b: np.ndarray): self.module_a.validate_data(X_a) self.module_b.validate_data(X_b) - def match_criterion_bin(self, X: np.ndarray, k: int, c_b: int, params: dict) -> bool: + def match_criterion_bin( + self, X: np.ndarray, k: int, c_b: int, params: dict + ) -> bool: """ Get the binary match criterion of the cluster. @@ -291,13 +294,13 @@ def match_criterion_bin(self, X: np.ndarray, k: int, c_b: int, params: dict) -> return M >= self.params["eta"] def match_reset_func( - self, - i: np.ndarray, - w: np.ndarray, - cluster_a, - params: dict, - extra: dict, - cache: Optional[dict] = None + self, + i: np.ndarray, + w: np.ndarray, + cluster_a, + params: dict, + extra: dict, + cache: Optional[dict] = None, ) -> bool: """ Permit external factors to influence cluster creation. @@ -372,7 +375,6 @@ def fit(self, X: np.ndarray, max_iter=1): X_b = self.module_b.prepare_data(X.T) self.validate_data(X_a, X_b) - self.module_b = self.module_b.fit(X_b, max_iter=max_iter) # init module A @@ -402,11 +404,7 @@ def fit(self, X: np.ndarray, max_iter=1): ) return self - - def visualize( - self, - cmap: Optional[Colormap] = None - ): + def visualize(self, cmap: Optional[Colormap] = None): """ Visualize the clustering of the data. @@ -420,7 +418,8 @@ def visualize( if cmap is None: from matplotlib.pyplot import cm - cmap=plt.cm.Blues + + cmap = plt.cm.Blues plt.matshow( np.outer(np.sort(self.row_labels_) + 1, np.sort(self.column_labels_) + 1), diff --git a/artlib/biclustering/__init__.py b/artlib/biclustering/__init__.py index 4b3a0d0..542f8ec 100644 --- a/artlib/biclustering/__init__.py +++ b/artlib/biclustering/__init__.py @@ -9,4 +9,4 @@ `Biclustering `_ -""" \ No newline at end of file +""" diff --git a/artlib/common/BaseART.py b/artlib/common/BaseART.py index dfac044..bcc2170 100644 --- a/artlib/common/BaseART.py +++ b/artlib/common/BaseART.py @@ -14,6 +14,7 @@ class BaseART(BaseEstimator, ClusterMixin): """ Generic implementation of Adaptive Resonance Theory (ART) """ + def __init__(self, params: dict): """ Parameters @@ -34,17 +35,18 @@ def __getattr__(self, key): return self.params[key] else: # If the key is not in params, raise an AttributeError - raise AttributeError(f"'{type(self).__name__}' object has no attribute '{key}'") + raise AttributeError( + f"'{type(self).__name__}' object has no attribute '{key}'" + ) def __setattr__(self, key, value): - if key in self.__dict__.get('params', {}): + if key in self.__dict__.get("params", {}): # If key is in params, set its value self.params[key] = value else: # Otherwise, proceed with normal attribute setting super().__setattr__(key, value) - def get_params(self, deep: bool = True) -> dict: """ Parameters @@ -106,7 +108,6 @@ def set_params(self, **params): self.validate_params(local_params) return self - def prepare_data(self, X: np.ndarray) -> np.ndarray: """ Prepare data for clustering. @@ -198,7 +199,9 @@ def validate_data(self, X: np.ndarray): assert np.all(X <= 1.0), "Data has not been normalized" self.check_dimensions(X) - def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: + def category_choice( + self, i: np.ndarray, w: np.ndarray, params: dict + ) -> tuple[float, Optional[dict]]: """ Get the activation of the cluster. @@ -219,7 +222,13 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f """ raise NotImplementedError - def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: + 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. @@ -242,7 +251,14 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt """ 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]: + 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. @@ -271,7 +287,13 @@ def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: cache["match_criterion_bin"] = M_bin return M_bin, cache - def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: + def update( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + ) -> np.ndarray: """ Get the updated cluster weight. @@ -341,10 +363,16 @@ def set_weight(self, idx: int, new_w: np.ndarray): self.weight_sample_counter_[idx] += 1 self.W[idx] = new_w - def _match_tracking(self, cache: dict, epsilon: float, params: dict, method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"]) -> bool: + def _match_tracking( + self, + cache: dict, + epsilon: float, + params: dict, + method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"], + ) -> bool: M = cache["match_criterion"] if method == "MT+": - self.params["rho"] = M+epsilon + self.params["rho"] = M + epsilon return True elif method == "MT-": self.params["rho"] = M - epsilon @@ -361,8 +389,10 @@ def _match_tracking(self, cache: dict, epsilon: float, params: dict, method: Lit raise ValueError(f"Invalid Match Tracking Method: {method}") @staticmethod - def _match_tracking_operator(method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"]) -> Callable: - if method in ["MT+", "MT-","MT1"]: + def _match_tracking_operator( + method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] + ) -> Callable: + if method in ["MT+", "MT-", "MT1"]: return operator.ge elif method in ["MT0", "MT~"]: return operator.gt @@ -375,8 +405,13 @@ def _set_params(self, new_params): def _deep_copy_params(self) -> dict: return deepcopy(self.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 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. @@ -405,28 +440,32 @@ def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None, m self.add_weight(w_new) return 0 else: - if match_reset_method in ["MT~"] and match_reset_func is not None: - T_values, T_cache = zip(*[ - self.category_choice(x, w, params=self.params) - if match_reset_func(x, w, c_, params=self.params, cache=None) - else (np.nan, None) - for c_, w in enumerate(self.W) - ]) + T_values, T_cache = zip( + *[ + self.category_choice(x, w, params=self.params) + if match_reset_func(x, w, c_, params=self.params, cache=None) + else (np.nan, None) + for c_, w in enumerate(self.W) + ] + ) else: - T_values, T_cache = zip(*[self.category_choice(x, w, params=self.params) for w in self.W]) + T_values, T_cache = zip( + *[self.category_choice(x, w, params=self.params) for w in self.W] + ) T = np.array(T_values) while any(~np.isnan(T)): c_ = int(np.nanargmax(T)) w = self.W[c_] cache = T_cache[c_] - m, cache = self.match_criterion_bin(x, w, params=self.params, cache=cache, op=mt_operator) + m, cache = self.match_criterion_bin( + x, w, params=self.params, cache=cache, op=mt_operator + ) if match_reset_method in ["MT~"] and match_reset_func is not None: no_match_reset = True else: - no_match_reset = ( - match_reset_func is None or - match_reset_func(x, w, c_, params=self.params, cache=cache) + no_match_reset = match_reset_func is None or match_reset_func( + x, w, c_, params=self.params, cache=cache ) if m and no_match_reset: self.set_weight(c_, self.update(x, w, self.params, cache=cache)) @@ -435,7 +474,9 @@ def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None, m else: T[c_] = np.nan if m and not no_match_reset: - keep_searching = self._match_tracking(cache, epsilon, self.params, match_reset_method) + keep_searching = self._match_tracking( + cache, epsilon, self.params, match_reset_method + ) if not keep_searching: T[:] = np.nan @@ -505,8 +546,16 @@ def post_fit(self, X: np.ndarray): # this is where pruning steps can go pass - - 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): + 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. @@ -533,23 +582,34 @@ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, match_reset_func: O self.is_fitted_ = True self.W: list[np.ndarray] = [] - self.labels_ = np.zeros((X.shape[0], ), dtype=int) + self.labels_ = np.zeros((X.shape[0],), dtype=int) for _ in range(max_iter): if verbose: from tqdm import tqdm + x_iter = tqdm(enumerate(X), total=int(X.shape[0])) else: x_iter = enumerate(X) for i, x in x_iter: self.pre_step_fit(X) - c = self.step_fit(x, match_reset_func=match_reset_func, match_reset_method=match_reset_method, epsilon=epsilon) + c = self.step_fit( + x, + match_reset_func=match_reset_func, + match_reset_method=match_reset_method, + epsilon=epsilon, + ) self.labels_[i] = c self.post_step_fit(X) self.post_fit(X) return self - - 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): + 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. @@ -568,21 +628,25 @@ def partial_fit(self, X: np.ndarray, match_reset_func: Optional[Callable] = None self.validate_data(X) self.check_dimensions(X) - self.is_fitted_ = True + self.is_fitted_ = True - if not hasattr(self, 'W'): + if not hasattr(self, "W"): self.W: list[np.ndarray] = [] - self.labels_ = np.zeros((X.shape[0], ), dtype=int) + self.labels_ = np.zeros((X.shape[0],), dtype=int) j = 0 else: j = len(self.labels_) - self.labels_ = np.pad(self.labels_, [(0, X.shape[0])], mode='constant') + self.labels_ = np.pad(self.labels_, [(0, X.shape[0])], mode="constant") for i, x in enumerate(X): - c = self.step_fit(x, match_reset_func=match_reset_func, match_reset_method=match_reset_method, epsilon=epsilon) - self.labels_[i+j] = c + c = self.step_fit( + x, + match_reset_func=match_reset_func, + match_reset_method=match_reset_method, + epsilon=epsilon, + ) + self.labels_[i + j] = c return self - def predict(self, X: np.ndarray) -> np.ndarray: """ Predict labels for the data. @@ -640,15 +704,14 @@ def get_cluster_centers(self) -> List[np.ndarray]: """ raise NotImplementedError - def visualize( - self, - X: np.ndarray, - y: np.ndarray, - ax: Optional[Axes] = None, - marker_size: int = 10, - linewidth: int = 1, - colors: Optional[Iterable] = None + self, + X: np.ndarray, + y: np.ndarray, + ax: Optional[Axes] = None, + marker_size: int = 10, + linewidth: int = 1, + colors: Optional[Iterable] = None, ): """ Visualize the clustering of the data. @@ -676,20 +739,20 @@ def visualize( if colors is None: from matplotlib.pyplot import cm + colors = cm.rainbow(np.linspace(0, 1, self.n_clusters)) for k, col in enumerate(colors): cluster_data = y == k - plt.scatter(X[cluster_data, 0], X[cluster_data, 1], color=col, marker=".", s=marker_size) + plt.scatter( + X[cluster_data, 0], + X[cluster_data, 1], + color=col, + marker=".", + s=marker_size, + ) try: self.plot_cluster_bounds(ax, colors, linewidth) except NotImplementedError: warn(f"{self.__class__.__name__} does not support plotting cluster bounds.") - - - - - - - diff --git a/artlib/common/BaseARTMAP.py b/artlib/common/BaseARTMAP.py index d45c3a2..abe718c 100644 --- a/artlib/common/BaseARTMAP.py +++ b/artlib/common/BaseARTMAP.py @@ -4,10 +4,12 @@ from matplotlib.axes import Axes 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() @@ -90,7 +92,14 @@ def validate_data(self, X: np.ndarray, y: np.ndarray): """ 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): + 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. @@ -110,7 +119,13 @@ def fit(self, X: np.ndarray, y: np.ndarray, max_iter=1, match_reset_method: Lite """ 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): + 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. @@ -179,13 +194,13 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): raise NotImplementedError def visualize( - self, - X: np.ndarray, - y: np.ndarray, - ax: Optional[Axes] = None, - marker_size: int = 10, - linewidth: int = 1, - colors: Optional[Iterable] = None + self, + X: np.ndarray, + y: np.ndarray, + ax: Optional[Axes] = None, + marker_size: int = 10, + linewidth: int = 1, + colors: Optional[Iterable] = None, ): """ Visualize the clustering of the data. @@ -206,4 +221,4 @@ def visualize( Colors to use for each cluster, by default None. """ - raise NotImplementedError \ No newline at end of file + raise NotImplementedError diff --git a/artlib/common/VAT.py b/artlib/common/VAT.py index db8fc28..46e89bc 100644 --- a/artlib/common/VAT.py +++ b/artlib/common/VAT.py @@ -5,7 +5,10 @@ from scipy.spatial.distance import pdist, squareform -def VAT(data: np.ndarray, distance_metric: Optional[Callable] = lambda X: pdist(X, "euclidean")) -> Tuple[np.ndarray, np.ndarray]: +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. @@ -44,5 +47,3 @@ def VAT(data: np.ndarray, distance_metric: Optional[Callable] = lambda X: pdist( remaining.pop(jx) return pairwise_dist[np.ix_(indicies, indicies)], np.array(indicies) - - diff --git a/artlib/common/__init__.py b/artlib/common/__init__.py index 10c2ceb..2d3288d 100644 --- a/artlib/common/__init__.py +++ b/artlib/common/__init__.py @@ -1,4 +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/common/utils.py b/artlib/common/utils.py index 694560e..cb45e38 100644 --- a/artlib/common/utils.py +++ b/artlib/common/utils.py @@ -2,8 +2,11 @@ from typing import Tuple, Optional -def normalize(data: np.ndarray, d_max: Optional[np.ndarray] = None, d_min: Optional[np.ndarray] = None) -> Tuple[ - np.ndarray, np.ndarray, np.ndarray]: +def normalize( + data: np.ndarray, + d_max: Optional[np.ndarray] = None, + d_min: Optional[np.ndarray] = None, +) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ Normalize data column-wise between 0 and 1. @@ -56,6 +59,7 @@ def de_normalize(data: np.ndarray, d_max: np.ndarray, d_min: np.ndarray) -> np.n """ return data * (d_max - d_min) + d_min + def compliment_code(data: np.ndarray) -> np.ndarray: """ Compliment code the data. @@ -71,9 +75,10 @@ def compliment_code(data: np.ndarray) -> np.ndarray: Compliment coded data. """ - cc_data = np.hstack([data, 1.0-data]) + cc_data = np.hstack([data, 1.0 - data]) return cc_data + def de_compliment_code(data: np.ndarray) -> np.ndarray: """ Find the centroid of compliment coded data. @@ -100,13 +105,14 @@ def de_compliment_code(data: np.ndarray) -> np.ndarray: # Split the array into two arrays of shape n x m arr1 = data[:, :m] - arr2 = 1-data[:, m:] + arr2 = 1 - data[:, m:] # Find the element-wise mean mean_array = (arr1 + arr2) / 2 return mean_array + def l1norm(x: np.ndarray) -> float: """ Get the L1 norm of a vector. @@ -124,6 +130,7 @@ def l1norm(x: np.ndarray) -> float: """ return float(np.sum(np.absolute(x))) + def l2norm2(data: np.ndarray) -> float: """ Get the squared L2 norm of a vector. @@ -141,6 +148,7 @@ def l2norm2(data: np.ndarray) -> float: """ 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. diff --git a/artlib/common/visualization.py b/artlib/common/visualization.py index a49afa7..a2fd8bd 100644 --- a/artlib/common/visualization.py +++ b/artlib/common/visualization.py @@ -1,14 +1,15 @@ import numpy as np from matplotlib.axes import Axes + def plot_gaussian_contours_fading( - ax: Axes, - mean: np.ndarray, - std_dev: np.ndarray, - color: np.ndarray, - max_std: int = 2, - sigma_steps: float = 0.25, - linewidth: int = 1 + ax: Axes, + mean: np.ndarray, + std_dev: np.ndarray, + color: np.ndarray, + max_std: int = 2, + sigma_steps: float = 0.25, + linewidth: int = 1, ): """ Plot concentric ellipses to represent the contours of a 2D Gaussian distribution with fading colors. @@ -38,7 +39,7 @@ def plot_gaussian_contours_fading( alphas = np.linspace(1, 0.1, steps) if len(color) != 4: - color = np.concatenate([color, [1.]]) + color = np.concatenate([color, [1.0]]) for i, alpha in zip(range(1, steps + 1), alphas): # Adjust the alpha value of the color @@ -46,20 +47,31 @@ def plot_gaussian_contours_fading( current_color[-1] = alpha # Update the alpha channel # Width and height of the ellipse are 2*i*sigma_steps times the std_dev values - width, height = 2 * i * sigma_steps * std_dev[0], 2 * i * sigma_steps * std_dev[1] - ellipse = Ellipse(xy=(mean[0], mean[1]), width=width, height=height, edgecolor=current_color, facecolor='none', linewidth=linewidth, - linestyle='dashed', label=f'{i * sigma_steps}σ') + width, height = ( + 2 * i * sigma_steps * std_dev[0], + 2 * i * sigma_steps * std_dev[1], + ) + ellipse = Ellipse( + xy=(mean[0], mean[1]), + width=width, + height=height, + edgecolor=current_color, + facecolor="none", + linewidth=linewidth, + linestyle="dashed", + label=f"{i * sigma_steps}σ", + ) ax.add_patch(ellipse) def plot_gaussian_contours_covariance( - ax: Axes, - mean: np.ndarray, - covariance: np.ndarray, - color: np.ndarray, - max_std: int = 2, - sigma_steps: float = 0.25, - linewidth: int = 1 + ax: Axes, + mean: np.ndarray, + covariance: np.ndarray, + color: np.ndarray, + max_std: int = 2, + sigma_steps: float = 0.25, + linewidth: int = 1, ): """ Plot concentric ellipses to represent the contours of a 2D Gaussian distribution with fading colors. @@ -87,10 +99,15 @@ def plot_gaussian_contours_covariance( # Calculate the eigenvalues and eigenvectors of the covariance matrix eigenvalues, eigenvectors = np.linalg.eig(covariance) - major_axis = np.sqrt(eigenvalues[0]) # The major axis length (sqrt of larger eigenvalue) - minor_axis = np.sqrt(eigenvalues[1]) # The minor axis length (sqrt of smaller eigenvalue) + major_axis = np.sqrt( + eigenvalues[0] + ) # The major axis length (sqrt of larger eigenvalue) + minor_axis = np.sqrt( + eigenvalues[1] + ) # The minor axis length (sqrt of smaller eigenvalue) angle = np.arctan2( - *eigenvectors[:, 0][::-1]) # Angle in radians between the x-axis and the major axis of the ellipse + *eigenvectors[:, 0][::-1] + ) # Angle in radians between the x-axis and the major axis of the ellipse # Calculate the number of steps steps = int(max_std / sigma_steps) @@ -102,20 +119,31 @@ def plot_gaussian_contours_covariance( current_color[-1] = alpha # Update the alpha channel # Width and height of the ellipse based on the covariance - width, height = 2 * i * sigma_steps * major_axis * 2, 2 * i * sigma_steps * minor_axis * 2 - ellipse = Ellipse(xy=(mean[0], mean[1]), width=width, height=height, angle=float(np.degrees(angle)), - edgecolor=current_color, facecolor='None', linewidth=linewidth, - linestyle='dashed', label=f'{i * sigma_steps}σ') + width, height = ( + 2 * i * sigma_steps * major_axis * 2, + 2 * i * sigma_steps * minor_axis * 2, + ) + ellipse = Ellipse( + xy=(mean[0], mean[1]), + width=width, + height=height, + angle=float(np.degrees(angle)), + edgecolor=current_color, + facecolor="None", + linewidth=linewidth, + linestyle="dashed", + label=f"{i * sigma_steps}σ", + ) ax.add_patch(ellipse) def plot_weight_matrix_as_ellipse( - ax: Axes, - s: float, - W: np.ndarray, - mean: np.ndarray, - color: np.ndarray, - linewidth: int = 1 + ax: Axes, + s: float, + W: np.ndarray, + mean: np.ndarray, + color: np.ndarray, + linewidth: int = 1, ): """ Plot the transformation of a unit circle by the weight matrix W as an ellipse. @@ -144,7 +172,7 @@ def plot_weight_matrix_as_ellipse( circle = np.array([np.cos(theta), np.sin(theta)]) # Unit circle # Apply the linear transformation to the circle to get an ellipse - ellipse = 0.25*s * s * (transform_matrix @ circle) + ellipse = 0.25 * s * s * (transform_matrix @ circle) # Shift the ellipse to the specified mean ellipse[0, :] += mean[0] diff --git a/artlib/cvi/CVIART.py b/artlib/cvi/CVIART.py index fc0a3a6..066a30a 100644 --- a/artlib/cvi/CVIART.py +++ b/artlib/cvi/CVIART.py @@ -17,6 +17,7 @@ class CVIART(BaseART): the other criteria has failed. This means it could run slower then it would otherwise. """ + CALINSKIHARABASZ = 1 DAVIESBOULDIN = 2 SILHOUETTE = 3 @@ -50,9 +51,13 @@ def validate_params(self, params: dict): """ self.base_module.validate_params(params) - assert 'validity' in params - assert isinstance(params['validity'], int) - assert params["validity"] in [CVIART.CALINSKIHARABASZ, CVIART.DAVIESBOULDIN, CVIART.SILHOUETTE] + assert "validity" in params + assert isinstance(params["validity"], int) + assert params["validity"] in [ + CVIART.CALINSKIHARABASZ, + CVIART.DAVIESBOULDIN, + CVIART.SILHOUETTE, + ] def prepare_data(self, X: np.ndarray) -> np.ndarray: """ @@ -104,7 +109,6 @@ def labels_(self): def labels_(self, new_labels_): self.base_module.labels_ = new_labels_ - def CVI_match(self, x, w, c_, params, extra, cache): """ Evaluate the cluster validity index (CVI) for a match. @@ -133,11 +137,11 @@ def CVI_match(self, x, w, c_, params, extra, cache): if len(self.W) < 2: return True - if extra['validity'] == self.CALINSKIHARABASZ: + if extra["validity"] == self.CALINSKIHARABASZ: valid_func = metrics.calinski_harabasz_score - elif extra['validity'] == self.DAVIESBOULDIN: + elif extra["validity"] == self.DAVIESBOULDIN: valid_func = metrics.davies_bouldin_score - elif extra['validity'] == self.SILHOUETTE: + elif extra["validity"] == self.SILHOUETTE: valid_func = metrics.silhouette_score else: raise ValueError(f"Invalid Validity Parameter: {extra['validity']}") @@ -146,13 +150,18 @@ def CVI_match(self, x, w, c_, params, extra, cache): new_labels = np.copy(self.labels_) new_labels[extra["index"]] = c_ new_VI = valid_func(self.data, new_labels) - if extra['validity'] != self.DAVIESBOULDIN: + if extra["validity"] != self.DAVIESBOULDIN: 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: + 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. @@ -175,7 +184,7 @@ def _match_tracking(self, cache: dict, epsilon: float, params: dict, method: Lit """ M = cache["match_criterion"] if method == "MT+": - self.base_module.params["rho"] = M+epsilon + self.base_module.params["rho"] = M + epsilon return True elif method == "MT-": self.base_module.params["rho"] = M - epsilon @@ -197,8 +206,15 @@ def _set_params(self, new_params): def _deep_copy_params(self) -> dict: return deepcopy(self.base_module.params) - 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): + 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. @@ -225,19 +241,48 @@ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, match_reset_func: O self.is_fitted_ = True self.W: list[np.ndarray] = [] - self.labels_ = np.zeros((X.shape[0], ), dtype=int) + self.labels_ = np.zeros((X.shape[0],), dtype=int) for _ in range(max_iter): for index, x in enumerate(X): self.pre_step_fit(X) if match_reset_func is None: - cvi_match_reset_func = lambda i, w, cluster_a, params, cache: self.CVI_match(i, w, cluster_a, params, {"index": index, "validity":self.params["validity"]}, cache) + cvi_match_reset_func = ( + lambda i, w, cluster_a, params, cache: self.CVI_match( + i, + w, + cluster_a, + params, + { + "index": index, + "validity": self.params["validity"], + }, + cache, + ) + ) else: - cvi_match_reset_func = lambda i, w, cluster_a, params, cache: (match_reset_func(i,w,cluster_a,params,cache) and self.CVI_match(i, w, cluster_a, params, {"index": index, "validity":self.params["validity"]}, cache)) - c = self.base_module.step_fit(x, match_reset_func=cvi_match_reset_func, match_reset_method=match_reset_method, epsilon=epsilon) + cvi_match_reset_func = lambda i, w, cluster_a, params, cache: ( + match_reset_func(i, w, cluster_a, params, cache) + and self.CVI_match( + i, + w, + cluster_a, + params, + { + "index": index, + "validity": self.params["validity"], + }, + cache, + ) + ) + c = self.base_module.step_fit( + x, + match_reset_func=cvi_match_reset_func, + match_reset_method=match_reset_method, + epsilon=epsilon, + ) self.labels_[index] = c self.post_step_fit(X) - def pre_step_fit(self, X: np.ndarray): """ Preprocessing step before fitting each sample. @@ -262,7 +307,13 @@ def post_step_fit(self, X: np.ndarray): """ 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: + 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. @@ -329,5 +380,4 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): Width of boundary line, by default 1. """ - return self.base_module.plot_cluster_bounds(ax, colors,linewidth) - + return self.base_module.plot_cluster_bounds(ax, colors, linewidth) diff --git a/artlib/cvi/__init__.py b/artlib/cvi/__init__.py index 3fded22..c6aa5d0 100644 --- a/artlib/cvi/__init__.py +++ b/artlib/cvi/__init__.py @@ -10,4 +10,4 @@ `Cluster validity indices `_ -""" \ No newline at end of file +""" diff --git a/artlib/cvi/iCVIFuzzyArt.py b/artlib/cvi/iCVIFuzzyArt.py index 2252e08..a0c715d 100644 --- a/artlib/cvi/iCVIFuzzyArt.py +++ b/artlib/cvi/iCVIFuzzyArt.py @@ -13,12 +13,18 @@ class iCVIFuzzyART(FuzzyART): - """iCVI Fuzzy Art For Clustering + """iCVI Fuzzy Art For Clustering""" - """ CALINSKIHARABASZ = 1 - def __init__(self, rho: float, alpha: float, beta: float, validity: int, offline: bool = True): + def __init__( + self, + rho: float, + alpha: float, + beta: float, + validity: int, + offline: bool = True, + ): """ Initialize the iCVIFuzzyART model. @@ -37,11 +43,14 @@ def __init__(self, rho: float, alpha: float, beta: float, validity: int, offline """ super().__init__(rho, alpha, beta) - self.params['validity'] = validity # Currently not used. Waiting for more algorithms. + 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) - + 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): """ @@ -71,11 +80,19 @@ def iCVI_match(self, x, w, c_, params, cache): else: new = self.iCVI.add_sample(x, c_) # Eventually this should be an icvi function that you pass the params, and it handles if this is true or false. - return new['criterion_value'] > self.iCVI.criterion_value + return new["criterion_value"] > self.iCVI.criterion_value # return self.iCVI.evalLabel(x, c_) This except pass params instead. # 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): + 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. @@ -101,7 +118,7 @@ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, match_reset_func: O self.is_fitted_ = True self.W: list[np.ndarray] = [] - self.labels_ = np.zeros((X.shape[0], ), dtype=int) + self.labels_ = np.zeros((X.shape[0],), dtype=int) self.iCVI = iCVI_CH(X[0]) @@ -114,10 +131,23 @@ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, match_reset_func: O self.pre_step_fit(X) self.index = i if match_reset_func is None: - c = self.step_fit(x, match_reset_func=self.iCVI_match, match_reset_method=match_reset_method, epsilon=epsilon) + c = self.step_fit( + x, + match_reset_func=self.iCVI_match, + match_reset_method=match_reset_method, + epsilon=epsilon, + ) else: - match_reset_func_ = lambda x, w, c_, params, cache: (match_reset_func(x, w, c_, params, cache) & self.iCVI_match(x, w, c_, params, cache)) - c = self.step_fit(x, match_reset_func=match_reset_func_, match_reset_method=match_reset_method, epsilon=epsilon) + match_reset_func_ = lambda x, w, c_, params, cache: ( + match_reset_func(x, w, c_, params, cache) + & self.iCVI_match(x, w, c_, params, cache) + ) + c = self.step_fit( + x, + match_reset_func=match_reset_func_, + match_reset_method=match_reset_method, + epsilon=epsilon, + ) if self.offline: params = self.iCVI.switch_label(x, self.labels_[i], c) diff --git a/artlib/cvi/iCVIs/CalinkskiHarabasz.py b/artlib/cvi/iCVIs/CalinkskiHarabasz.py index 1021f9c..20029c7 100644 --- a/artlib/cvi/iCVIs/CalinkskiHarabasz.py +++ b/artlib/cvi/iCVIs/CalinkskiHarabasz.py @@ -12,7 +12,9 @@ import numpy as np -def delta_add_sample_to_average(average: float, sample: float, total_samples: int) -> float: +def delta_add_sample_to_average( + average: float, sample: float, total_samples: int +) -> float: """ Calculate the new average if a sample is added. @@ -34,7 +36,9 @@ def delta_add_sample_to_average(average: float, sample: float, total_samples: in return (sample - average) / total_samples -def delta_remove_sample_from_average(average: float, sample: float, total_samples: int) -> float: +def delta_remove_sample_from_average( + average: float, sample: float, total_samples: int +) -> float: """ Calculate the new average if a sample is removed. @@ -56,7 +60,7 @@ def delta_remove_sample_from_average(average: float, sample: float, total_sample return (average - sample) / (total_samples - 1) -class iCVI_CH(): +class iCVI_CH: """Implementation of the Calinski Harabasz Validity Index in incremental form. Expanded implementation of the incremental version of the Calinski Harabasz Cluster Validity Index. @@ -89,8 +93,8 @@ def __init__(self, x: np.ndarray) -> None: self.dim = x.shape[0] # Dimension of the input data self.n_samples: int = 0 # number of samples encountered self.mu = np.array([]) # geometric mean of the data - self.CD = {} # Dict for each cluster label containing n,v,CP, and G - self.WGSS = 0 # within group sum of squares + self.CD = {} # Dict for each cluster label containing n,v,CP, and G + self.WGSS = 0 # within group sum of squares self.criterion_value = 0 # calcualted CH index def add_sample(self, x: np.ndarray, label: int) -> dict: @@ -110,62 +114,73 @@ def add_sample(self, x: np.ndarray, label: int) -> dict: A dictionary containing the updated values after the sample is added. """ - newP = {'x': x, 'label': label} # New Parameters - newP['n_samples'] = self.n_samples + 1 + newP = {"x": x, "label": label} # New Parameters + newP["n_samples"] = self.n_samples + 1 if self.mu.size == 0: # mu will be size 0 if no samples in dataset. - newP['mu'] = x + newP["mu"] = x else: - newP['mu'] = self.mu + delta_add_sample_to_average(self.mu, x, newP['n_samples']) + newP["mu"] = self.mu + delta_add_sample_to_average( + self.mu, x, newP["n_samples"] + ) CD = {} - newP['CD'] = CD + newP["CD"] = CD SEP = [] # separation between each cluster and the mean of the data if label not in self.CD: n_clusters = len(self.CD) + 1 - CD['n'] = 1 - CD['v'] = x - CD['CP'] = 0 - CD['G'] = np.zeros(self.dim) - newP['CP_diff'] = 0 - diff = x - newP['mu'] - SEP.append(sum(diff ** 2)) # Handle SEP for new clusters now instead of later. + CD["n"] = 1 + CD["v"] = x + CD["CP"] = 0 + CD["G"] = np.zeros(self.dim) + newP["CP_diff"] = 0 + diff = x - newP["mu"] + SEP.append( + sum(diff**2) + ) # Handle SEP for new clusters now instead of later. else: n_clusters = len(self.CD) Data = self.CD[label] - CD['n'] = Data['n'] + 1 + CD["n"] = Data["n"] + 1 # The paper defines deltaV = Vold - Vnew, so I need to switch this sign. Consider changing functions to do this. - deltaV = -1 * delta_add_sample_to_average(Data['v'], x, CD['n']) - CD['v'] = Data['v'] - deltaV # Vnew = Vold - deltaV - diff_x_v = x - CD['v'] - - newP['CP_diff'] = (diff_x_v @ diff_x_v.T) + (CD['n'] - 1) * (deltaV @ deltaV.T) + 2 * (deltaV @ Data['G']) - CD['CP'] = Data['CP'] + newP['CP_diff'] - CD['G'] = Data['G'] + diff_x_v + (CD['n'] - 1) * deltaV + deltaV = -1 * delta_add_sample_to_average(Data["v"], x, CD["n"]) + CD["v"] = Data["v"] - deltaV # Vnew = Vold - deltaV + diff_x_v = x - CD["v"] + + newP["CP_diff"] = ( + (diff_x_v @ diff_x_v.T) + + (CD["n"] - 1) * (deltaV @ deltaV.T) + + 2 * (deltaV @ Data["G"]) + ) + CD["CP"] = Data["CP"] + newP["CP_diff"] + CD["G"] = Data["G"] + diff_x_v + (CD["n"] - 1) * deltaV if n_clusters < 2: - newP['criterion_value'] = 0 + newP["criterion_value"] = 0 else: for i in self.CD: # A new label won't be processed, but handled earlier if i == label: - n = CD['n'] - v = CD['v'] + n = CD["n"] + v = CD["v"] else: - n = self.CD[i]['n'] - v = self.CD[i]['v'] - diff = v - newP['mu'] - SEP.append(n * sum(diff ** 2)) + n = self.CD[i]["n"] + v = self.CD[i]["v"] + diff = v - newP["mu"] + SEP.append(n * sum(diff**2)) - WGSS = self.WGSS + newP['CP_diff'] + WGSS = self.WGSS + newP["CP_diff"] BGSS = sum(SEP) # between-group sum of squares - if WGSS == 0: # this can be 0 if all samples in different clusters, which is a divide by 0 error. - newP['criterion_value'] = 0 + if ( + WGSS == 0 + ): # this can be 0 if all samples in different clusters, which is a divide by 0 error. + newP["criterion_value"] = 0 else: - newP['criterion_value'] = (BGSS / WGSS) * (newP['n_samples'] - n_clusters) / (n_clusters - 1) + newP["criterion_value"] = ( + (BGSS / WGSS) * (newP["n_samples"] - n_clusters) / (n_clusters - 1) + ) return newP 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. @@ -178,14 +193,14 @@ def update(self, params: dict) -> None: Dictionary containing the updated parameters to be applied. """ - self.n_samples = params['n_samples'] - self.mu = params['mu'] - self.criterion_value = params['criterion_value'] - self.CD[params['label']] = params['CD'] - self.WGSS += params['CP_diff'] - if 'label2' in params: - self.CD[params['label2']] = params['CD2'] - self.WGSS += params['CP_diff2'] + self.n_samples = params["n_samples"] + self.mu = params["mu"] + self.criterion_value = params["criterion_value"] + self.CD[params["label"]] = params["CD"] + self.WGSS += params["CP_diff"] + if "label2" in params: + self.CD[params["label2"]] = params["CD2"] + self.WGSS += params["CP_diff2"] def switch_label(self, x: np.ndarray, label_old: int, label_new: int) -> dict: """ @@ -214,64 +229,80 @@ def switch_label(self, x: np.ndarray, label_old: int, label_new: int) -> dict: """ if label_new == label_old: - return {'n_samples': self.n_samples, - 'mu': self.mu, - 'criterion_value': self.criterion_value, - 'label': label_old, - 'CP_diff': 0, - 'CD': { - 'n': self.CD[label_old]['n'], - 'v': self.CD[label_old]['v'], - 'CP': self.CD[label_old]['CP'], - 'G': self.CD[label_old]['G']} - } - if self.CD[label_old]['n'] <= 1: + return { + "n_samples": self.n_samples, + "mu": self.mu, + "criterion_value": self.criterion_value, + "label": label_old, + "CP_diff": 0, + "CD": { + "n": self.CD[label_old]["n"], + "v": self.CD[label_old]["v"], + "CP": self.CD[label_old]["CP"], + "G": self.CD[label_old]["G"], + }, + } + if self.CD[label_old]["n"] <= 1: raise Exception("Can't remove a value from a cluster of 1") - newP = {'x': x, 'label': label_old, 'label2': label_new} # New Parameters - newP['mu'] = self.mu - newP['n_samples'] = self.n_samples + newP = { + "x": x, + "label": label_old, + "label2": label_new, + } # New Parameters + newP["mu"] = self.mu + newP["n_samples"] = self.n_samples params_remove = self.remove_sample(x, label_old) params_add = self.add_sample(x, label_new) - newP['CD'] = params_remove['CD'] - newP['CP_diff'] = params_remove['CP_diff'] - newP['CD2'] = params_add['CD'] - newP['CP_diff2'] = params_add['CP_diff'] + newP["CD"] = params_remove["CD"] + newP["CP_diff"] = params_remove["CP_diff"] + newP["CD2"] = params_add["CD"] + newP["CP_diff2"] = params_add["CP_diff"] SEP = [] # separation between each cluster and the mean of the data if label_new not in self.CD: n_clusters = len(self.CD) + 1 - diff = x - newP['mu'] - SEP.append(sum(diff ** 2)) # Handle SEP for new clusters now instead of later. + diff = x - newP["mu"] + SEP.append( + sum(diff**2) + ) # Handle SEP for new clusters now instead of later. else: n_clusters = len(self.CD) - if n_clusters < 2: # I don't think this can ever happen with remove label not deleting categories. - newP['criterion_value'] = 0 + if ( + n_clusters < 2 + ): # I don't think this can ever happen with remove label not deleting categories. + newP["criterion_value"] = 0 else: for i in self.CD: if i == label_old: - n = params_remove['CD']['n'] - v = params_remove['CD']['v'] + n = params_remove["CD"]["n"] + v = params_remove["CD"]["v"] elif i == label_new: - n = params_add['CD']['n'] - v = params_add['CD']['v'] + n = params_add["CD"]["n"] + v = params_add["CD"]["v"] else: - n = self.CD[i]['n'] - v = self.CD[i]['v'] - diff = v - newP['mu'] - SEP.append(n * sum(diff ** 2)) + n = self.CD[i]["n"] + v = self.CD[i]["v"] + diff = v - newP["mu"] + SEP.append(n * sum(diff**2)) - WGSS = self.WGSS + newP['CP_diff'] - WGSS += newP['CP_diff2'] + WGSS = self.WGSS + newP["CP_diff"] + WGSS += newP["CP_diff2"] BGSS = sum(SEP) # between-group sum of squares - if WGSS == 0: # this can be 0 if all samples in different clusters, which is a divide by 0 error. - newP['criterion_value'] = 0 + if ( + WGSS == 0 + ): # this can be 0 if all samples in different clusters, which is a divide by 0 error. + newP["criterion_value"] = 0 else: - newP['criterion_value'] = (BGSS / WGSS) * (newP['n_samples'] - n_clusters) / (n_clusters - 1) + newP["criterion_value"] = ( + (BGSS / WGSS) * (newP["n_samples"] - n_clusters) / (n_clusters - 1) + ) return newP - 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. + 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. @@ -289,47 +320,59 @@ def remove_sample(self, x: np.ndarray, label: int) -> dict: # This is left here """ Data = self.CD[label] - if Data['n'] <= 1: - raise Exception("Can't remove a value from a cluster of 1") # At least for now + if Data["n"] <= 1: + raise Exception( + "Can't remove a value from a cluster of 1" + ) # At least for now - newP = {'x': x, 'label': label} # New Parameters after removal - newP['mu'] = self.mu - delta_remove_sample_from_average(self.mu, x, self.n_samples) - newP['n_samples'] = self.n_samples - 1 + newP = {"x": x, "label": label} # New Parameters after removal + newP["mu"] = self.mu - delta_remove_sample_from_average( + self.mu, x, self.n_samples + ) + newP["n_samples"] = self.n_samples - 1 CD = {} - newP['CD'] = CD - CD['n'] = Data['n'] - 1 + newP["CD"] = CD + CD["n"] = Data["n"] - 1 # We need the delta v from when the sample was added, but the paper defines deltaV = Vold - Vnew, so I need to keep these signs - deltaVPrior = delta_remove_sample_from_average(Data['v'], x, Data['n']) - CD['v'] = Data['v'] + deltaVPrior # Vnew + deltaV = Vold - diff_x_vPrior = x - Data['v'] + deltaVPrior = delta_remove_sample_from_average(Data["v"], x, Data["n"]) + CD["v"] = Data["v"] + deltaVPrior # Vnew + deltaV = Vold + diff_x_vPrior = x - Data["v"] # We already subtracted 1 from n - CD['G'] = Data['G'] - (diff_x_vPrior + (CD['n']) * deltaVPrior) + CD["G"] = Data["G"] - (diff_x_vPrior + (CD["n"]) * deltaVPrior) # CD's G is the old G, which is what we added before. - newP['CP_diff'] = -1 * ((diff_x_vPrior @ diff_x_vPrior.T) + (CD['n']) * (deltaVPrior @ deltaVPrior.T) + 2 * (deltaVPrior @ CD['G'])) - CD['CP'] = Data['CP'] + newP['CP_diff'] + newP["CP_diff"] = -1 * ( + (diff_x_vPrior @ diff_x_vPrior.T) + + (CD["n"]) * (deltaVPrior @ deltaVPrior.T) + + 2 * (deltaVPrior @ CD["G"]) + ) + CD["CP"] = Data["CP"] + newP["CP_diff"] n_clusters = len(self.CD) # Move this up if deleting clusters are allowed. if n_clusters < 2: - newP['criterion_value'] = 0 + newP["criterion_value"] = 0 else: SEP = [] # separation between each cluster and the mean of the data for i in self.CD: if i == label: - n = CD['n'] - v = CD['v'] + n = CD["n"] + v = CD["v"] else: - n = self.CD[i]['n'] - v = self.CD[i]['v'] - diff = v - newP['mu'] - SEP.append(n * sum(diff ** 2)) + n = self.CD[i]["n"] + v = self.CD[i]["v"] + diff = v - newP["mu"] + SEP.append(n * sum(diff**2)) - WGSS = self.WGSS + newP['CP_diff'] + WGSS = self.WGSS + newP["CP_diff"] BGSS = sum(SEP) # between-group sum of squares - if WGSS == 0: # this can be 0 if all samples in different clusters, which is a divide by 0 error. - newP['criterion_value'] = 0 + if ( + WGSS == 0 + ): # this can be 0 if all samples in different clusters, which is a divide by 0 error. + newP["criterion_value"] = 0 else: - newP['criterion_value'] = (BGSS / WGSS) * (newP['n_samples'] - n_clusters) / (n_clusters - 1) + newP["criterion_value"] = ( + (BGSS / WGSS) * (newP["n_samples"] - n_clusters) / (n_clusters - 1) + ) return newP diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index fc3afe7..33cd84a 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -20,6 +20,7 @@ class ART1(BaseART): ART1 is intended for binary data clustering only. """ + def __init__(self, rho: float, beta: float, L: float): """ Initialize the ART1 model. @@ -34,11 +35,7 @@ def __init__(self, rho: float, beta: float, L: float): Uncommitted node bias, a value greater than or equal to 1. """ - params = { - "rho": rho, - "beta": beta, - "L": L - } + params = {"rho": rho, "beta": beta, "L": L} super().__init__(params) @staticmethod @@ -55,9 +52,9 @@ def validate_params(params: dict): assert "rho" in params assert "beta" in params assert "L" in params - assert 1. >= params["rho"] >= 0. - assert 1. >= params["beta"] >= 0. - assert params["L"] >= 1. + assert 1.0 >= params["rho"] >= 0.0 + assert 1.0 >= params["beta"] >= 0.0 + assert params["L"] >= 1.0 assert isinstance(params["rho"], float) assert isinstance(params["beta"], float) assert isinstance(params["L"], float) @@ -75,7 +72,9 @@ def validate_data(self, X: np.ndarray): assert np.array_equal(X, X.astype(bool)), "ART1 only supports binary data" self.check_dimensions(X) - def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: + def category_choice( + self, i: np.ndarray, w: np.ndarray, params: dict + ) -> tuple[float, Optional[dict]]: """ Get the activation of the cluster. @@ -96,10 +95,16 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f Cache used for later processing. """ - w_bu = w[:self.dim_] + w_bu = w[: self.dim_] return float(np.dot(i, w_bu)), None - def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: + 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. @@ -122,11 +127,16 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt Cache used for later processing. """ - w_td = w[self.dim_:] + w_td = w[self.dim_ :] return l1norm(np.logical_and(i, w_td)) / l1norm(i), cache - - def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: + def update( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + ) -> np.ndarray: """ Get the updated cluster weight. @@ -147,13 +157,12 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic Updated cluster weight. """ - w_td = w[self.dim_:] + w_td = w[self.dim_ :] w_td_new = np.logical_and(i, w_td) - w_bu_new = (params["L"] / (params["L"] - 1 + l1norm(w_td_new)))*w_td_new + w_bu_new = (params["L"] / (params["L"] - 1 + l1norm(w_td_new))) * w_td_new return np.concatenate([w_bu_new, w_td_new]) - def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ Generate a new cluster weight. @@ -172,7 +181,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ w_td_new = i - w_bu_new = (params["L"] / (params["L"] - 1 + self.dim_))*w_td_new + w_bu_new = (params["L"] / (params["L"] - 1 + self.dim_)) * w_td_new return np.concatenate([w_bu_new, w_td_new]) def get_cluster_centers(self) -> List[np.ndarray]: @@ -185,4 +194,4 @@ def get_cluster_centers(self) -> List[np.ndarray]: Cluster centroids. """ - return [w[self.dim_:] for w in self.W] + return [w[self.dim_ :] for w in self.W] diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index cbd2fff..2b0435f 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -32,6 +32,7 @@ class ART2A(BaseART): analog data. This method is implemented for historical purposes and is not recommended for use. """ + def __init__(self, rho: float, alpha: float, beta: float): """ Initialize the ART2-A model. @@ -46,7 +47,9 @@ def __init__(self, rho: float, alpha: float, 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") + warn( + "Do Not Use ART2. It does not work. This module is provided for completeness only" + ) params = { "rho": rho, @@ -69,13 +72,13 @@ def validate_params(params: dict): assert "rho" in params assert "alpha" in params assert "beta" in params - assert 1. >= params["rho"] >= 0. - assert 1. >= params["alpha"] >= 0. - assert 1. >= params["beta"] >= 0. + assert 1.0 >= params["rho"] >= 0.0 + assert 1.0 >= params["alpha"] >= 0.0 + assert 1.0 >= params["beta"] >= 0.0 assert isinstance(params["rho"], float) assert isinstance(params["alpha"], float) assert isinstance(params["beta"], float) - + def check_dimensions(self, X: np.ndarray): """ Check that the data has the correct dimensions. @@ -91,8 +94,10 @@ def check_dimensions(self, X: np.ndarray): assert self.params["alpha"] <= 1 / np.sqrt(self.dim_) else: assert X.shape[1] == self.dim_ - - def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: + + def category_choice( + self, i: np.ndarray, w: np.ndarray, params: dict + ) -> tuple[float, Optional[dict]]: """ Get the activation of the cluster. @@ -117,7 +122,13 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f cache = {"activation": activation} return activation, cache - def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: + 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. @@ -144,15 +155,20 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt raise ValueError("No cache provided") # TODO: make this more efficient M = cache["activation"] - M_u = params["alpha"]*np.sum(i) + M_u = params["alpha"] * np.sum(i) # suppress if uncommitted activation is higher if M < M_u: - return -1., cache + return -1.0, cache else: return M, cache - - def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: + def update( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + ) -> np.ndarray: """ Get the updated cluster weight. @@ -173,7 +189,7 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic Updated cluster weight. """ - return params["beta"]*i + (1-params["beta"])*w + return params["beta"] * i + (1 - params["beta"]) * w def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ @@ -204,4 +220,4 @@ def get_cluster_centers(self) -> List[np.ndarray]: Cluster centroids. """ - return self.W \ No newline at end of file + return self.W diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index 76d4385..7121844 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -20,7 +20,9 @@ class BayesianART(BaseART): in that it allows arbitrary rotation of the hyper-ellipsoid. """ + pi2 = np.pi * 2 + def __init__(self, rho: float, cov_init: np.ndarray): """ Initialize the Bayesian ART model. @@ -73,7 +75,9 @@ def check_dimensions(self, X: np.ndarray): else: assert X.shape[1] == self.dim_ - def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: + def category_choice( + self, i: np.ndarray, w: np.ndarray, params: dict + ) -> tuple[float, Optional[dict]]: """ Get the activation of the cluster. @@ -94,27 +98,32 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f Cache used for later processing. """ - mean = w[:self.dim_] - cov = w[self.dim_:-1].reshape((self.dim_, self.dim_)) + mean = w[: self.dim_] + cov = w[self.dim_ : -1].reshape((self.dim_, self.dim_)) n = w[-1] dist = mean - i - exp_dist_cov_dist = np.exp(-0.5 * np.matmul(dist.T, np.matmul(np.linalg.inv(cov), dist))) + exp_dist_cov_dist = np.exp( + -0.5 * np.matmul(dist.T, np.matmul(np.linalg.inv(cov), dist)) + ) det_cov = np.linalg.det(cov) - p_i_cj = exp_dist_cov_dist / np.sqrt((self.pi2 ** self.dim_) * det_cov) + p_i_cj = exp_dist_cov_dist / np.sqrt((self.pi2**self.dim_) * det_cov) p_cj = n / np.sum(w_[-1] for w_ in self.W) activation = p_i_cj * p_cj - cache = { - "cov": cov, - "det_cov": det_cov - } + cache = {"cov": cov, "det_cov": det_cov} return activation, cache - def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: + 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. @@ -140,14 +149,21 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt # the original paper uses the det(cov_old) for match criterion # however, it makes logical sense to use the new_cov and results are improved when doing so new_w = self.update(i, w, params, cache) - new_cov = new_w[self.dim_:-1].reshape((self.dim_, self.dim_)) + new_cov = new_w[self.dim_ : -1].reshape((self.dim_, self.dim_)) cache["new_w"] = new_w # if cache is None: # raise ValueError("No cache provided") # return cache["det_cov"] return np.linalg.det(new_cov), 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]: + 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. @@ -173,7 +189,9 @@ def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: """ M, cache = self.match_criterion(i, w, params=params, cache=cache) - M_bin = op(params["rho"], M) # note that this is backwards from the base ART: rho >= M + M_bin = op( + params["rho"], M + ) # note that this is backwards from the base ART: rho >= M if cache is None: cache = dict() cache["match_criterion"] = M @@ -181,7 +199,13 @@ 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: + 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. @@ -221,7 +245,13 @@ def _match_tracking(self, cache: dict, epsilon: float, params: dict, method: Lit else: raise ValueError(f"Invalid Match Tracking Method: {method}") - def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: + def update( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + ) -> np.ndarray: """ Get the updated cluster weight. @@ -248,15 +278,15 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic if "new_w" in cache: return cache["new_w"] - mean = w[:self.dim_] - cov = w[self.dim_:-1].reshape((self.dim_, self.dim_)) + mean = w[: self.dim_] + cov = w[self.dim_ : -1].reshape((self.dim_, self.dim_)) n = w[-1] - n_new = n+1 - mean_new = (1-(1/n_new))*mean + (1/n_new)*i + n_new = n + 1 + mean_new = (1 - (1 / n_new)) * mean + (1 / n_new) * i - i_mean_dist = i-mean_new - i_mean_dist_2 = i_mean_dist.reshape((-1, 1))*i_mean_dist.reshape((1, -1)) + i_mean_dist = i - mean_new + i_mean_dist_2 = i_mean_dist.reshape((-1, 1)) * i_mean_dist.reshape((1, -1)) cov_new = (n / n_new) * cov + (1 / n_new) * i_mean_dist_2 @@ -291,7 +321,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: Cluster centroids. """ - return [w[:self.dim_] for w in self.W] + return [w[: self.dim_] for w in self.W] def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ @@ -308,7 +338,7 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ for w, col in zip(self.W, colors): - mean = w[:self.dim_] - cov = w[self.dim_:-1].reshape((self.dim_, self.dim_)) + mean = w[: self.dim_] + cov = w[self.dim_ : -1].reshape((self.dim_, self.dim_)) # sigma = np.sqrt(np.diag(cov)) plot_gaussian_contours_covariance(ax, mean, cov, col, linewidth=linewidth) diff --git a/artlib/elementary/EllipsoidART.py b/artlib/elementary/EllipsoidART.py index e0ada65..b45b392 100644 --- a/artlib/elementary/EllipsoidART.py +++ b/artlib/elementary/EllipsoidART.py @@ -15,6 +15,7 @@ from artlib.common.BaseART import BaseART from artlib.common.utils import l2norm2 + class EllipsoidART(BaseART): """Ellipsoid ART for Clustering @@ -26,6 +27,7 @@ class EllipsoidART(BaseART): sample will determine the orientation of the principal axes. """ + def __init__(self, rho: float, alpha: float, beta: float, mu: float, r_hat: float): """ Initialize the Ellipsoid ART model. @@ -69,10 +71,10 @@ def validate_params(params: dict): assert "beta" in params assert "mu" in params assert "r_hat" in params - assert 1.0 >= params["rho"] >= 0. - assert 1.0 >= params["alpha"] >= 0. - assert 1.0 >= params["beta"] >= 0. - assert 1.0 >= params["mu"] > 0. + assert 1.0 >= params["rho"] >= 0.0 + assert 1.0 >= params["alpha"] >= 0.0 + assert 1.0 >= params["beta"] >= 0.0 + assert 1.0 >= params["mu"] > 0.0 assert isinstance(params["rho"], float) assert isinstance(params["alpha"], float) assert isinstance(params["beta"], float) @@ -80,7 +82,12 @@ 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: dict) -> float: + 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. @@ -101,16 +108,20 @@ def category_distance(i: np.ndarray, centroid: np.ndarray, major_axis: np.ndarra Distance between the sample and the cluster centroid. """ - ic_dist = (i - centroid) + ic_dist = i - centroid if major_axis.any(): - return (1. / params["mu"]) * np.sqrt( - l2norm2(ic_dist) - (1 - params["mu"] * params["mu"]) * (np.matmul(major_axis, ic_dist) ** 2) + return (1.0 / params["mu"]) * np.sqrt( + l2norm2(ic_dist) + - (1 - params["mu"] * params["mu"]) + * (np.matmul(major_axis, ic_dist) ** 2) ) else: return np.sqrt(l2norm2(ic_dist)) - def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: + def category_choice( + self, i: np.ndarray, w: np.ndarray, params: dict + ) -> tuple[float, Optional[dict]]: """ Get the activation of the cluster. @@ -131,19 +142,24 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f Cache used for later processing. """ - centroid = w[:self.dim_] - major_axis = w[self.dim_:-1] + centroid = w[: self.dim_] + major_axis = w[self.dim_ : -1] radius = w[-1] dist = self.category_distance(i, centroid, major_axis, params) - cache = { - "dist": dist - } - return (params["r_hat"] - radius - max(radius, dist)) / (params["r_hat"] - 2*radius + params["alpha"]), cache - - - def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: + cache = {"dist": dist} + return (params["r_hat"] - radius - max(radius, dist)) / ( + params["r_hat"] - 2 * radius + params["alpha"] + ), cache + + 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. @@ -171,10 +187,15 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt raise ValueError("No cache provided") dist = cache["dist"] - return 1 - (radius + max(radius, dist))/params["r_hat"], cache - + return 1 - (radius + max(radius, dist)) / params["r_hat"], cache - def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: + def update( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + ) -> np.ndarray: """ Get the updated cluster weight. @@ -195,19 +216,21 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic Updated cluster weight. """ - centroid = w[:self.dim_] - major_axis = w[self.dim_:-1] + centroid = w[: self.dim_] + major_axis = w[self.dim_ : -1] radius = w[-1] if cache is None: raise ValueError("No cache provided") dist = cache["dist"] - radius_new = radius + (params["beta"]/2)*(max(radius, dist) - radius) - centroid_new = centroid + (params["beta"]/2)*(i-centroid)*(1-(min(radius, dist)/dist)) + radius_new = radius + (params["beta"] / 2) * (max(radius, dist) - radius) + centroid_new = centroid + (params["beta"] / 2) * (i - centroid) * ( + 1 - (min(radius, dist) / dist) + ) - if not radius == 0.: - major_axis_new = (i-centroid_new)/np.sqrt(l2norm2((i-centroid_new))) + if not radius == 0.0: + major_axis_new = (i - centroid_new) / np.sqrt(l2norm2((i - centroid_new))) else: major_axis_new = major_axis @@ -230,7 +253,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: New cluster weight. """ - return np.concatenate([i, np.zeros_like(i), [0.]]) + return np.concatenate([i, np.zeros_like(i), [0.0]]) def get_2d_ellipsoids(self) -> list[tuple]: """ @@ -245,12 +268,12 @@ def get_2d_ellipsoids(self) -> list[tuple]: ellipsoids = [] for w in self.W: centroid = w[:2] - major_axis = w[self.dim_:-1] + major_axis = w[self.dim_ : -1] radius = w[-1] angle = np.rad2deg(np.arctan2(major_axis[1], major_axis[0])) - height = radius*2 - width = self.params["mu"]*height + height = radius * 2 + width = self.params["mu"] * height ellipsoids.append((centroid, width, height, angle)) @@ -266,7 +289,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: Cluster centroids. """ - return [w[:self.dim_] for w in self.W] + return [w[: self.dim_] for w in self.W] def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ @@ -293,7 +316,6 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): angle=angle, linewidth=linewidth, edgecolor=col, - facecolor='none' + facecolor="none", ) ax.add_patch(ellip) - diff --git a/artlib/elementary/FuzzyART.py b/artlib/elementary/FuzzyART.py index fe24eb6..140b3d8 100644 --- a/artlib/elementary/FuzzyART.py +++ b/artlib/elementary/FuzzyART.py @@ -7,10 +7,18 @@ from typing import Optional, Iterable, List from matplotlib.axes import Axes from artlib.common.BaseART import BaseART -from artlib.common.utils import normalize, compliment_code, l1norm, fuzzy_and, de_compliment_code - - -def get_bounding_box(w: np.ndarray, n: Optional[int] = None) -> tuple[list[int], list[int]]: +from artlib.common.utils import ( + normalize, + compliment_code, + l1norm, + fuzzy_and, + de_compliment_code, +) + + +def get_bounding_box( + w: np.ndarray, n: Optional[int] = None +) -> tuple[list[int], list[int]]: """ Extract the bounding boxes from a FuzzyART weight. @@ -37,10 +45,10 @@ def get_bounding_box(w: np.ndarray, n: Optional[int] = None) -> tuple[list[int], for i in range(n): a_min = w[i] - a_max = 1-w[i+n] + a_max = 1 - w[i + n] ref_point.append(a_min) - widths.append(a_max-a_min) + widths.append(a_max - a_min) return ref_point, widths @@ -53,6 +61,7 @@ class FuzzyART(BaseART): Neural Networks, 4, 759 – 771. doi:10.1016/0893-6080(91)90056-B. Fuzzy ART is a hyper-box based clustering method. """ + def __init__(self, rho: float, alpha: float, beta: float): """ Initialize the Fuzzy ART model. @@ -125,9 +134,9 @@ def validate_params(params: dict): assert "rho" in params assert "alpha" in params assert "beta" in params - assert 1.0 >= params["rho"] >= 0. - assert params["alpha"] >= 0. - assert 1.0 >= params["beta"] > 0. + assert 1.0 >= params["rho"] >= 0.0 + assert params["alpha"] >= 0.0 + assert 1.0 >= params["beta"] > 0.0 assert isinstance(params["rho"], float) assert isinstance(params["alpha"], float) assert isinstance(params["beta"], float) @@ -144,7 +153,7 @@ def check_dimensions(self, X: np.ndarray): """ if not hasattr(self, "dim_"): self.dim_ = X.shape[1] - self.dim_original = int(self.dim_//2) + self.dim_original = int(self.dim_ // 2) else: assert X.shape[1] == self.dim_ @@ -161,10 +170,14 @@ def validate_data(self, X: np.ndarray): assert X.shape[1] % 2 == 0, "Data has not been compliment coded" assert np.all(X >= 0), "Data has not been normalized" assert np.all(X <= 1.0), "Data has not been normalized" - assert np.all(abs(np.sum(X, axis=1) - float(X.shape[1]/2)) <= 0.01), "Data has not been compliment coded" + assert np.all( + abs(np.sum(X, axis=1) - float(X.shape[1] / 2)) <= 0.01 + ), "Data has not been compliment coded" self.check_dimensions(X) - def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: + def category_choice( + self, i: np.ndarray, w: np.ndarray, params: dict + ) -> tuple[float, Optional[dict]]: """ Get the activation of the cluster. @@ -187,7 +200,13 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f """ 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]: + 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. @@ -212,8 +231,13 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt """ return l1norm(fuzzy_and(i, w)) / self.dim_original, cache - - def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: + def update( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + ) -> np.ndarray: """ Get the updated cluster weight. @@ -258,7 +282,9 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ return i - def get_bounding_boxes(self, n: Optional[int] = None) -> List[tuple[list[int], list[int]]]: + def get_bounding_boxes( + self, n: Optional[int] = None + ) -> List[tuple[list[int], list[int]]]: """ Get the bounding boxes for each cluster. @@ -285,7 +311,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: Cluster centroids. """ - return [self.restore_data(w.reshape((1,-1))).reshape((-1,)) for w in self.W] + return [self.restore_data(w.reshape((1, -1))).reshape((-1,)) for w in self.W] def shrink_clusters(self, shrink_ratio: float = 0.1): """ @@ -303,17 +329,16 @@ def shrink_clusters(self, shrink_ratio: float = 0.1): """ new_W = [] - dim = len(self.W[0])//2 + dim = len(self.W[0]) // 2 for w in self.W: new_w = np.copy(w) - widths = (1-w[dim:]) - w[:dim] - new_w[:dim] += widths*shrink_ratio - new_w[dim:] += widths*shrink_ratio + widths = (1 - w[dim:]) - w[:dim] + new_w[:dim] += widths * shrink_ratio + new_w[dim:] += widths * shrink_ratio new_W.append(new_w) self.W = new_W return self - def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ Visualize the bounds of each cluster. @@ -329,6 +354,7 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ from matplotlib.patches import Rectangle + bboxes = self.get_bounding_boxes(n=2) for bbox, col in zip(bboxes, colors): rect = Rectangle( @@ -337,6 +363,6 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): bbox[1][1], linewidth=linewidth, edgecolor=col, - facecolor='none' + facecolor="none", ) ax.add_patch(rect) diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index 7954731..ce2dc8a 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -23,6 +23,7 @@ class GaussianART(BaseART): It is also faster than Bayesian ART. """ + def __init__(self, rho: float, sigma_init: np.ndarray, alpha: float = 1e-10): """ Initialize the Gaussian ART model. @@ -37,14 +38,9 @@ def __init__(self, rho: float, sigma_init: np.ndarray, alpha: float = 1e-10): Small parameter to prevent division by zero errors, by default 1e-10. """ - params = { - "rho": rho, - "sigma_init": sigma_init, - "alpha": alpha - } + params = {"rho": rho, "sigma_init": sigma_init, "alpha": alpha} super().__init__(params) - @staticmethod def validate_params(params: dict): """ @@ -59,13 +55,14 @@ def validate_params(params: dict): assert "rho" in params assert "sigma_init" in params assert "alpha" in params - assert 1.0 >= params["rho"] >= 0. - assert params["alpha"] > 0. + assert 1.0 >= params["rho"] >= 0.0 + assert params["alpha"] > 0.0 assert isinstance(params["rho"], float) assert isinstance(params["sigma_init"], np.ndarray) - - def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: + def category_choice( + self, i: np.ndarray, w: np.ndarray, params: dict + ) -> tuple[float, Optional[dict]]: """ Get the activation of the cluster. @@ -86,28 +83,31 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f Cache used for later processing. """ - mean = w[:self.dim_] + mean = w[: self.dim_] # sigma = w[self.dim_:2*self.dim] - inv_sig = w[2*self.dim_:3*self.dim_] + inv_sig = w[2 * self.dim_ : 3 * self.dim_] sqrt_det_sig = w[-2] n = w[-1] - dist = mean-i + dist = mean - i exp_dist_sig_dist = np.exp(-0.5 * np.dot(dist, np.multiply(inv_sig, dist))) - cache = { - "exp_dist_sig_dist": exp_dist_sig_dist - } + cache = {"exp_dist_sig_dist": exp_dist_sig_dist} # ignore the (2*pi)^d term as that is constant - p_i_cj = exp_dist_sig_dist/(params["alpha"]+sqrt_det_sig) - p_cj = n/np.sum(w_[-1] for w_ in self.W) + p_i_cj = exp_dist_sig_dist / (params["alpha"] + sqrt_det_sig) + p_cj = n / np.sum(w_[-1] for w_ in self.W) - activation = p_i_cj*p_cj + activation = p_i_cj * p_cj return activation, cache - - def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: + 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. @@ -135,9 +135,13 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt exp_dist_sig_dist = cache["exp_dist_sig_dist"] return exp_dist_sig_dist, cache - - - def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: + def update( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + ) -> np.ndarray: """ Get the updated cluster weight. @@ -158,13 +162,16 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic Updated cluster weight. """ - mean = w[:self.dim_] - sigma = w[self.dim_:2*self.dim_] + mean = w[: self.dim_] + sigma = w[self.dim_ : 2 * self.dim_] n = w[-1] - n_new = n+1 - mean_new = (1-(1/n_new))*mean + (1/n_new)*i - sigma_new = np.sqrt((1-(1/n_new))*np.multiply(sigma, sigma) + (1/n_new)*((mean_new - i)**2)) + n_new = n + 1 + mean_new = (1 - (1 / n_new)) * mean + (1 / n_new) * i + sigma_new = np.sqrt( + (1 - (1 / n_new)) * np.multiply(sigma, sigma) + + (1 / n_new) * ((mean_new - i) ** 2) + ) sigma2 = np.multiply(sigma_new, sigma_new) inv_sig = 1 / sigma2 @@ -172,7 +179,6 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic return np.concatenate([mean_new, sigma_new, inv_sig, [det_sig], [n_new]]) - def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ Generate a new cluster weight. @@ -193,7 +199,9 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: sigma2 = np.multiply(params["sigma_init"], params["sigma_init"]) inv_sig_init = 1 / sigma2 det_sig_init = np.sqrt(np.prod(sigma2)) - return np.concatenate([i, params["sigma_init"], inv_sig_init, [det_sig_init], [1.]]) + return np.concatenate( + [i, params["sigma_init"], inv_sig_init, [det_sig_init], [1.0]] + ) def get_cluster_centers(self) -> List[np.ndarray]: """ @@ -205,8 +213,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: Cluster centroids. """ - return [w[:self.dim_] for w in self.W] - + return [w[: self.dim_] for w in self.W] def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ @@ -223,6 +230,6 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ for w, col in zip(self.W, colors): - mean = w[:self.dim_] - sigma = w[self.dim_:-1] + mean = w[: self.dim_] + sigma = w[self.dim_ : -1] plot_gaussian_contours_fading(ax, mean, sigma, col, linewidth=linewidth) diff --git a/artlib/elementary/HypersphereART.py b/artlib/elementary/HypersphereART.py index 8c2d241..ff37c9b 100644 --- a/artlib/elementary/HypersphereART.py +++ b/artlib/elementary/HypersphereART.py @@ -10,6 +10,7 @@ from artlib.common.BaseART import BaseART from artlib.common.utils import l2norm2 + class HypersphereART(BaseART): """Hypersphere ART for Clustering @@ -20,6 +21,7 @@ class HypersphereART(BaseART): Hyperpshere ART clusters data in Hyper-spheres similar to k-means with a dynamic k. """ + def __init__(self, rho: float, alpha: float, beta: float, r_hat: float): """ Initialize the Hypersphere ART model. @@ -59,16 +61,18 @@ def validate_params(params: dict): assert "alpha" in params assert "beta" in params assert "r_hat" in params - assert 1.0 >= params["rho"] >= 0. - assert params["alpha"] >= 0. - assert 1.0 >= params["beta"] >= 0. + assert 1.0 >= params["rho"] >= 0.0 + assert params["alpha"] >= 0.0 + assert 1.0 >= params["beta"] >= 0.0 assert isinstance(params["rho"], float) assert isinstance(params["alpha"], float) assert isinstance(params["beta"], float) assert isinstance(params["r_hat"], float) @staticmethod - def category_distance(i: np.ndarray, centroid: np.ndarray, radius: float, params) -> float: + def category_distance( + i: np.ndarray, centroid: np.ndarray, radius: float, params + ) -> float: """ Compute the category distance between a data sample and a centroid. @@ -89,10 +93,11 @@ def category_distance(i: np.ndarray, centroid: np.ndarray, radius: float, params Category distance. """ - return np.sqrt(l2norm2(i-centroid)) + return np.sqrt(l2norm2(i - centroid)) - - def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: + def category_choice( + self, i: np.ndarray, w: np.ndarray, params: dict + ) -> tuple[float, Optional[dict]]: """ Get the activation of the cluster. @@ -123,10 +128,17 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f "max_radius": max_radius, "i_radius": i_radius, } - return (params["r_hat"] - max_radius)/(params["r_hat"] - radius + params["alpha"]), cache - - - def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: + return (params["r_hat"] - max_radius) / ( + params["r_hat"] - radius + params["alpha"] + ), cache + + 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. @@ -154,11 +166,15 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt raise ValueError("No cache provided") max_radius = cache["max_radius"] - return 1 - (max(radius, max_radius)/params["r_hat"]), cache - + return 1 - (max(radius, max_radius) / params["r_hat"]), cache - - def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: + def update( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + ) -> np.ndarray: """ Get the updated cluster weight. @@ -186,12 +202,13 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic max_radius = cache["max_radius"] i_radius = cache["i_radius"] - radius_new = radius + (params["beta"]/2)*(max_radius-radius) - centroid_new = centroid + (params["beta"]/2)*(i-centroid)*(1-(min(radius, i_radius)/i_radius)) + radius_new = radius + (params["beta"] / 2) * (max_radius - radius) + centroid_new = centroid + (params["beta"] / 2) * (i - centroid) * ( + 1 - (min(radius, i_radius) / i_radius) + ) return np.concatenate([centroid_new, [radius_new]]) - def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ Generate a new cluster weight. @@ -209,7 +226,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: New cluster weight. """ - return np.concatenate([i, [0.]]) + return np.concatenate([i, [0.0]]) def get_cluster_centers(self) -> List[np.ndarray]: """ @@ -223,7 +240,6 @@ def get_cluster_centers(self) -> List[np.ndarray]: """ return [w[:-1] for w in self.W] - def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ Visualize the bounds of each cluster. @@ -248,12 +264,6 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): radius, linewidth=linewidth, edgecolor=col, - facecolor='none' + facecolor="none", ) ax.add_patch(circ) - - - - - - diff --git a/artlib/elementary/QuadraticNeuronART.py b/artlib/elementary/QuadraticNeuronART.py index 7b38ae9..8c4d596 100644 --- a/artlib/elementary/QuadraticNeuronART.py +++ b/artlib/elementary/QuadraticNeuronART.py @@ -26,7 +26,10 @@ class QuadraticNeuronART(BaseART): and resonance. """ - def __init__(self, rho: float, s_init: float, lr_b: float, lr_w: float, lr_s: float): + + def __init__( + self, rho: float, s_init: float, lr_b: float, lr_w: float, lr_s: float + ): """ Initialize the Quadratic Neuron ART model. @@ -69,17 +72,19 @@ def validate_params(params: dict): assert "lr_b" in params assert "lr_w" in params assert "lr_s" in params - assert 1.0 >= params["rho"] >= 0. - assert 1.0 >= params["lr_b"] > 0. - assert 1.0 >= params["lr_w"] >= 0. - assert 1.0 >= params["lr_s"] >= 0. + assert 1.0 >= params["rho"] >= 0.0 + assert 1.0 >= params["lr_b"] > 0.0 + assert 1.0 >= params["lr_w"] >= 0.0 + assert 1.0 >= params["lr_s"] >= 0.0 assert isinstance(params["rho"], float) assert isinstance(params["s_init"], float) assert isinstance(params["lr_b"], float) assert isinstance(params["lr_w"], float) assert isinstance(params["lr_s"], float) - def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: + def category_choice( + self, i: np.ndarray, w: np.ndarray, params: dict + ) -> tuple[float, Optional[dict]]: """ Get the activation of the cluster. @@ -105,8 +110,8 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f b = w[dim2:-1] s = w[-1] z = np.matmul(w_, i) - l2norm2_z_b = l2norm2(z-b) - activation = np.exp(-s*s*l2norm2_z_b) + l2norm2_z_b = l2norm2(z - b) + activation = np.exp(-s * s * l2norm2_z_b) cache = { "activation": activation, @@ -114,11 +119,17 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f "w": w_, "b": b, "s": s, - "z": z + "z": z, } return activation, cache - def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: + 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. @@ -145,7 +156,13 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt raise ValueError("No cache provided") return cache["activation"], cache - def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: + def update( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + ) -> np.ndarray: """ Get the updated cluster weight. @@ -173,15 +190,16 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic T = cache["activation"] l2norm2_z_b = cache["l2norm2_z_b"] - sst2 = 2*s*s*T + sst2 = 2 * s * s * T - b_new = b + params["lr_b"]*(sst2*(z-b)) - w_new = w_ + params["lr_w"]*(-sst2*((z-b).reshape((-1, 1))*i.reshape((1, -1)))) - s_new = s + params["lr_s"]*(-2*s*T*l2norm2_z_b) + b_new = b + params["lr_b"] * (sst2 * (z - b)) + w_new = w_ + params["lr_w"] * ( + -sst2 * ((z - b).reshape((-1, 1)) * i.reshape((1, -1))) + ) + s_new = s + params["lr_s"] * (-2 * s * T * l2norm2_z_b) return np.concatenate([w_new.flatten(), b_new, [s_new]]) - def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ Generate a new cluster weight. @@ -236,4 +254,3 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): b = w[dim2:-1] s = w[-1] plot_weight_matrix_as_ellipse(ax, s, w_, b, col) - diff --git a/artlib/elementary/__init__.py b/artlib/elementary/__init__.py index 1eebf63..df4099f 100644 --- a/artlib/elementary/__init__.py +++ b/artlib/elementary/__init__.py @@ -1,4 +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/ConvexHullART.py b/artlib/experimental/ConvexHullART.py index 17feed5..f418487 100644 --- a/artlib/experimental/ConvexHullART.py +++ b/artlib/experimental/ConvexHullART.py @@ -8,7 +8,12 @@ from artlib.experimental.merging import merge_objects -def plot_convex_polygon(vertices: np.ndarray, ax: Axes, line_color: str = 'b', line_width: float = 1.0): +def plot_convex_polygon( + vertices: np.ndarray, + ax: Axes, + line_color: str = "b", + line_width: float = 1.0, +): """ Plots a convex polygon given its vertices using Matplotlib. @@ -28,7 +33,13 @@ def plot_convex_polygon(vertices: np.ndarray, ax: Axes, line_color: str = 'b', l # Close the polygon by appending the first vertex at the end vertices = np.vstack([vertices, vertices[0]]) - ax.plot(vertices[:, 0], vertices[:, 1], linestyle='-', color=line_color, linewidth=line_width) + ax.plot( + vertices[:, 0], + vertices[:, 1], + linestyle="-", + color=line_color, + linewidth=line_width, + ) def volume_of_simplex(vertices: np.ndarray) -> float: @@ -70,6 +81,7 @@ def minimum_distance(a1: np.ndarray, a2: np.ndarray) -> 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) @@ -117,7 +129,6 @@ def line_segment_to_line_segment_distance(A1, A2, B1, B2): raise RuntimeError("INVALID DISTANCE CALCULATION") - class PseudoConvexHull: def __init__(self, points: np.ndarray): """ @@ -181,6 +192,7 @@ class ConvexHullART(BaseART): """ ConvexHull ART for Clustering """ + def __init__(self, rho: float, merge_rho: float): """ Initializes the ConvexHullART object. @@ -193,10 +205,7 @@ def __init__(self, rho: float, merge_rho: float): Merge vigilance parameter. """ - params = { - "rho": rho, - "merge_rho": merge_rho - } + params = {"rho": rho, "merge_rho": merge_rho} super().__init__(params) @staticmethod @@ -211,10 +220,12 @@ def validate_params(params: dict): """ assert "rho" in params - assert params["rho"] >= 0. + assert params["rho"] >= 0.0 assert isinstance(params["rho"], float) - def category_choice(self, i: np.ndarray, w: HullTypes, params: dict) -> tuple[float, Optional[dict]]: + def category_choice( + self, i: np.ndarray, w: HullTypes, params: dict + ) -> tuple[float, Optional[dict]]: """ Get the activation of the cluster. @@ -236,23 +247,31 @@ def category_choice(self, i: np.ndarray, w: HullTypes, params: dict) -> tuple[fl """ if isinstance(w, PseudoConvexHull): - d = minimum_distance(i.reshape((1,-1)), w.points) - activation = 1. - d**len(i) + d = minimum_distance(i.reshape((1, -1)), w.points) + activation = 1.0 - d ** len(i) if w.points.shape[0] == 1: new_w = deepcopy(w) - new_w.add_points(i.reshape((1,-1))) + new_w.add_points(i.reshape((1, -1))) else: - new_points = np.vstack([w.points[w.vertices,:], i.reshape((1,-1))]) + new_points = np.vstack( + [w.points[w.vertices, :], i.reshape((1, -1))] + ) new_w = ConvexHull(new_points, incremental=True) else: - new_w = ConvexHull(w.points[w.vertices,:], incremental=True) - new_w.add_points(i.reshape((1,-1))) + new_w = ConvexHull(w.points[w.vertices, :], incremental=True) + new_w.add_points(i.reshape((1, -1))) activation = w.area / new_w.area cache = {"new_w": new_w, "activation": activation} return activation, cache - def match_criterion(self, i: np.ndarray, w: HullTypes, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: + 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. @@ -277,8 +296,13 @@ def match_criterion(self, i: np.ndarray, w: HullTypes, params: dict, cache: Opti """ return cache["activation"], cache - - def update(self, i: np.ndarray, w: HullTypes, params: dict, cache: Optional[dict] = None) -> HullTypes: + def update( + self, + i: np.ndarray, + w: HullTypes, + params: dict, + cache: Optional[dict] = None, + ) -> HullTypes: """ Get the updated cluster weight. @@ -318,7 +342,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> HullTypes: New cluster weight. """ - new_w = PseudoConvexHull(i.reshape((1,-1))) + new_w = PseudoConvexHull(i.reshape((1, -1))) return new_w def merge_clusters(self): @@ -326,12 +350,17 @@ 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,:]]) + combined_points = np.vstack( + [w1.points[w1.vertices, :], w2.points[w2.vertices, :]] + ) - if isinstance(w1, PseudoConvexHull) and isinstance(w2, PseudoConvexHull): + if isinstance(w1, PseudoConvexHull) and isinstance( + w2, PseudoConvexHull + ): d = minimum_distance(w1.points, w2.points) - activation = 1.0 - d**w1.points.shape[1] + activation = 1.0 - d ** w1.points.shape[1] else: new_w = ConvexHull(combined_points) if isinstance(w1, ConvexHull): @@ -342,7 +371,7 @@ def can_merge(w1, w2): a2 = w2.area / new_w.area else: a2 = np.nan - activation = np.max([a1,a2]) + activation = np.max([a1, a2]) if activation > self.params["merge_rho"]: return True @@ -356,7 +385,9 @@ def can_merge(w1, w2): new_labels = np.copy(self.labels_) for i in range(len(merges)): new_labels[np.isin(self.labels_, merges[i])] = i - new_sample_counter.append(sum(self.weight_sample_counter_[j] for j in merges[i])) + new_sample_counter.append( + sum(self.weight_sample_counter_[j] for j in merges[i]) + ) if len(merges[i]) > 1: new_points = np.vstack([self.W[j].points for j in merges[i]]) if new_points.shape[0] > 2: @@ -381,9 +412,6 @@ def post_fit(self, X: np.ndarray): """ self.merge_clusters() - - - def get_cluster_centers(self) -> List[np.ndarray]: """ Get the centers of each cluster, used for regression. @@ -399,7 +427,9 @@ def get_cluster_centers(self) -> List[np.ndarray]: centers.append(centroid_of_convex_hull(w)) return centers - def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): + def plot_cluster_bounds( + self, ax: Axes, colors: Iterable, linewidth: int = 1 + ): """ Visualize the bounds of each cluster. @@ -415,7 +445,9 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ for c, w in zip(colors, self.W): if isinstance(w, ConvexHull): - vertices = w.points[w.vertices,:2] + vertices = w.points[w.vertices, :2] else: vertices = w.points[:, :2] - plot_convex_polygon(vertices, ax, line_width=linewidth, line_color=c) + plot_convex_polygon( + vertices, ax, line_width=linewidth, line_color=c + ) diff --git a/artlib/experimental/SeqART.py b/artlib/experimental/SeqART.py index 2a2d023..8d1a66a 100644 --- a/artlib/experimental/SeqART.py +++ b/artlib/experimental/SeqART.py @@ -4,6 +4,7 @@ import operator import re + def compress_dashes(input_string: str) -> str: """ Compress consecutive dashes in a string into a single dash. @@ -18,7 +19,8 @@ def compress_dashes(input_string: str) -> str: str The string with consecutive dashes compressed into one dash. """ - return re.sub('-+', '-', input_string) + return re.sub("-+", "-", input_string) + def arr2seq(x: np.ndarray) -> str: """ @@ -36,7 +38,14 @@ def arr2seq(x: np.ndarray) -> str: """ return "".join([str(i_) for i_ in x]) -def needleman_wunsch(seq1: str, seq2: str, match_score: int =1, gap_cost: int =-1, mismatch_cost: int =-1) -> Tuple[str, float]: + +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. @@ -65,60 +74,64 @@ def needleman_wunsch(seq1: str, seq2: str, match_score: int =1, gap_cost: int =- # Initialize the gap penalties for the first row and column for i in range(1, m + 1): - score_matrix[i][0] = score_matrix[i-1][0] + gap_cost + score_matrix[i][0] = score_matrix[i - 1][0] + gap_cost for j in range(1, n + 1): - score_matrix[0][j] = score_matrix[0][j-1] + gap_cost + score_matrix[0][j] = score_matrix[0][j - 1] + gap_cost # Fill in the scoring matrix for i in range(1, m + 1): for j in range(1, n + 1): - match = score_matrix[i-1][j-1] + (match_score if seq1[i-1] == seq2[j-1] else mismatch_cost) - delete = score_matrix[i-1][j] + gap_cost - insert = score_matrix[i][j-1] + gap_cost + match = score_matrix[i - 1][j - 1] + ( + match_score if seq1[i - 1] == seq2[j - 1] else mismatch_cost + ) + delete = score_matrix[i - 1][j] + gap_cost + insert = score_matrix[i][j - 1] + gap_cost score_matrix[i][j] = max(match, delete, insert) # Traceback to get the alignment - align1, align2 = '', '' + align1, align2 = "", "" i, j = m, n while i > 0 and j > 0: current_score = score_matrix[i][j] - diagonal_score = score_matrix[i-1][j-1] - up_score = score_matrix[i-1][j] - left_score = score_matrix[i][j-1] - - if current_score == diagonal_score + (match_score if seq1[i-1] == seq2[j-1] else mismatch_cost): - align1 += seq1[i-1] - align2 += seq2[j-1] + diagonal_score = score_matrix[i - 1][j - 1] + up_score = score_matrix[i - 1][j] + left_score = score_matrix[i][j - 1] + + if current_score == diagonal_score + ( + match_score if seq1[i - 1] == seq2[j - 1] else mismatch_cost + ): + align1 += seq1[i - 1] + align2 += seq2[j - 1] i -= 1 j -= 1 elif current_score == up_score + gap_cost: - align1 += seq1[i-1] - align2 += '-' + align1 += seq1[i - 1] + align2 += "-" i -= 1 elif current_score == left_score + gap_cost: - align1 += '-' - align2 += seq2[j-1] + align1 += "-" + align2 += seq2[j - 1] j -= 1 # Fill the remaining sequence if the above while loop finishes while i > 0: - align1 += seq1[i-1] - align2 += '-' + align1 += seq1[i - 1] + align2 += "-" i -= 1 while j > 0: - align1 += '-' - align2 += seq2[j-1] + align1 += "-" + align2 += seq2[j - 1] j -= 1 # The alignments are built from the end to the beginning, so we need to reverse them align1 = align1[::-1] align2 = align2[::-1] - alignment = ''.join([a if a == b else '-' for a, b in zip(align1, align2)]) + 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 + return alignment, float(score_matrix[m][n]) / l def prepare_data(data: np.ndarray) -> np.ndarray: @@ -159,6 +172,7 @@ def __init__(self, rho: float, metric: Callable = needleman_wunsch): } self.metric = metric super().__init__(params) + @staticmethod def validate_params(params: dict): """ @@ -194,8 +208,9 @@ def check_dimensions(self, X: np.ndarray): """ pass - - def category_choice(self, i: str, w: str, params: dict) -> tuple[float, Optional[dict]]: + def category_choice( + self, i: str, w: str, params: dict + ) -> tuple[float, Optional[dict]]: """ Get the activation of the cluster. @@ -214,10 +229,12 @@ def category_choice(self, i: str, w: str, params: dict) -> tuple[float, Optional Cluster activation and cache used for later processing. """ alignment, score = self.metric(arr2seq(i), w) - cache = {'alignment': alignment, 'score': score} + cache = {"alignment": alignment, "score": score} return score, cache - def match_criterion(self, i: str, w: str, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: + def match_criterion( + self, i: str, w: str, params: dict, cache: Optional[dict] = None + ) -> tuple[float, dict]: """ Get the match criterion of the cluster. @@ -239,9 +256,16 @@ def match_criterion(self, i: str, w: str, params: dict, cache: Optional[dict] = """ # _, M = self.metric(cache['alignment'], w) - return cache['score'], cache + return cache["score"], 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]: + 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. @@ -272,7 +296,9 @@ def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: return M_bin, cache - def update(self, i: str, w: str, params: dict, cache: Optional[dict] = None) -> str: + def update( + self, i: str, w: str, params: dict, cache: Optional[dict] = None + ) -> str: """ Update the cluster weight. @@ -293,7 +319,7 @@ def update(self, i: str, w: str, params: dict, cache: Optional[dict] = None) -> Updated cluster weight. """ # print(cache['alignment']) - return compress_dashes(cache['alignment']) + return compress_dashes(cache["alignment"]) def new_weight(self, i: str, params: dict) -> str: """ @@ -311,4 +337,4 @@ def new_weight(self, i: str, params: dict) -> str: str New cluster weight. """ - return arr2seq(i) \ No newline at end of file + return arr2seq(i) diff --git a/artlib/experimental/__init__.py b/artlib/experimental/__init__.py index 6786ead..748227e 100644 --- a/artlib/experimental/__init__.py +++ b/artlib/experimental/__init__.py @@ -1,3 +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/experimental/merging.py b/artlib/experimental/merging.py index 31334df..bb25f18 100644 --- a/artlib/experimental/merging.py +++ b/artlib/experimental/merging.py @@ -1,5 +1,6 @@ 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. diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index 98cc53f..7731118 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -13,7 +13,10 @@ from sklearn.utils.validation import check_is_fitted import operator -def get_channel_position_tuples(channel_dims: list[int]) -> list[tuple[int, int]]: + +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. @@ -35,6 +38,7 @@ def get_channel_position_tuples(channel_dims: list[int]) -> list[tuple[int, int] start = end return positions + class FusionART(BaseART): """Fusion ART for Data Fusion and Regression @@ -54,12 +58,11 @@ class FusionART(BaseART): """ def __init__( - self, - modules: List[BaseART], - gamma_values: Union[List[float], np.ndarray], - channel_dims: Union[List[int], np.ndarray] + self, + modules: List[BaseART], + gamma_values: Union[List[float], np.ndarray], + channel_dims: Union[List[int], np.ndarray], ): - """ Initialize the FusionART instance. @@ -125,14 +128,8 @@ def W(self): Concatenated weights of all channels from the ART modules. """ W = [ - np.concatenate( - [ - self.modules[k].W[i] - for k in range(self.n) - ] - ) - for i - in range(self.modules[0].n_clusters) + np.concatenate([self.modules[k].W[i] for k in range(self.n)]) + for i in range(self.modules[0].n_clusters) ] return W @@ -148,7 +145,9 @@ def W(self, new_W): """ 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]] + self.modules[k].W = new_W[ + self._channel_indices[k][0] : self._channel_indices[k][1] + ] else: self.modules[k].W = [] @@ -167,7 +166,6 @@ def validate_params(params: dict): assert sum(params["gamma_values"]) == 1.0 assert isinstance(params["gamma_values"], np.ndarray) - def validate_data(self, X: np.ndarray): """ Validate the input data for clustering. @@ -179,7 +177,7 @@ def validate_data(self, X: np.ndarray): """ self.check_dimensions(X) for k in range(self.n): - X_k = X[:, self._channel_indices[k][0]:self._channel_indices[k][1]] + X_k = X[:, self._channel_indices[k][0] : self._channel_indices[k][1]] self.modules[k].validate_data(X_k) def check_dimensions(self, X: np.ndarray): @@ -207,7 +205,9 @@ def prepare_data(self, channel_data: List[np.ndarray]) -> np.ndarray: np.ndarray Processed and concatenated data. """ - prepared_channel_data = [self.modules[i].prepare_data(channel_data[i]) for i in range(self.n)] + 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) -> List[np.ndarray]: @@ -225,10 +225,18 @@ def restore_data(self, X: np.ndarray) -> List[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)] + restored_channel_data = [ + self.modules[i].restore_data(channel_data[i]) for i in range(self.n) + ] return restored_channel_data - def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict, skip_channels: List[int] = []) -> tuple[float, Optional[dict]]: + 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. @@ -251,20 +259,29 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict, skip_chann activations, caches = zip( *[ self.modules[k].category_choice( - i[self._channel_indices[k][0]:self._channel_indices[k][1]], - w[self._channel_indices[k][0]:self._channel_indices[k][1]], - self.modules[k].params + i[self._channel_indices[k][0] : self._channel_indices[k][1]], + w[self._channel_indices[k][0] : self._channel_indices[k][1]], + self.modules[k].params, ) if k not in skip_channels - else (1., dict()) + else (1.0, dict()) for k in range(self.n) ] ) cache = {k: cache_k for k, cache_k in enumerate(caches)} - activation = sum([a*self.params["gamma_values"][k] for k, a in enumerate(activations)]) + activation = sum( + [a * self.params["gamma_values"][k] for k, a in enumerate(activations)] + ) 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]: + 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. @@ -291,10 +308,10 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt M, caches = zip( *[ self.modules[k].match_criterion( - i[self._channel_indices[k][0]:self._channel_indices[k][1]], - w[self._channel_indices[k][0]:self._channel_indices[k][1]], + i[self._channel_indices[k][0] : self._channel_indices[k][1]], + w[self._channel_indices[k][0] : self._channel_indices[k][1]], self.modules[k].params, - cache[k] + cache[k], ) if k not in skip_channels else (np.inf, {"match_criterion": np.inf}) @@ -304,7 +321,15 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt cache = {k: cache_k for k, cache_k in enumerate(caches)} return M, cache - 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]: + 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 for the cluster. @@ -333,11 +358,11 @@ def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: M_bin, caches = zip( *[ self.modules[k].match_criterion_bin( - i[self._channel_indices[k][0]:self._channel_indices[k][1]], - w[self._channel_indices[k][0]:self._channel_indices[k][1]], + i[self._channel_indices[k][0] : self._channel_indices[k][1]], + w[self._channel_indices[k][0] : self._channel_indices[k][1]], self.modules[k].params, cache[k], - op + op, ) if k not in skip_channels else (True, {"match_criterion": np.inf}) @@ -347,8 +372,13 @@ def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: cache = {k: cache_k for k, cache_k in enumerate(caches)} return all(M_bin), cache - - def _match_tracking(self, cache: List[dict], epsilon: float, params: List[dict], method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"]) -> bool: + 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. @@ -371,13 +401,14 @@ def _match_tracking(self, cache: List[dict], epsilon: float, params: List[dict], keep_searching = [] for i in range(len(cache)): if cache[i]["match_criterion_bin"]: - keep_searching_i = self.modules[i]._match_tracking(cache[i], epsilon, params[i], method) + keep_searching_i = self.modules[i]._match_tracking( + cache[i], epsilon, params[i], method + ) keep_searching.append(keep_searching_i) else: keep_searching.append(True) return all(keep_searching) - def _set_params(self, new_params: List[dict]): """ Set the parameters for each module in FusionART. @@ -401,8 +432,13 @@ def _deep_copy_params(self) -> dict: """ 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): + 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. @@ -420,18 +456,23 @@ def partial_fit(self, X: np.ndarray, match_reset_func: Optional[Callable] = None self.validate_data(X) self.check_dimensions(X) - self.is_fitted_ = True + self.is_fitted_ = True - if not hasattr(self.modules[0], 'W'): + if not hasattr(self.modules[0], "W"): self.W: list[np.ndarray] = [] - self.labels_ = np.zeros((X.shape[0], ), dtype=int) + self.labels_ = np.zeros((X.shape[0],), dtype=int) j = 0 else: j = len(self.labels_) - self.labels_ = np.pad(self.labels_, [(0, X.shape[0])], mode='constant') + self.labels_ = np.pad(self.labels_, [(0, X.shape[0])], mode="constant") for i, x in enumerate(X): - c = self.step_fit(x, match_reset_func=match_reset_func, match_reset_method=match_reset_method, epsilon=epsilon) - self.labels_[i+j] = c + c = self.step_fit( + x, + match_reset_func=match_reset_func, + match_reset_method=match_reset_method, + epsilon=epsilon, + ) + self.labels_[i + j] = c return self def step_pred(self, x, skip_channels: List[int] = []) -> int: @@ -452,7 +493,14 @@ def step_pred(self, x, skip_channels: List[int] = []) -> int: """ assert len(self.W) >= 0, "ART module is not fit." - T, _ = zip(*[self.category_choice(x, w, params=self.params, skip_channels=skip_channels) for w in self.W]) + T, _ = zip( + *[ + self.category_choice( + x, w, params=self.params, skip_channels=skip_channels + ) + for w in self.W + ] + ) c_ = int(np.argmax(T)) return c_ @@ -483,7 +531,13 @@ def predict(self, X: np.ndarray, skip_channels: List[int] = []) -> np.ndarray: y[i] = c return y - def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: + def update( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + ) -> np.ndarray: """ Update the cluster weight. @@ -505,10 +559,10 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic """ W = [ self.modules[k].update( - i[self._channel_indices[k][0]:self._channel_indices[k][1]], - w[self._channel_indices[k][0]:self._channel_indices[k][1]], + i[self._channel_indices[k][0] : self._channel_indices[k][1]], + w[self._channel_indices[k][0] : self._channel_indices[k][1]], self.modules[k].params, - cache[k] + cache[k], ) for k in range(self.n) ] @@ -532,7 +586,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ W = [ self.modules[k].new_weight( - i[self._channel_indices[k][0]:self._channel_indices[k][1]], + i[self._channel_indices[k][0] : self._channel_indices[k][1]], self.modules[k].params, ) for k in range(self.n) @@ -548,7 +602,7 @@ def add_weight(self, new_w: np.ndarray): """ for k in range(self.n): - new_w_k = new_w[self._channel_indices[k][0]:self._channel_indices[k][1]] + new_w_k = new_w[self._channel_indices[k][0] : self._channel_indices[k][1]] self.modules[k].add_weight(new_w_k) def set_weight(self, idx: int, new_w: np.ndarray): @@ -561,7 +615,7 @@ def set_weight(self, idx: int, new_w: np.ndarray): """ for k in range(self.n): - new_w_k = new_w[self._channel_indices[k][0]:self._channel_indices[k][1]] + new_w_k = new_w[self._channel_indices[k][0] : self._channel_indices[k][1]] self.modules[k].set_weight(idx, new_w_k) def get_cluster_centers(self) -> List[np.ndarray]: @@ -575,14 +629,8 @@ def get_cluster_centers(self) -> List[np.ndarray]: """ centers_ = [module.get_cluster_centers() for module in self.modules] centers = [ - np.concatenate( - [ - centers_[k][i] - for k in range(self.n) - ] - ) - for i - in range(self.n_clusters) + np.concatenate([centers_[k][i] for k in range(self.n)]) + for i in range(self.n_clusters) ] return centers @@ -602,7 +650,9 @@ def get_channel_centers(self, channel: int) -> List[np.ndarray]: """ 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]]: + 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. @@ -620,7 +670,7 @@ def predict_regression(self, X: np.ndarray, target_channels: List[int] = [-1]) - 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] + 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] if len(target_channels) == 1: @@ -628,7 +678,9 @@ def predict_regression(self, X: np.ndarray, target_channels: List[int] = [-1]) - else: 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: + 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. @@ -644,7 +696,7 @@ def join_channel_data(self, channel_data: List[np.ndarray], skip_channels: List[ np.ndarray Concatenated data. """ - skip_channels = [self.n+k if k < 0 else k for k in skip_channels] + skip_channels = [self.n + k if k < 0 else k for k in skip_channels] n_samples = channel_data[0].shape[0] formatted_channel_data = [] @@ -654,12 +706,22 @@ def join_channel_data(self, channel_data: List[np.ndarray], skip_channels: List[ formatted_channel_data.append(channel_data[i]) i += 1 else: - formatted_channel_data.append(0.5*np.ones((n_samples, self._channel_indices[k][1]-self._channel_indices[k][0]))) + formatted_channel_data.append( + 0.5 + * np.ones( + ( + n_samples, + self._channel_indices[k][1] - self._channel_indices[k][0], + ) + ) + ) X = np.hstack(formatted_channel_data) return X - def split_channel_data(self, joined_data: np.ndarray, skip_channels: List[int] = []) -> List[np.ndarray]: + def split_channel_data( + self, joined_data: np.ndarray, skip_channels: List[int] = [] + ) -> List[np.ndarray]: """ Split the concatenated data into its original channels. @@ -686,7 +748,9 @@ def split_channel_data(self, joined_data: np.ndarray, skip_channels: List[int] = if k not in skip_channels: # Extract the original channel data - channel_data.append(joined_data[:, current_col:current_col + channel_width]) + channel_data.append( + joined_data[:, current_col : current_col + channel_width] + ) current_col += channel_width else: # If this channel was skipped, we know it was filled with 0.5, so we skip those columns diff --git a/artlib/fusion/__init__.py b/artlib/fusion/__init__.py index 2bd37d9..6323a30 100644 --- a/artlib/fusion/__init__.py +++ b/artlib/fusion/__init__.py @@ -11,4 +11,4 @@ This is the recommended module for such regression problems. `Data fusion `_ -""" \ No newline at end of file +""" diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index fd3fecb..cf5dedb 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -12,6 +12,7 @@ from artlib.supervised.SimpleARTMAP import SimpleARTMAP from artlib.supervised.ARTMAP import ARTMAP + class DeepARTMAP(BaseEstimator, ClassifierMixin, ClusterMixin): """DeepARTMAP for Hierachical Supervised and Unsupervised Learning @@ -130,13 +131,9 @@ def labels_deep_(self) -> np.ndarray: Deep labels from all ART layers concatenated together. """ return np.concatenate( - [ - layer.labels_.reshape((-1, 1)) - for layer in self.layers - ]+[ - self.layers[-1].labels_a.reshape((-1, 1)) - ], - axis=1 + [layer.labels_.reshape((-1, 1)) for layer in self.layers] + + [self.layers[-1].labels_a.reshape((-1, 1))], + axis=1, ) @property @@ -163,7 +160,9 @@ def n_layers(self) -> int: """ return len(self.layers) - def map_deep(self, level: int, y_a: Union[np.ndarray, int]) -> Union[np.ndarray, int]: + 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. @@ -183,16 +182,11 @@ def map_deep(self, level: int, y_a: Union[np.ndarray, int]) -> Union[np.ndarray, level += len(self.layers) y_b = self.layers[level].map_a2b(y_a) if level > 0: - return self.map_deep(level-1, y_b) + return self.map_deep(level - 1, y_b) else: return y_b - - def validate_data( - self, - X: list[np.ndarray], - y: Optional[np.ndarray] = None - ): + def validate_data(self, X: list[np.ndarray], y: Optional[np.ndarray] = None): """ Validate the data before clustering. @@ -208,15 +202,20 @@ def validate_data( 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" + assert ( + len(X) == self.n_modules + ), f"Must provide {self.n_modules} input matrices for {self.n_modules} ART modules" if y is not None: n = len(y) else: n = X[0].shape[0] - assert all(x.shape[0] == n for x in X), "Inconsistent sample number in input matrices" + assert all( + x.shape[0] == n for x in X + ), "Inconsistent sample number in input matrices" - def prepare_data(self, X: list[np.ndarray], y: Optional[np.ndarray] = None) ->Tuple[list[np.ndarray], Optional[np.ndarray]]: + def prepare_data( + self, X: list[np.ndarray], y: Optional[np.ndarray] = None + ) -> Tuple[list[np.ndarray], Optional[np.ndarray]]: """ Prepare the data for clustering. @@ -234,7 +233,9 @@ def prepare_data(self, X: list[np.ndarray], y: Optional[np.ndarray] = None) ->T """ 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]]: + def restore_data( + self, X: list[np.ndarray], y: Optional[np.ndarray] = None + ) -> Tuple[list[np.ndarray], Optional[np.ndarray]]: """ Restore the data to its original state before preparation. @@ -252,8 +253,14 @@ def restore_data(self, X: list[np.ndarray], y: Optional[np.ndarray] = None) ->T """ 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): + 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 DeepARTMAP model to the data. @@ -279,22 +286,51 @@ def fit(self, X: list[np.ndarray], y: Optional[np.ndarray] = None, max_iter=1, m if y is not None: self.is_supervised = True self.layers = [SimpleARTMAP(self.modules[i]) for i in range(self.n_modules)] - self.layers[0] = self.layers[0].fit(X[0], y, max_iter=max_iter, match_reset_method=match_reset_method, epsilon=epsilon) + self.layers[0] = self.layers[0].fit( + X[0], + y, + max_iter=max_iter, + match_reset_method=match_reset_method, + epsilon=epsilon, + ) else: self.is_supervised = False - assert self.n_modules >= 2, "Must provide at least two ART modules when providing cluster labels" - self.layers = cast(list[BaseARTMAP], [ARTMAP(self.modules[1], self.modules[0])]) + \ - cast(list[BaseARTMAP], [SimpleARTMAP(self.modules[i]) for i in range(2, self.n_modules)]) - self.layers[0] = self.layers[0].fit(X[1], X[0], max_iter=max_iter, match_reset_method=match_reset_method, epsilon=epsilon) + assert ( + self.n_modules >= 2 + ), "Must provide at least two ART modules when providing cluster labels" + self.layers = cast( + list[BaseARTMAP], [ARTMAP(self.modules[1], self.modules[0])] + ) + cast( + list[BaseARTMAP], + [SimpleARTMAP(self.modules[i]) for i in range(2, self.n_modules)], + ) + self.layers[0] = self.layers[0].fit( + X[1], + X[0], + max_iter=max_iter, + match_reset_method=match_reset_method, + epsilon=epsilon, + ) for art_i in range(1, self.n_layers): - y_i = self.layers[art_i-1].labels_a - self.layers[art_i] = self.layers[art_i].fit(X[art_i], y_i, max_iter=max_iter, match_reset_method=match_reset_method, epsilon=epsilon) + y_i = self.layers[art_i - 1].labels_a + self.layers[art_i] = self.layers[art_i].fit( + X[art_i], + y_i, + max_iter=max_iter, + match_reset_method=match_reset_method, + epsilon=epsilon, + ) return self - - 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): + 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, + ): """ Partially fit the DeepARTMAP model to the data. @@ -318,29 +354,52 @@ def partial_fit(self, X: list[np.ndarray], y: Optional[np.ndarray] = None, match if y is not None: if len(self.layers) == 0: self.is_supervised = True - self.layers = [SimpleARTMAP(self.modules[i]) for i in range(self.n_modules)] - assert self.is_supervised, "Labels were previously provided. Must continue to provide labels for partial fit." - self.layers[0] = self.layers[0].partial_fit(X[0], y, match_reset_method=match_reset_method, epsilon=epsilon) + self.layers = [ + SimpleARTMAP(self.modules[i]) for i in range(self.n_modules) + ] + assert ( + self.is_supervised + ), "Labels were previously provided. Must continue to provide labels for partial fit." + self.layers[0] = self.layers[0].partial_fit( + X[0], y, match_reset_method=match_reset_method, epsilon=epsilon + ) x_i = 1 else: if len(self.layers) == 0: self.is_supervised = False - assert self.n_modules >= 2, "Must provide at least two ART modules when providing cluster labels" - self.layers = cast(list[BaseARTMAP], [ARTMAP(self.modules[1], self.modules[0])]) + \ - cast(list[BaseARTMAP], [SimpleARTMAP(self.modules[i]) for i in range(2, self.n_modules)]) - assert not self.is_supervised, "Labels were not previously provided. Do not provide labels to continue partial fit." - - self.layers[0] = self.layers[0].partial_fit(X[1], X[0], match_reset_method=match_reset_method, epsilon=epsilon) + assert ( + self.n_modules >= 2 + ), "Must provide at least two ART modules when providing cluster labels" + self.layers = cast( + list[BaseARTMAP], [ARTMAP(self.modules[1], self.modules[0])] + ) + cast( + list[BaseARTMAP], + [SimpleARTMAP(self.modules[i]) for i in range(2, self.n_modules)], + ) + assert ( + not self.is_supervised + ), "Labels were not previously provided. Do not provide labels to continue partial fit." + + self.layers[0] = self.layers[0].partial_fit( + X[1], + X[0], + match_reset_method=match_reset_method, + epsilon=epsilon, + ) x_i = 2 n_samples = X[0].shape[0] for art_i in range(1, self.n_layers): - y_i = self.layers[art_i-1].labels_a[-n_samples:] - self.layers[art_i] = self.layers[art_i].partial_fit(X[x_i], y_i, match_reset_method=match_reset_method, epsilon=epsilon) + y_i = self.layers[art_i - 1].labels_a[-n_samples:] + self.layers[art_i] = self.layers[art_i].partial_fit( + X[x_i], + y_i, + match_reset_method=match_reset_method, + epsilon=epsilon, + ) x_i += 1 return self - def predict(self, X: Union[np.ndarray, list[np.ndarray]]) -> list[np.ndarray]: """ Predict the labels for the input data. @@ -365,8 +424,3 @@ def predict(self, X: Union[np.ndarray, list[np.ndarray]]) -> list[np.ndarray]: pred.append(layer.map_a2b(pred[-1])) return pred[::-1] - - - - - diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index 6858f1b..70b96da 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -11,6 +11,7 @@ from artlib.common.BaseART import BaseART from artlib.hierarchical.DeepARTMAP import DeepARTMAP + class SMART(DeepARTMAP): """SMART for Hierachical Clustering @@ -25,7 +26,13 @@ class SMART(DeepARTMAP): """ - def __init__(self, base_ART_class: Type, rho_values: Union[list[float], np.ndarray], base_params: dict, **kwargs): + def __init__( + self, + base_ART_class: Type, + rho_values: Union[list[float], np.ndarray], + base_params: dict, + **kwargs + ): """ Initialize the SMART model. @@ -41,15 +48,21 @@ def __init__(self, base_ART_class: Type, rho_values: Union[list[float], np.ndarr 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" + 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" + 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] modules = [base_ART_class(**params, **kwargs) for params in layer_params] for module in modules: - assert isinstance(module, BaseART), "Only elementary ART-like objects are supported" + assert isinstance( + module, BaseART + ), "Only elementary ART-like objects are supported" super().__init__(modules) def prepare_data(self, X: np.ndarray) -> np.ndarray: @@ -66,7 +79,7 @@ def prepare_data(self, X: np.ndarray) -> np.ndarray: np.ndarray Prepared data. """ - X_, _ = super(SMART, self).prepare_data([X]*self.n_modules) + X_, _ = super(SMART, self).prepare_data([X] * self.n_modules) return X_[0] def restore_data(self, X: np.ndarray) -> np.ndarray: @@ -86,7 +99,14 @@ def restore_data(self, X: np.ndarray) -> np.ndarray: 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): + 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 SMART model to the data. @@ -108,10 +128,21 @@ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, max_iter=1, match_r 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): + 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. @@ -132,7 +163,9 @@ def partial_fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, match_reset 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) + 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): """ @@ -160,15 +193,14 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): layer_colors.append(colors[self.map_deep(j - 1, k)]) self.modules[j].plot_cluster_bounds(ax, layer_colors, linewidth) - def visualize( - self, - X: np.ndarray, - y: np.ndarray, - ax: Optional[Axes] = None, - marker_size: int = 10, - linewidth: int = 1, - colors: Optional[Iterable] = None + self, + X: np.ndarray, + y: np.ndarray, + ax: Optional[Axes] = None, + marker_size: int = 10, + linewidth: int = 1, + colors: Optional[Iterable] = None, ): """ Visualize the clustering of the data with cluster boundaries. @@ -199,10 +231,17 @@ def visualize( if colors is None: from matplotlib.pyplot import cm + colors = cm.rainbow(np.linspace(0, 1, self.modules[0].n_clusters)) for k, col in enumerate(colors): cluster_data = y == k - plt.scatter(X[cluster_data, 0], X[cluster_data, 1], color=col, marker=".", s=marker_size) - - self.plot_cluster_bounds(ax, colors, linewidth) \ No newline at end of file + plt.scatter( + X[cluster_data, 0], + X[cluster_data, 1], + color=col, + marker=".", + s=marker_size, + ) + + self.plot_cluster_bounds(ax, colors, linewidth) diff --git a/artlib/hierarchical/__init__.py b/artlib/hierarchical/__init__.py index af78747..b3006a6 100644 --- a/artlib/hierarchical/__init__.py +++ b/artlib/hierarchical/__init__.py @@ -9,4 +9,4 @@ `Divisive clustering `_ -""" \ No newline at end of file +""" diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index 78fe910..531f647 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -26,13 +26,14 @@ class FALCON: functions are implemented for getting optimal reward and action predictions. """ + 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] + 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], ): """ Initialize the FALCON model. @@ -53,10 +54,12 @@ def __init__( self.fusion_art = FusionART( modules=[state_art, action_art, reward_art], gamma_values=gamma_values, - channel_dims=channel_dims + channel_dims=channel_dims, ) - def prepare_data(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + def prepare_data( + self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray + ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ Prepare data for clustering. @@ -74,9 +77,15 @@ def prepare_data(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndar 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) + 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]: + def restore_data( + self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray + ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ Restore data to its original form before preparation. @@ -94,7 +103,11 @@ def restore_data(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndar 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) + 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): """ @@ -140,7 +153,9 @@ 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) -> Tuple[np.ndarray, np.ndarray]: + 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. @@ -163,7 +178,9 @@ def get_actions_and_rewards(self, state: np.ndarray, action_space: Optional[np.n action_space_prepared = self.fusion_art.modules[1].prepare_data(action_space) viable_clusters = [] for action in action_space_prepared: - data = self.fusion_art.join_channel_data([state.reshape(1, -1), action.reshape(1, -1)], skip_channels=[2]) + data = self.fusion_art.join_channel_data( + [state.reshape(1, -1), action.reshape(1, -1)], skip_channels=[2] + ) c = self.fusion_art.predict(data, skip_channels=[2]) viable_clusters.append(c[0]) @@ -171,8 +188,12 @@ def get_actions_and_rewards(self, state: np.ndarray, action_space: Optional[np.n 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: + 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. @@ -197,7 +218,13 @@ def get_action(self, state: np.ndarray, action_space: Optional[np.ndarray] = Non c_winner = np.argmin(rewards) 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: + 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. @@ -220,13 +247,12 @@ def get_probabilistic_action(self, state: np.ndarray, action_space: Optional[np. action_space, rewards = self.get_actions_and_rewards(state, action_space) action_indices = np.array(range(len(action_space))) - reward_dist = rewards reward_dist /= np.sum(reward_dist) reward_dist = reward_dist.reshape((-1,)) if optimality == "min": - reward_dist = 1.-reward_dist + reward_dist = 1.0 - reward_dist reward_dist = np.maximum(np.minimum(reward_dist, offset), 0.0001) reward_dist /= np.sum(reward_dist) @@ -269,14 +295,14 @@ class TD_FALCON(FALCON): """ 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, + 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. @@ -300,7 +326,9 @@ def __init__( """ 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) + 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): """ @@ -313,7 +341,13 @@ 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): + 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. @@ -336,7 +370,6 @@ def calculate_SARSA(self, states: np.ndarray, actions: np.ndarray, rewards: np.n # 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) @@ -344,7 +377,9 @@ def calculate_SARSA(self, states: np.ndarray, actions: np.ndarray, rewards: np.n # 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]) + 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 @@ -363,7 +398,13 @@ 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): + 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. @@ -383,7 +424,11 @@ def partial_fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarr 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]) + 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 index 0df7fc7..2c86b7d 100644 --- a/artlib/reinforcement/__init__.py +++ b/artlib/reinforcement/__init__.py @@ -14,4 +14,4 @@ `SARSA `_ `Reactive agents `_ -""" \ No newline at end of file +""" diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index 7dd7955..f8c7542 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -25,6 +25,7 @@ class ARTMAP(SimpleARTMAP): allow this. However, FusionART provides substantially better fit for regression problems which are not monotonic. """ + def __init__(self, module_a: BaseART, module_b: BaseART): """ Initialize the ARTMAP model with two ART modules. @@ -66,7 +67,6 @@ def get_params(self, deep: bool = True) -> dict: out.update(("module_b" + "__" + k, val) for k, val in deep_b_items) return out - @property def labels_a(self) -> np.ndarray: """ @@ -117,7 +117,9 @@ def validate_data(self, X: np.ndarray, y: np.ndarray): 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]: + def prepare_data( + self, X: np.ndarray, y: np.ndarray + ) -> Tuple[np.ndarray, np.ndarray]: """ Prepare data for clustering by normalizing and transforming. @@ -135,7 +137,9 @@ def prepare_data(self, X: np.ndarray, y: np.ndarray) -> Tuple[np.ndarray, np.nda """ 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]: + def restore_data( + self, X: np.ndarray, y: np.ndarray + ) -> Tuple[np.ndarray, np.ndarray]: """ Restore data to its original state before preparation. @@ -153,7 +157,14 @@ def restore_data(self, X: np.ndarray, y: np.ndarray) -> Tuple[np.ndarray, np.nda """ 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): + 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 ARTMAP model to the data. @@ -178,16 +189,32 @@ def fit(self, X: np.ndarray, y: np.ndarray, max_iter=1, match_reset_method: Lite # Check that X and y have correct shape self.validate_data(X, y) - self.module_b.fit(y, max_iter=max_iter, match_reset_method=match_reset_method, epsilon=epsilon) + self.module_b.fit( + y, + max_iter=max_iter, + match_reset_method=match_reset_method, + epsilon=epsilon, + ) y_c = self.module_b.labels_ - super(ARTMAP, self).fit(X, y_c, max_iter=max_iter, match_reset_method=match_reset_method, epsilon=epsilon) + super(ARTMAP, self).fit( + X, + y_c, + max_iter=max_iter, + match_reset_method=match_reset_method, + epsilon=epsilon, + ) return self - - def partial_fit(self, X: np.ndarray, y: np.ndarray, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10): + def partial_fit( + self, + X: np.ndarray, + y: np.ndarray, + match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + epsilon: float = 1e-10, + ): """ Partially fit the ARTMAP model to the data. @@ -208,11 +235,17 @@ def partial_fit(self, X: np.ndarray, y: np.ndarray, match_reset_method: Literal[ Partially fitted ARTMAP model. """ self.validate_data(X, y) - self.module_b.partial_fit(y, match_reset_method=match_reset_method, epsilon=epsilon) - super(ARTMAP, self).partial_fit(X, self.labels_b, match_reset_method=match_reset_method, epsilon=epsilon) + self.module_b.partial_fit( + y, match_reset_method=match_reset_method, epsilon=epsilon + ) + super(ARTMAP, self).partial_fit( + X, + self.labels_b, + match_reset_method=match_reset_method, + epsilon=epsilon, + ) return self - def predict(self, X: np.ndarray) -> np.ndarray: """ Predict the labels for the given data. diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index 2cb7c2d..ac3a74f 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -39,15 +39,14 @@ def __init__(self, module_a: BaseART): self.module_a = module_a super().__init__() - def match_reset_func( - self, - i: np.ndarray, - w: np.ndarray, - cluster_a, - params: dict, - extra: dict, - cache: Optional[dict] = None + self, + i: np.ndarray, + w: np.ndarray, + cluster_a, + params: dict, + extra: dict, + cache: Optional[dict] = None, ) -> bool: """ Permits external factors to influence cluster creation. @@ -97,8 +96,9 @@ def get_params(self, deep: bool = True) -> dict: out.update(("module_a" + "__" + k, val) for k, val in deep_items) return out - - def validate_data(self, X: np.ndarray, y: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + def validate_data( + self, X: np.ndarray, y: np.ndarray + ) -> tuple[np.ndarray, np.ndarray]: """ Validate data prior to clustering. @@ -150,7 +150,13 @@ def restore_data(self, X: np.ndarray) -> np.ndarray: """ 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: + 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. @@ -171,16 +177,34 @@ def step_fit(self, x: np.ndarray, c_b: int, match_reset_method: Literal["MT+", " 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 + i, + w, + cluster, + params=params, + extra={"cluster_b": c_b}, + cache=cache, + ) + c_a = self.module_a.step_fit( + x, + match_reset_func=match_reset_func, + match_reset_method=match_reset_method, + epsilon=epsilon, ) - c_a = self.module_a.step_fit(x, match_reset_func=match_reset_func, match_reset_method=match_reset_method, epsilon=epsilon) if c_a not in self.map: self.map[c_a] = c_b else: assert self.map[c_a] == c_b return c_a - 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): + 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. @@ -216,17 +240,29 @@ def fit(self, X: np.ndarray, y: np.ndarray, max_iter=1, match_reset_method: Lite for _ in range(max_iter): if verbose: from tqdm import tqdm + x_y_iter = tqdm(enumerate(zip(X, y)), total=int(X.shape[0])) else: x_y_iter = enumerate(zip(X, y)) for i, (x, c_b) in x_y_iter: self.module_a.pre_step_fit(X) - c_a = self.step_fit(x, c_b, match_reset_method=match_reset_method, epsilon=epsilon) + c_a = self.step_fit( + x, + c_b, + match_reset_method=match_reset_method, + epsilon=epsilon, + ) self.module_a.labels_[i] = c_a self.module_a.post_step_fit(X) return self - def partial_fit(self, X: np.ndarray, y: np.ndarray, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10): + 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. @@ -247,20 +283,24 @@ def partial_fit(self, X: np.ndarray, y: np.ndarray, match_reset_method: Literal[ The partially fitted model. """ SimpleARTMAP.validate_data(self, X, y) - if not hasattr(self, 'labels_'): + if not hasattr(self, "labels_"): self.labels_ = y self.module_a.W = [] self.module_a.labels_ = np.zeros((X.shape[0],), dtype=int) j = 0 else: j = len(self.labels_) - self.labels_ = np.pad(self.labels_, [(0, X.shape[0])], mode='constant') + self.labels_ = np.pad(self.labels_, [(0, X.shape[0])], mode="constant") self.labels_[j:] = y - self.module_a.labels_ = np.pad(self.module_a.labels_, [(0, X.shape[0])], mode='constant') + self.module_a.labels_ = np.pad( + self.module_a.labels_, [(0, X.shape[0])], mode="constant" + ) for i, (x, c_b) in enumerate(zip(X, y)): self.module_a.pre_step_fit(X) - c_a = self.step_fit(x, c_b, match_reset_method=match_reset_method, epsilon=epsilon) - self.module_a.labels_[i+j] = c_a + c_a = self.step_fit( + x, c_b, match_reset_method=match_reset_method, epsilon=epsilon + ) + self.module_a.labels_[i + j] = c_a self.module_a.post_step_fit(X) return self @@ -417,13 +457,13 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): self.module_a.plot_cluster_bounds(ax, colors_a, linewidth) def visualize( - self, - X: np.ndarray, - y: np.ndarray, - ax: Optional[Axes] = None, - marker_size: int = 10, - linewidth: int = 1, - colors: Optional[Iterable] = None + self, + X: np.ndarray, + y: np.ndarray, + ax: Optional[Axes] = None, + marker_size: int = 10, + linewidth: int = 1, + colors: Optional[Iterable] = None, ): """ Visualize the clustering of the data. @@ -450,10 +490,17 @@ def visualize( if colors is None: from matplotlib.pyplot import cm + colors = cm.rainbow(np.linspace(0, 1, self.n_clusters_b)) for k_b, col in enumerate(colors): cluster_data = y == k_b - plt.scatter(X[cluster_data, 0], X[cluster_data, 1], color=col, marker=".", s=marker_size) + plt.scatter( + X[cluster_data, 0], + X[cluster_data, 1], + color=col, + marker=".", + s=marker_size, + ) self.plot_cluster_bounds(ax, colors, linewidth) diff --git a/artlib/supervised/__init__.py b/artlib/supervised/__init__.py index 0b7f347..40b7d4c 100644 --- a/artlib/supervised/__init__.py +++ b/artlib/supervised/__init__.py @@ -9,4 +9,4 @@ `Supervised learning `_ -""" \ No newline at end of file +""" diff --git a/artlib/topological/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index 11448d5..d3c7b2c 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -43,8 +43,9 @@ def __init__(self, base_module: BaseART, rho_lower_bound: float): f"{base_module.__class__.__name__} is an abstraction of the BaseART class. " f"This module will only make use of the base_module {base_module.base_module.__class__.__name__}" ) - assert "rho" in base_module.params, \ - "Dual Vigilance ART is only compatible with ART modules relying on 'rho' for vigilance." + assert ( + "rho" in base_module.params + ), "Dual Vigilance ART is only compatible with ART modules relying on 'rho' for vigilance." params = {"rho_lower_bound": rho_lower_bound} assert base_module.params["rho"] > params["rho_lower_bound"] >= 0 @@ -104,7 +105,7 @@ def get_params(self, deep: bool = True) -> dict: """ out = { "rho_lower_bound": self.params["rho_lower_bound"], - "base_module": self.base_module + "base_module": self.base_module, } if deep: deep_items = self.base_module.get_params().items() @@ -212,12 +213,19 @@ def validate_params(self, params: dict): """ - assert "rho_lower_bound" in params, \ - "Dual Vigilance ART requires a lower bound 'rho' value" + assert ( + "rho_lower_bound" in params + ), "Dual Vigilance ART requires a lower bound 'rho' value" assert params["rho_lower_bound"] >= 0 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: + 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. @@ -240,7 +248,7 @@ def _match_tracking(self, cache: dict, epsilon: float, params: dict, method: Lit """ M = cache["match_criterion"] if method == "MT+": - self.base_module.params["rho"] = M+epsilon + self.base_module.params["rho"] = M + epsilon return True elif method == "MT-": self.base_module.params["rho"] = M - epsilon @@ -262,7 +270,13 @@ def _set_params(self, 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 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. @@ -295,7 +309,9 @@ def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None,ma else: T_values, T_cache = zip( *[ - self.base_module.category_choice(x, w, params=self.base_module.params) + self.base_module.category_choice( + x, w, params=self.base_module.params + ) for w in self.base_module.W ] ) @@ -304,30 +320,50 @@ def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None,ma c_ = int(np.nanargmax(T)) w = self.base_module.W[c_] cache = T_cache[c_] - m1, cache = self.base_module.match_criterion_bin(x, w, params=self.base_module.params, cache=cache, op=mt_operator) - no_match_reset = ( - match_reset_func is None or - match_reset_func(x, w, self.map[c_], params=self.base_module.params, cache=cache) + m1, cache = self.base_module.match_criterion_bin( + x, + w, + params=self.base_module.params, + cache=cache, + op=mt_operator, + ) + no_match_reset = match_reset_func is None or match_reset_func( + x, + w, + self.map[c_], + params=self.base_module.params, + cache=cache, ) if no_match_reset: if m1: - new_w = self.base_module.update(x, w, self.base_module.params, cache=cache) + new_w = self.base_module.update( + x, w, self.base_module.params, cache=cache + ) self.base_module.set_weight(c_, new_w) self._set_params(base_params) return self.map[c_] else: - lb_params = dict(self.base_module.params, **{"rho": self.rho_lower_bound}) - m2, _ = self.base_module.match_criterion_bin(x, w, params=lb_params, cache=cache, op=mt_operator) + lb_params = dict( + self.base_module.params, + **{"rho": self.rho_lower_bound}, + ) + m2, _ = self.base_module.match_criterion_bin( + x, w, params=lb_params, cache=cache, op=mt_operator + ) if m2: c_new = len(self.base_module.W) - w_new = self.base_module.new_weight(x, self.base_module.params) + w_new = self.base_module.new_weight( + x, self.base_module.params + ) self.base_module.add_weight(w_new) self.map[c_new] = self.map[c_] self._set_params(base_params) return self.map[c_new] else: - keep_searching = self._match_tracking(cache, epsilon, self.params, match_reset_method) + keep_searching = self._match_tracking( + cache, epsilon, self.params, match_reset_method + ) if not keep_searching: T[:] = np.nan T[c_] = np.nan @@ -395,6 +431,10 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): colors_base.append(colors[self.map[k_a]]) try: - self.base_module.plot_cluster_bounds(ax=ax, colors=colors_base, linewidth=linewidth) + self.base_module.plot_cluster_bounds( + ax=ax, colors=colors_base, linewidth=linewidth + ) except NotImplementedError: - warn(f"{self.base_module.__class__.__name__} does not support plotting cluster bounds.") + warn( + f"{self.base_module.__class__.__name__} does not support plotting cluster bounds." + ) diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index 2fe4b1c..7dc11b3 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -54,13 +54,15 @@ def __init__(self, base_module: BaseART, beta_lower: float, tau: int, phi: int): f"{base_module.__class__.__name__} is an abstraction of the BaseART class. " f"This module will only make use of the base_module {base_module.base_module.__class__.__name__}" ) - params = dict(base_module.params, **{"beta_lower": beta_lower, "tau": tau, "phi": phi}) + params = dict( + base_module.params, + **{"beta_lower": beta_lower, "tau": tau, "phi": phi}, + ) super().__init__(params) self.base_module = base_module self.adjacency = np.zeros([], dtype=int) self._permanent_mask = np.zeros([], dtype=bool) - @staticmethod def validate_params(params: dict): """ @@ -76,7 +78,9 @@ def validate_params(params: dict): 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" in params + ), "TopoART is only compatible with ART modules relying on 'beta' for learning." assert "beta_lower" in params assert "tau" in params assert "phi" in params @@ -87,7 +91,6 @@ def validate_params(params: dict): assert isinstance(params["tau"], int) assert isinstance(params["phi"], int) - @property def W(self) -> List[np.ndarray]: """ @@ -100,7 +103,6 @@ def W(self) -> List[np.ndarray]: """ return self.base_module.W - @W.setter def W(self, new_W: list[np.ndarray]): """ @@ -113,7 +115,6 @@ def W(self, new_W: list[np.ndarray]): """ self.base_module.W = new_W - def validate_data(self, X: np.ndarray): """ Validate the data prior to clustering. @@ -125,7 +126,6 @@ def validate_data(self, X: np.ndarray): """ self.base_module.validate_data(X) - def prepare_data(self, X: np.ndarray) -> np.ndarray: """ Prepare data for clustering. @@ -142,7 +142,6 @@ def prepare_data(self, X: np.ndarray) -> np.ndarray: """ return self.base_module.prepare_data(X) - def restore_data(self, X: np.ndarray) -> np.ndarray: """ Restore data to the state prior to preparation. @@ -159,8 +158,9 @@ def restore_data(self, X: np.ndarray) -> np.ndarray: """ return self.base_module.restore_data(X) - - def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: + def category_choice( + self, i: np.ndarray, w: np.ndarray, params: dict + ) -> tuple[float, Optional[dict]]: """ Get the activation of the cluster. @@ -180,8 +180,13 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f """ 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]: + 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. @@ -203,8 +208,14 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt """ 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]: + 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. @@ -228,8 +239,13 @@ def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: """ 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: + def update( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + ) -> np.ndarray: """ Update the cluster weight. @@ -253,7 +269,6 @@ def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dic 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. @@ -273,7 +288,6 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: return self.base_module.new_weight(i, params) - def add_weight(self, new_w: np.ndarray): """ Add a new cluster weight. @@ -291,7 +305,6 @@ def add_weight(self, new_w: np.ndarray): self.weight_sample_counter_.append(1) self.W.append(new_w) - def prune(self, X: np.ndarray): """ Prune clusters based on the number of associated samples. @@ -301,15 +314,27 @@ def prune(self, X: np.ndarray): X : np.ndarray The input dataset. """ - a = np.array(self.weight_sample_counter_).reshape(-1,) >= self.phi + a = ( + np.array(self.weight_sample_counter_).reshape( + -1, + ) + >= self.phi + ) b = self._permanent_mask print(a.shape, b.shape) - self._permanent_mask += np.array(self.weight_sample_counter_).reshape(-1,) >= self.phi + self._permanent_mask += ( + np.array(self.weight_sample_counter_).reshape( + -1, + ) + >= self.phi + ) perm_labels = np.where(self._permanent_mask)[0] self.W = [w for w, pm in zip(self.W, self._permanent_mask) if pm] - self.weight_sample_counter_ = [self.weight_sample_counter_[i] for i in perm_labels] + self.weight_sample_counter_ = [ + self.weight_sample_counter_[i] for i in perm_labels + ] self.adjacency = self.adjacency[perm_labels][:, perm_labels] self._permanent_mask = self._permanent_mask[perm_labels] @@ -328,7 +353,6 @@ def prune(self, X: np.ndarray): else: self.labels_[i] = -1 - def post_step_fit(self, X: np.ndarray): """ Perform post-fit operations, such as cluster pruning, after fitting each sample. @@ -341,8 +365,13 @@ def post_step_fit(self, X: np.ndarray): 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: + 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. @@ -364,7 +393,7 @@ def _match_tracking(self, cache: dict, epsilon: float, params: dict, method: Lit """ M = cache["match_criterion"] if method == "MT+": - self.base_module.params["rho"] = M+epsilon + self.base_module.params["rho"] = M + epsilon return True elif method == "MT-": self.base_module.params["rho"] = M - epsilon @@ -380,7 +409,6 @@ 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. @@ -392,7 +420,6 @@ def _set_params(self, new_params): """ self.base_module.params = new_params - def _deep_copy_params(self) -> dict: """ Create a deep copy of the parameters. @@ -404,8 +431,13 @@ 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 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. @@ -434,31 +466,47 @@ def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None, m new_w = self.new_weight(x, self.params) self.add_weight(new_w) self.adjacency = np.zeros((1, 1), dtype=int) - self._permanent_mask = np.zeros((1, ), dtype=bool) + self._permanent_mask = np.zeros((1,), dtype=bool) return 0 else: - T_values, T_cache = zip(*[self.category_choice(x, w, params=self.base_module.params) for w in self.W]) + T_values, T_cache = zip( + *[ + self.category_choice(x, w, params=self.base_module.params) + for w in self.W + ] + ) T = np.array(T_values) while any(~np.isnan(T)): c_ = int(np.nanargmax(T)) w = self.W[c_] cache = T_cache[c_] - m, cache = self.match_criterion_bin(x, w, params=self.base_module.params, cache=cache, op=mt_operator) - no_match_reset = ( - match_reset_func is None or - match_reset_func(x, w, c_, params=self.base_module.params, cache=cache) + m, cache = self.match_criterion_bin( + x, + w, + params=self.base_module.params, + cache=cache, + op=mt_operator, + ) + no_match_reset = match_reset_func is None or match_reset_func( + x, w, c_, params=self.base_module.params, cache=cache ) if m and no_match_reset: if resonant_c < 0: params = self.base_module.params else: - params = dict(self.base_module.params, **{"beta": self.params["beta_lower"]}) - #TODO: make compatible with DualVigilanceART + params = dict( + self.base_module.params, + **{"beta": self.params["beta_lower"]}, + ) + # TODO: make compatible with DualVigilanceART new_w = self.update( x, w, params=params, - cache=dict((cache if cache else {}), **{"resonant_c": resonant_c, "current_c": c_}) + cache=dict( + (cache if cache else {}), + **{"resonant_c": resonant_c, "current_c": c_}, + ), ) self.set_weight(c_, new_w) if resonant_c < 0: @@ -470,7 +518,9 @@ def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None, m else: T[c_] = np.nan if not no_match_reset: - keep_searching = self._match_tracking(cache, epsilon, self.params, match_reset_method) + keep_searching = self._match_tracking( + cache, epsilon, self.params, match_reset_method + ) if not keep_searching: T[:] = np.nan @@ -483,7 +533,6 @@ def step_fit(self, x: np.ndarray, match_reset_func: Optional[Callable] = None, m return resonant_c - def get_cluster_centers(self) -> List[np.ndarray]: """ Get the centers of each cluster. @@ -495,7 +544,6 @@ def get_cluster_centers(self) -> List[np.ndarray]: """ return self.base_module.get_cluster_centers() - def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): """ Visualize the boundaries of each cluster. @@ -510,6 +558,10 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): Width of boundary lines. """ try: - self.base_module.plot_cluster_bounds(ax=ax, colors=colors, linewidth=linewidth) + self.base_module.plot_cluster_bounds( + ax=ax, colors=colors, linewidth=linewidth + ) except NotImplementedError: - warn(f"{self.base_module.__class__.__name__} does not support plotting cluster bounds.") \ No newline at end of file + warn( + f"{self.base_module.__class__.__name__} does not support plotting cluster bounds." + ) diff --git a/artlib/topological/__init__.py b/artlib/topological/__init__.py index c156898..352263b 100644 --- a/artlib/topological/__init__.py +++ b/artlib/topological/__init__.py @@ -12,4 +12,4 @@ `Topological clustering `_ -""" \ No newline at end of file +""" diff --git a/docs/source/artlib.biclustering.rst b/docs/source/artlib.biclustering.rst index 38d7512..a65b925 100644 --- a/docs/source/artlib.biclustering.rst +++ b/docs/source/artlib.biclustering.rst @@ -19,5 +19,3 @@ artlib.biclustering.BARTMAP module :members: :undoc-members: :show-inheritance: - - diff --git a/docs/source/artlib.common.rst b/docs/source/artlib.common.rst index 77a39ca..13cce7c 100644 --- a/docs/source/artlib.common.rst +++ b/docs/source/artlib.common.rst @@ -51,4 +51,3 @@ artlib.common.visualization module :members: :undoc-members: :show-inheritance: - diff --git a/docs/source/artlib.cvi.iCVIs.rst b/docs/source/artlib.cvi.iCVIs.rst index 023924e..4283789 100644 --- a/docs/source/artlib.cvi.iCVIs.rst +++ b/docs/source/artlib.cvi.iCVIs.rst @@ -19,5 +19,3 @@ artlib.cvi.iCVIs.CalinkskiHarabasz module :members: :undoc-members: :show-inheritance: - - diff --git a/docs/source/artlib.cvi.rst b/docs/source/artlib.cvi.rst index 8b22857..a79c0d7 100644 --- a/docs/source/artlib.cvi.rst +++ b/docs/source/artlib.cvi.rst @@ -35,5 +35,3 @@ artlib.cvi.iCVIFuzzyArt module :members: :undoc-members: :show-inheritance: - - diff --git a/docs/source/artlib.elementary.rst b/docs/source/artlib.elementary.rst index 87ec705..03f6a18 100644 --- a/docs/source/artlib.elementary.rst +++ b/docs/source/artlib.elementary.rst @@ -75,5 +75,3 @@ artlib.elementary.QuadraticNeuronART module :members: :undoc-members: :show-inheritance: - - diff --git a/docs/source/artlib.experimental.rst b/docs/source/artlib.experimental.rst index 3e991ae..760180f 100644 --- a/docs/source/artlib.experimental.rst +++ b/docs/source/artlib.experimental.rst @@ -35,5 +35,3 @@ artlib.experimental.merging module :members: :undoc-members: :show-inheritance: - - diff --git a/docs/source/artlib.fusion.rst b/docs/source/artlib.fusion.rst index 354cb6f..08eb2af 100644 --- a/docs/source/artlib.fusion.rst +++ b/docs/source/artlib.fusion.rst @@ -19,5 +19,3 @@ artlib.fusion.FusionART module :members: :undoc-members: :show-inheritance: - - diff --git a/docs/source/artlib.hierarchical.rst b/docs/source/artlib.hierarchical.rst index 1eb9acd..cd953fe 100644 --- a/docs/source/artlib.hierarchical.rst +++ b/docs/source/artlib.hierarchical.rst @@ -27,5 +27,3 @@ artlib.hierarchical.SMART module :members: :undoc-members: :show-inheritance: - - diff --git a/docs/source/artlib.reinforcement.rst b/docs/source/artlib.reinforcement.rst index c9aaa86..e68ca15 100644 --- a/docs/source/artlib.reinforcement.rst +++ b/docs/source/artlib.reinforcement.rst @@ -19,5 +19,3 @@ artlib.reinforcement.FALCON module :members: :undoc-members: :show-inheritance: - - diff --git a/docs/source/artlib.rst b/docs/source/artlib.rst index 8a27f50..83c0402 100644 --- a/docs/source/artlib.rst +++ b/docs/source/artlib.rst @@ -25,5 +25,3 @@ Subpackages artlib.reinforcement artlib.supervised artlib.topological - - diff --git a/docs/source/artlib.supervised.rst b/docs/source/artlib.supervised.rst index 882670e..d14e899 100644 --- a/docs/source/artlib.supervised.rst +++ b/docs/source/artlib.supervised.rst @@ -27,5 +27,3 @@ artlib.supervised.SimpleARTMAP module :members: :undoc-members: :show-inheritance: - - diff --git a/docs/source/artlib.topological.rst b/docs/source/artlib.topological.rst index f10b26a..e471d45 100644 --- a/docs/source/artlib.topological.rst +++ b/docs/source/artlib.topological.rst @@ -27,5 +27,3 @@ artlib.topological.TopoART module :members: :undoc-members: :show-inheritance: - - diff --git a/docs/source/available_models.rst b/docs/source/available_models.rst index fcc20cf..0803940 100644 --- a/docs/source/available_models.rst +++ b/docs/source/available_models.rst @@ -2,4 +2,3 @@ :parser: myst_parser.sphinx_ :start-after: :end-before: - diff --git a/docs/source/comparison.rst b/docs/source/comparison.rst index 3765d81..c128375 100644 --- a/docs/source/comparison.rst +++ b/docs/source/comparison.rst @@ -2,4 +2,3 @@ :parser: myst_parser.sphinx_ :start-after: :end-before: - diff --git a/docs/source/conf.py b/docs/source/conf.py index 1335c8b..89f8d81 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,52 +6,52 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = 'AdaptiveResonanceLib' -copyright = '2024, Niklas Melton' -author = 'Niklas Melton' -release = '0.1.2' +project = "AdaptiveResonanceLib" +copyright = "2024, Niklas Melton" +author = "Niklas Melton" +release = "0.1.2" # -- 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', - 'sphinxcontrib.bibtex', + "sphinx.ext.autodoc", + "autoapi.extension", + "sphinx.ext.napoleon", + "myst_parser", + "sphinx.ext.intersphinx", + "sphinxcontrib.bibtex", ] source_suffix = { - '.rst': 'restructuredtext', - '.md': 'markdown', + ".rst": "restructuredtext", + ".md": "markdown", } -templates_path = ['_templates'] -exclude_patterns = ['artlib/experimental/*', '../../artlib/experimental/*'] +templates_path = ["_templates"] +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/*'] -autoapi_python_class_content = 'both' +autoapi_type = "python" +autoapi_dirs = ["../../artlib"] # Adjust this to point to your source code directory +autoapi_ignore = ["*/experimental", "*/experimental/*"] +autoapi_python_class_content = "both" -bibtex_bibfiles = ['references.bib'] +bibtex_bibfiles = ["references.bib"] intersphinx_mapping = { - 'python': ('https://docs.python.org/3', None), - 'sklearn': ('https://scikit-learn.org/stable/', None) + "python": ("https://docs.python.org/3", None), + "sklearn": ("https://scikit-learn.org/stable/", None), } -suppress_warnings = ['ref.duplicate', 'duplicate.object', 'myst.duplicate_def', 'ref.python'] +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 -html_theme = 'sphinx_rtd_theme' -html_static_path = ['../_static'] - - - - - +html_theme = "sphinx_rtd_theme" +html_static_path = ["../_static"] diff --git a/docs/source/contact.rst b/docs/source/contact.rst index 0fc9e70..348e1f5 100644 --- a/docs/source/contact.rst +++ b/docs/source/contact.rst @@ -2,4 +2,3 @@ :parser: myst_parser.sphinx_ :start-after: :end-before: - diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 38baf90..4c0d940 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -2,4 +2,3 @@ :parser: myst_parser.sphinx_ :start-after: :end-before: - diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 5983c5b..270cc6a 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -2,4 +2,3 @@ :parser: myst_parser.sphinx_ :start-after: :end-before: - diff --git a/docs/source/index.rst b/docs/source/index.rst index 0fabef5..e740fae 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -37,4 +37,4 @@ AdaptiveResonanceLib Home artlib.hierarchical artlib.reinforcement artlib.supervised - artlib.topological \ No newline at end of file + artlib.topological diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 5a58eb8..339f40d 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -2,4 +2,3 @@ :parser: myst_parser.sphinx_ :start-after: :end-before: - diff --git a/docs/source/license.rst b/docs/source/license.rst index d8aa21e..51661c7 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -3,4 +3,3 @@ License .. include:: ../../LICENSE :parser: myst_parser.sphinx_ - diff --git a/docs/source/quick_start.rst b/docs/source/quick_start.rst index d9f1602..5b6db9c 100644 --- a/docs/source/quick_start.rst +++ b/docs/source/quick_start.rst @@ -2,4 +2,3 @@ :parser: myst_parser.sphinx_ :start-after: :end-before: - diff --git a/examples/demo_VAT.py b/examples/demo_VAT.py index 6caa12a..05f6919 100644 --- a/examples/demo_VAT.py +++ b/examples/demo_VAT.py @@ -6,7 +6,13 @@ def visualize_blobs(): - data, target = make_blobs(n_samples=2000, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=2000, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) t0 = time.time() @@ -14,14 +20,13 @@ def visualize_blobs(): dt = time.time() - t0 print("time:", dt) - plt.figure() - plt.scatter(data[:,0], data[:,1], c=P, cmap="jet", s=10) + plt.scatter(data[:, 0], data[:, 1], c=P, cmap="jet", s=10) plt.figure() plt.imshow(R) plt.show() -if __name__ == "__main__": +if __name__ == "__main__": visualize_blobs() diff --git a/examples/demo_art1.py b/examples/demo_art1.py index ff5c333..fb04ea3 100644 --- a/examples/demo_art1.py +++ b/examples/demo_art1.py @@ -5,16 +5,17 @@ def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) - data = ( data > 0.5).astype(int) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) + data = (data > 0.5).astype(int) print("Data has shape:", data.shape) - - params = { - "rho": 0.7, - "beta": 1.0, - "L": 1.0 - } + params = {"rho": 0.7, "beta": 1.0, "L": 1.0} cls = ART1(**params) X = cls.prepare_data(data) @@ -29,4 +30,4 @@ def cluster_blobs(): if __name__ == "__main__": - cluster_blobs() \ No newline at end of file + cluster_blobs() diff --git a/examples/demo_art2.py b/examples/demo_art2.py index 9149a2b..86e9d11 100644 --- a/examples/demo_art2.py +++ b/examples/demo_art2.py @@ -11,8 +11,15 @@ ================================================================== """ + def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=2, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=2, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) params = { diff --git a/examples/demo_artmap.py b/examples/demo_artmap.py index f40b581..8ccf75d 100644 --- a/examples/demo_artmap.py +++ b/examples/demo_artmap.py @@ -9,17 +9,15 @@ def cluster_iris(): from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report import umap.plot + data, target = load_iris(return_X_y=True) print("Data has shape:", data.shape) + X_train, X_test, y_train, y_test = train_test_split( + X, target, test_size=0.33, random_state=0 + ) - X_train, X_test, y_train, y_test = train_test_split(X, target, test_size=0.33, random_state=0) - - params = { - "rho": 0.0, - "alpha": 0.0, - "beta": 1.0 - } + params = {"rho": 0.0, "alpha": 0.0, "beta": 1.0} art = FuzzyART(**params) # params = { # "rho": 0.0, @@ -45,19 +43,23 @@ def cluster_iris(): mapper = umap.UMAP().fit(X_test) - umap.plot.points(mapper, labels=y_pred, color_key_cmap='Paired', background='black') + umap.plot.points( + mapper, labels=y_pred, color_key_cmap="Paired", background="black" + ) umap.plot.plt.show() def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) - params = { - "rho": 0.9, - "alpha": 0.0, - "beta": 1.0 - } + params = {"rho": 0.9, "alpha": 0.0, "beta": 1.0} art = FuzzyART(**params) # params = { @@ -89,6 +91,5 @@ def main(): cluster_blobs() - if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/demo_bartmap.py b/examples/demo_bartmap.py index 8da0e97..aae8f28 100644 --- a/examples/demo_bartmap.py +++ b/examples/demo_bartmap.py @@ -7,23 +7,19 @@ def cluster_checkerboard(): n_clusters = (4, 3) data, rows, columns = make_checkerboard( - shape=(300, 300), n_clusters=n_clusters, noise=10, shuffle=False, random_state=42 + shape=(300, 300), + n_clusters=n_clusters, + noise=10, + shuffle=False, + random_state=42, ) print("Data has shape:", data.shape) - params_a = { - "rho": 0.6, - "alpha": 0.0, - "beta": 1.0 - } - params_b = { - "rho": 0.6, - "alpha": 0.0, - "beta": 1.0 - } + params_a = {"rho": 0.6, "alpha": 0.0, "beta": 1.0} + params_b = {"rho": 0.6, "alpha": 0.0, "beta": 1.0} art_a = FuzzyART(**params_a) art_b = FuzzyART(**params_b) - cls = BARTMAP(art_a, art_b, eta=-1.) + cls = BARTMAP(art_a, art_b, eta=-1.0) X = data diff --git a/examples/demo_bayesian_art.py b/examples/demo_bayesian_art.py index 86df104..6ba02c5 100644 --- a/examples/demo_bayesian_art.py +++ b/examples/demo_bayesian_art.py @@ -15,7 +15,13 @@ def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) params = { @@ -27,7 +33,6 @@ def cluster_blobs(): X = cls.prepare_data(data) print("Prepared data has shape:", X.shape) - y = cls.fit_predict(X) print(f"{cls.n_clusters} clusters found") @@ -37,4 +42,4 @@ def cluster_blobs(): if __name__ == "__main__": - cluster_blobs() \ No newline at end of file + cluster_blobs() diff --git a/examples/demo_convex_hull_art.py b/examples/demo_convex_hull_art.py index 88c2073..1642553 100644 --- a/examples/demo_convex_hull_art.py +++ b/examples/demo_convex_hull_art.py @@ -7,14 +7,16 @@ def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) - - params = { - "rho": 0.85, - "merge_rho": 0.8 - } + params = {"rho": 0.85, "merge_rho": 0.8} cls = ConvexHullART(**params) X = cls.prepare_data(data) diff --git a/examples/demo_cvi_art.py b/examples/demo_cvi_art.py index d308443..b8d67b5 100644 --- a/examples/demo_cvi_art.py +++ b/examples/demo_cvi_art.py @@ -5,7 +5,13 @@ def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) params = { @@ -28,4 +34,4 @@ def cluster_blobs(): if __name__ == "__main__": - cluster_blobs() \ No newline at end of file + cluster_blobs() diff --git a/examples/demo_dual_vigilance_art.py b/examples/demo_dual_vigilance_art.py index 578e121..eb83699 100644 --- a/examples/demo_dual_vigilance_art.py +++ b/examples/demo_dual_vigilance_art.py @@ -5,14 +5,16 @@ def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) - params = { - "rho": 0.85, - "alpha": 0.8, - "beta": 1.0 - } + params = {"rho": 0.85, "alpha": 0.8, "beta": 1.0} base_art = FuzzyART(**params) cls = DualVigilanceART(base_art, 0.78) @@ -29,4 +31,4 @@ def cluster_blobs(): if __name__ == "__main__": - cluster_blobs() \ No newline at end of file + cluster_blobs() diff --git a/examples/demo_ellipsoid_art.py b/examples/demo_ellipsoid_art.py index bf1b2b0..28f447a 100644 --- a/examples/demo_ellipsoid_art.py +++ b/examples/demo_ellipsoid_art.py @@ -5,16 +5,16 @@ def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) - params = { - "rho": 0.01, - "alpha": 0.0, - "beta": 1.0, - "r_hat": 0.65, - "mu": 0.8 - } + params = {"rho": 0.01, "alpha": 0.0, "beta": 1.0, "r_hat": 0.65, "mu": 0.8} cls = EllipsoidART(**params) X = cls.prepare_data(data) @@ -29,4 +29,4 @@ def cluster_blobs(): if __name__ == "__main__": - cluster_blobs() \ No newline at end of file + cluster_blobs() diff --git a/examples/demo_fusion_art.py b/examples/demo_fusion_art.py index 01c6747..ce9b858 100644 --- a/examples/demo_fusion_art.py +++ b/examples/demo_fusion_art.py @@ -6,20 +6,24 @@ def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) - data_channel_a = data[:,0].reshape((-1,1)) - data_channel_b = data[:,1].reshape((-1,1)) + data_channel_a = data[:, 0].reshape((-1, 1)) + data_channel_b = data[:, 1].reshape((-1, 1)) - params = { - "rho": 0.5, - "alpha": 0.0, - "beta": 1.0 - } + params = {"rho": 0.5, "alpha": 0.0, "beta": 1.0} art_a = FuzzyART(**params) art_b = FuzzyART(**params) - cls = FusionART([art_a, art_b], gamma_values=np.array([0.5, 0.5]), channel_dims=[2,2]) + cls = FusionART( + [art_a, art_b], gamma_values=np.array([0.5, 0.5]), channel_dims=[2, 2] + ) X_channel_a = art_a.prepare_data(data_channel_a) X_channel_b = art_b.prepare_data(data_channel_b) @@ -36,4 +40,4 @@ def cluster_blobs(): if __name__ == "__main__": - cluster_blobs() \ No newline at end of file + cluster_blobs() diff --git a/examples/demo_fuzzy_art.py b/examples/demo_fuzzy_art.py index 29be523..e647638 100644 --- a/examples/demo_fuzzy_art.py +++ b/examples/demo_fuzzy_art.py @@ -5,14 +5,16 @@ def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) - params = { - "rho": 0.5, - "alpha": 0.0, - "beta": 1.0 - } + params = {"rho": 0.5, "alpha": 0.0, "beta": 1.0} cls = FuzzyART(**params) X = cls.prepare_data(data) @@ -27,4 +29,4 @@ def cluster_blobs(): if __name__ == "__main__": - cluster_blobs() \ No newline at end of file + cluster_blobs() diff --git a/examples/demo_gaussian_art.py b/examples/demo_gaussian_art.py index f384a2f..ae8d1cd 100644 --- a/examples/demo_gaussian_art.py +++ b/examples/demo_gaussian_art.py @@ -6,7 +6,13 @@ def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) params = { @@ -27,4 +33,4 @@ def cluster_blobs(): if __name__ == "__main__": - cluster_blobs() \ No newline at end of file + cluster_blobs() diff --git a/examples/demo_grid_search_cv.py b/examples/demo_grid_search_cv.py index a63fb58..418ae6f 100644 --- a/examples/demo_grid_search_cv.py +++ b/examples/demo_grid_search_cv.py @@ -9,18 +9,15 @@ def grid_search_blobs(): data, target = load_iris(return_X_y=True) print("Data has shape:", data.shape) - base_params = { - "rho": 0.3, - "alpha": 0.0, - "beta": 1.0, - "r_hat": 0.8 - } + base_params = {"rho": 0.3, "alpha": 0.0, "beta": 1.0, "r_hat": 0.8} art = HypersphereART(**base_params) X = art.prepare_data(data) print("Prepared data has shape:", X.shape) - X_train, X_test, y_train, y_test = train_test_split(X, target, test_size=0.33, random_state=0) + X_train, X_test, y_train, y_test = train_test_split( + X, target, test_size=0.33, random_state=0 + ) cls = SimpleARTMAP(art) # print(cls.get_params(deep=False)) @@ -30,7 +27,7 @@ def grid_search_blobs(): "module_a__rho": [0.0, 0.25, 0.5, 0.7, 0.9], "module_a__alpha": [0.0], "module_a__beta": [1.0], - "module_a__r_hat": [0.8] + "module_a__r_hat": [0.8], } grid = GridSearchCV(cls, param_grid, refit=True, verbose=3, n_jobs=1) diff --git a/examples/demo_grid_search_cv_elementary.py b/examples/demo_grid_search_cv_elementary.py index c6b1e13..3781f59 100644 --- a/examples/demo_grid_search_cv_elementary.py +++ b/examples/demo_grid_search_cv_elementary.py @@ -15,7 +15,13 @@ def grid_search_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) base_params = { @@ -41,16 +47,7 @@ def grid_search_blobs(): verbose=3, n_jobs=1, scoring="adjusted_rand_score", - cv=[ - ( - np.array( - list(range(len(X))) - ), - np.array( - list(range(len(X))) - ) - ) - ] + cv=[(np.array(list(range(len(X)))), np.array(list(range(len(X)))))], ) grid.fit(X, target) diff --git a/examples/demo_hypersphere_art.py b/examples/demo_hypersphere_art.py index 99c7a36..be49324 100644 --- a/examples/demo_hypersphere_art.py +++ b/examples/demo_hypersphere_art.py @@ -5,15 +5,16 @@ def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) - params = { - "rho": 0.5, - "alpha": 0.0, - "beta": 1.0, - "r_hat": 0.8 - } + params = {"rho": 0.5, "alpha": 0.0, "beta": 1.0, "r_hat": 0.8} cls = HypersphereART(**params) X = cls.prepare_data(data) @@ -28,4 +29,4 @@ def cluster_blobs(): if __name__ == "__main__": - cluster_blobs() \ No newline at end of file + cluster_blobs() diff --git a/examples/demo_icvi_art.py b/examples/demo_icvi_art.py index 3919708..aef19f2 100644 --- a/examples/demo_icvi_art.py +++ b/examples/demo_icvi_art.py @@ -5,14 +5,20 @@ def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) params = { "rho": 0.0, "alpha": 0.0, "beta": 1.0, - "validity": iCVIFuzzyART.CALINSKIHARABASZ + "validity": iCVIFuzzyART.CALINSKIHARABASZ, } cls = iCVIFuzzyART(**params) @@ -28,4 +34,4 @@ def cluster_blobs(): if __name__ == "__main__": - cluster_blobs() \ No newline at end of file + cluster_blobs() diff --git a/examples/demo_quadratic_neuron_art.py b/examples/demo_quadratic_neuron_art.py index c90cefb..b283205 100644 --- a/examples/demo_quadratic_neuron_art.py +++ b/examples/demo_quadratic_neuron_art.py @@ -5,16 +5,16 @@ def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) - params = { - "rho": 0.9, - "s_init": 0.9, - "lr_b": 0.1, - "lr_w": 0.1, - "lr_s": 0.1 - } + params = {"rho": 0.9, "s_init": 0.9, "lr_b": 0.1, "lr_w": 0.1, "lr_s": 0.1} cls = QuadraticNeuronART(**params) X = cls.prepare_data(data) @@ -29,4 +29,4 @@ def cluster_blobs(): if __name__ == "__main__": - cluster_blobs() \ No newline at end of file + cluster_blobs() diff --git a/examples/demo_regression.py b/examples/demo_regression.py index d4c1a64..bc01467 100644 --- a/examples/demo_regression.py +++ b/examples/demo_regression.py @@ -4,25 +4,22 @@ from artlib import FuzzyART, ARTMAP, FusionART, normalize + def make_data(): - x_ = np.arange(0.0,4.0, 0.01) - x = x_+0.5 - y = np.sin(x)+np.cos(x**2)+np.log(x**3)+np.cos(x)*np.log(x**2) + x_ = np.arange(0.0, 4.0, 0.01) + x = x_ + 0.5 + y = np.sin(x) + np.cos(x**2) + np.log(x**3) + np.cos(x) * np.log(x**2) x_norm, _, _ = normalize(x) y_norm, _, _ = normalize(y) - return x_norm.reshape((-1,1)), y_norm.reshape((-1,1)) + return x_norm.reshape((-1, 1)), y_norm.reshape((-1, 1)) def fit_regression_ARTMAP(): X_, y_ = make_data() print("Data has shape:", X_.shape) - params = { - "rho": 0.95, - "alpha": 0.0, - "beta": 1.0 - } + params = {"rho": 0.95, "alpha": 0.0, "beta": 1.0} art_a = FuzzyART(**params) art_b = FuzzyART(**params) @@ -38,20 +35,17 @@ def fit_regression_ARTMAP(): y_pred = cls.predict_regression(X) - plt.plot(X_, y_, 'r-', label="original") + plt.plot(X_, y_, "r-", label="original") plt.plot(X_, y_pred, "b-", label="ARTMAP") plt.legend() plt.show() + def fit_regression_FusionART(): X_, y_ = make_data() print("Data has shape:", X_.shape) - params = { - "rho": 0.95, - "alpha": 0.0, - "beta": 1.0 - } + params = {"rho": 0.95, "alpha": 0.0, "beta": 1.0} art_a = FuzzyART(**params) art_b = FuzzyART(**params) @@ -59,7 +53,9 @@ def fit_regression_FusionART(): y = art_b.prepare_data(y_) print("Prepared data has shape:", X.shape) - cls = FusionART([art_a, art_b], gamma_values=np.array([0.5, 0.5]), channel_dims=[2, 2]) + cls = FusionART( + [art_a, art_b], gamma_values=np.array([0.5, 0.5]), channel_dims=[2, 2] + ) Xy = cls.join_channel_data(channel_data=[X, y]) @@ -71,16 +67,16 @@ def fit_regression_FusionART(): y_pred = cls.predict_regression(X_no_y, target_channels=[-1]) - plt.plot(X_, y_, 'r-', label="original") + plt.plot(X_, y_, "r-", label="original") plt.plot(X_, y_pred, "b-", label="FusionART") plt.legend() plt.show() + def main(): # fit_regression_ARTMAP() fit_regression_FusionART() - if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/demo_reinforcement_learning.py b/examples/demo_reinforcement_learning.py index faa0172..2051768 100644 --- a/examples/demo_reinforcement_learning.py +++ b/examples/demo_reinforcement_learning.py @@ -6,6 +6,7 @@ import matplotlib.pyplot as plt from copy import deepcopy + # only works with Fuzzy ART based FALCON def prune_clusters(cls): # get existing state and action weights @@ -19,24 +20,29 @@ def prune_clusters(cls): # identify unique combinations unique_combined, indices = np.unique(combined, axis=0, return_inverse=True) # get mean reward prediction for each unique state-action pair - unique_rewards = np.array([reward[indices == i, :].mean() for i in range(len(unique_combined))]) + unique_rewards = np.array( + [reward[indices == i, :].mean() for i in range(len(unique_combined))] + ) # split unique state-action pairs - unique_states = unique_combined[:, :state.shape[1]] - unique_actions = unique_combined[:, state.shape[1]:] + unique_states = unique_combined[:, : state.shape[1]] + unique_actions = unique_combined[:, state.shape[1] :] # update model to only have unique state-action pairs and their average rewards cls.fusion_art.modules[0].W = [row for row in unique_states] cls.fusion_art.modules[1].W = [row for row in unique_actions] - cls.fusion_art.modules[2].W = [row for row in compliment_code(unique_rewards.reshape((-1, 1)))] + cls.fusion_art.modules[2].W = [ + row for row in compliment_code(unique_rewards.reshape((-1, 1))) + ] return cls + def update_FALCON(records, cls, shrink_ratio): # convert records into arrays - states = np.array(records["states"]).reshape((-1,1)) - actions = np.array(records["actions"]).reshape((-1,1)) - rewards = np.array(records["rewards"]).reshape((-1,1)) + states = np.array(records["states"]).reshape((-1, 1)) + actions = np.array(records["actions"]).reshape((-1, 1)) + rewards = np.array(records["rewards"]).reshape((-1, 1)) # compliment code states and actions states_cc = compliment_code(states) @@ -56,17 +62,33 @@ def update_FALCON(records, cls, shrink_ratio): # fit FALCON to data # data = cls.fusion_art.join_channel_data([states_fit, actions_fit, sarsa_rewards_fit]) # cls.fusion_art = cls.fusion_art.partial_fit(data) - cls = cls.partial_fit(states_cc, actions_cc, rewards_cc, single_sample_reward=1.0) + cls = cls.partial_fit( + states_cc, actions_cc, rewards_cc, single_sample_reward=1.0 + ) for m in range(3): - cls.fusion_art.modules[m] = cls.fusion_art.modules[m].shrink_clusters(shrink_ratio) + cls.fusion_art.modules[m] = cls.fusion_art.modules[m].shrink_clusters( + shrink_ratio + ) return cls -def training_cycle(cls, epochs, steps, sarsa_alpha, sarsa_gamma, render_mode=None, shrink_ratio=0.1, explore_rate=0.0, checkpoint_frequency=50, early_stopping_reward=-20): + +def training_cycle( + cls, + epochs, + steps, + sarsa_alpha, + sarsa_gamma, + render_mode=None, + shrink_ratio=0.1, + explore_rate=0.0, + checkpoint_frequency=50, + early_stopping_reward=-20, +): # create the environment - env = gym.make('CliffWalking-v0', render_mode=render_mode) + env = gym.make("CliffWalking-v0", render_mode=render_mode) # define some constants - ACTION_SPACE = np.array([[0], [1.], [2.], [3.]]) + ACTION_SPACE = np.array([[0], [1.0], [2.0], [3.0]]) STATE_MAX = 47 ACTION_MAX = 3 REWARD_MAX = 150 @@ -87,11 +109,15 @@ def training_cycle(cls, epochs, steps, sarsa_alpha, sarsa_gamma, render_mode=Non past_states = [] for _ in range(steps): # get an action - observation_cc = compliment_code(np.array([n_observation]).reshape(1, -1)) + observation_cc = compliment_code( + np.array([n_observation]).reshape(1, -1) + ) if np.random.random() < explore_rate: action = int(np.random.choice(ACTION_SPACE.flatten())) else: - cls_action = cls.get_action(observation_cc, action_space=ACTION_SPACE, optimality="min") + cls_action = cls.get_action( + observation_cc, action_space=ACTION_SPACE, optimality="min" + ) action = int(cls_action[0]) # normalize state and action n_observation = observation / STATE_MAX @@ -121,14 +147,16 @@ def training_cycle(cls, epochs, steps, sarsa_alpha, sarsa_gamma, render_mode=Non # train FALCON cls = update_FALCON(records, cls, shrink_ratio) # record sum of rewards generated during this epoch - reward_history.append(-sum(records["rewards"])*REWARD_MAX) + reward_history.append(-sum(records["rewards"]) * REWARD_MAX) # if this isnt random exploration if explore_rate < 1.0: # see if we should save a checkpoint - if (e+1)%checkpoint_frequency == 0 or e == epochs-1: + if (e + 1) % checkpoint_frequency == 0 or e == epochs - 1: # test model - cls, test_reward_history = demo_cycle(cls, 1, steps, render_mode=None) + cls, test_reward_history = demo_cycle( + cls, 1, steps, render_mode=None + ) # check if our current model is better than the best previous model if test_reward_history[0] >= best_reward or best_cls is None: # same a checkpoint @@ -137,13 +165,13 @@ def training_cycle(cls, epochs, steps, sarsa_alpha, sarsa_gamma, render_mode=Non # check early stopping condition if best_reward > early_stopping_reward: # show current best reward on progress bar - pbar.set_postfix({'Best Reward': best_reward}) + pbar.set_postfix({"Best Reward": best_reward}) return cls, reward_history else: # restore previous best model cls = deepcopy(best_cls) # show current best reward on progress bar - pbar.set_postfix({'Best Reward': best_reward}) + pbar.set_postfix({"Best Reward": best_reward}) env.close() return cls, reward_history @@ -151,9 +179,9 @@ def training_cycle(cls, epochs, steps, sarsa_alpha, sarsa_gamma, render_mode=Non def demo_cycle(cls, epochs, steps, render_mode=None): # create the environment - env = gym.make('CliffWalking-v0', render_mode=render_mode) + env = gym.make("CliffWalking-v0", render_mode=render_mode) # define some constants - ACTION_SPACE = np.array([[0], [1.], [2.], [3.]]) + ACTION_SPACE = np.array([[0], [1.0], [2.0], [3.0]]) STATE_MAX = 47 ACTION_MAX = 3 REWARD_MAX = 150 @@ -167,10 +195,13 @@ def demo_cycle(cls, epochs, steps, render_mode=None): records = {"states": [], "actions": [], "rewards": []} past_states = [] for _ in range(steps): - # get an action - observation_cc = compliment_code(np.array([n_observation]).reshape(1, -1)) - cls_action = cls.get_action(observation_cc, action_space=ACTION_SPACE, optimality="min") + observation_cc = compliment_code( + np.array([n_observation]).reshape(1, -1) + ) + cls_action = cls.get_action( + observation_cc, action_space=ACTION_SPACE, optimality="min" + ) action = int(cls_action[0]) # normalize state and action @@ -198,7 +229,7 @@ def demo_cycle(cls, epochs, steps, render_mode=None): if terminated or truncated or reward == -100: break # record sum of rewards generated during this epoch - reward_history.append(-sum(records["rewards"])*REWARD_MAX) + reward_history.append(-sum(records["rewards"]) * REWARD_MAX) env.close() return cls, reward_history @@ -206,7 +237,7 @@ def demo_cycle(cls, epochs, steps, render_mode=None): def max_up_to_each_element(lst): max_list = [] - current_max = float('-inf') # Start with the smallest possible value + current_max = float("-inf") # Start with the smallest possible value for num in lst: current_max = max(current_max, num) max_list.append(current_max) @@ -216,18 +247,46 @@ def max_up_to_each_element(lst): def train_FALCON(): # define training regimen training_regimen = [ - {"name": "random", "epochs": 1000, "shrink_ratio": 0.3, "gamma": 0.0, "explore_rate": 1.0, "render_mode": None}, - {"name": "explore 33%", "epochs": 500, "shrink_ratio": 0.3, "gamma": 0.2, "explore_rate": 0.333, "render_mode": None}, - {"name": "explore 20%", "epochs": 500, "shrink_ratio": 0.3, "gamma": 0.2, "explore_rate": 0.20, "render_mode": None}, - {"name": "explore 5%", "epochs": 1000, "shrink_ratio": 0.3, "gamma": 0.2, "explore_rate": 0.05, "render_mode": None}, + { + "name": "random", + "epochs": 1000, + "shrink_ratio": 0.3, + "gamma": 0.0, + "explore_rate": 1.0, + "render_mode": None, + }, + { + "name": "explore 33%", + "epochs": 500, + "shrink_ratio": 0.3, + "gamma": 0.2, + "explore_rate": 0.333, + "render_mode": None, + }, + { + "name": "explore 20%", + "epochs": 500, + "shrink_ratio": 0.3, + "gamma": 0.2, + "explore_rate": 0.20, + "render_mode": None, + }, + { + "name": "explore 5%", + "epochs": 1000, + "shrink_ratio": 0.3, + "gamma": 0.2, + "explore_rate": 0.05, + "render_mode": None, + }, ] MAX_STEPS = 25 SARSA_ALPHA = 1.0 # define parameters for state, action, and reward modules - art_state = FuzzyART(rho=0.99,alpha=0.01, beta=1.0) - art_action = FuzzyART(rho=0.99,alpha=0.01, beta=1.0) - art_reward = FuzzyART(rho=0.0,alpha=0.01, beta=1.0) + art_state = FuzzyART(rho=0.99, alpha=0.01, beta=1.0) + art_action = FuzzyART(rho=0.99, alpha=0.01, beta=1.0) + art_reward = FuzzyART(rho=0.0, alpha=0.01, beta=1.0) # instantiate FALCON cls = TD_FALCON(art_state, art_action, art_reward, channel_dims=[2, 2, 2]) # record rewards for each epoch @@ -244,13 +303,15 @@ def train_FALCON(): sarsa_gamma=regimen["gamma"], render_mode=regimen["render_mode"], shrink_ratio=regimen["shrink_ratio"], - explore_rate=regimen["explore_rate"] + explore_rate=regimen["explore_rate"], ) all_rewards.extend(reward_history) - print("MAX REWARD:",max(reward_history)) + print("MAX REWARD:", max(reward_history)) # demo learned policy - cls, reward_history = demo_cycle(cls, epochs=2, steps=25, render_mode="human") + cls, reward_history = demo_cycle( + cls, epochs=2, steps=25, render_mode="human" + ) print(reward_history) all_rewards.extend(reward_history) max_rewards = max_up_to_each_element(all_rewards) @@ -264,7 +325,8 @@ def train_FALCON(): plt.title("Rewards over Time") plt.show() + if __name__ == "__main__": # This takes approximately 3 minutes np.random.seed(42) - train_FALCON() \ No newline at end of file + train_FALCON() diff --git a/examples/demo_seq_art.py b/examples/demo_seq_art.py index 1828c77..c9369cf 100644 --- a/examples/demo_seq_art.py +++ b/examples/demo_seq_art.py @@ -18,12 +18,11 @@ "substitution", ] + def cluster_sequences(): X = np.array(sequences, dtype=object) - params = { - "rho": -0.5 - } + params = {"rho": -0.5} cls = SeqART(**params) y = cls.fit_predict(X) @@ -41,5 +40,3 @@ def cluster_sequences(): if __name__ == "__main__": cluster_sequences() - - diff --git a/examples/demo_smart.py b/examples/demo_smart.py index 11f76cc..a6ad002 100644 --- a/examples/demo_smart.py +++ b/examples/demo_smart.py @@ -5,13 +5,16 @@ def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) - base_params = { - "alpha": 0.0, - "beta": 1.0 - } + base_params = {"alpha": 0.0, "beta": 1.0} rho_values = [0.5, 0.85, 0.9] cls = SMART(FuzzyART, rho_values, base_params) @@ -29,6 +32,5 @@ def main(): cluster_blobs() - if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/demo_topo_art.py b/examples/demo_topo_art.py index 3e7bda7..b25d2f5 100644 --- a/examples/demo_topo_art.py +++ b/examples/demo_topo_art.py @@ -5,14 +5,16 @@ def cluster_blobs(): - data, target = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) - params = { - "rho": 0.4, - "alpha": 0.8, - "beta": 1.0 - } + params = {"rho": 0.4, "alpha": 0.8, "beta": 1.0} base_art = FuzzyART(**params) X = base_art.prepare_data(data) @@ -33,4 +35,4 @@ def cluster_blobs(): if __name__ == "__main__": - cluster_blobs() \ No newline at end of file + cluster_blobs() diff --git a/scripts/comparison_of_methods.py b/scripts/comparison_of_methods.py index fedddca..e71cce6 100644 --- a/scripts/comparison_of_methods.py +++ b/scripts/comparison_of_methods.py @@ -17,23 +17,36 @@ n_samples = 500 seed = 30 noisy_circles = datasets.make_circles( - n_samples=n_samples, factor=0.5, noise=0.05, random_state=seed, shuffle=False + n_samples=n_samples, + factor=0.5, + noise=0.05, + random_state=seed, + shuffle=False, +) +noisy_moons = datasets.make_moons( + n_samples=n_samples, noise=0.05, random_state=seed, shuffle=False +) +blobs = datasets.make_blobs( + n_samples=n_samples, random_state=seed, shuffle=False ) -noisy_moons = datasets.make_moons(n_samples=n_samples, noise=0.05, random_state=seed, shuffle=False) -blobs = datasets.make_blobs(n_samples=n_samples, random_state=seed, shuffle=False) rng = np.random.RandomState(seed) no_structure = rng.rand(n_samples, 2), None # Anisotropicly distributed data random_state = 170 -X, y = datasets.make_blobs(n_samples=n_samples, random_state=random_state, shuffle=False) +X, y = datasets.make_blobs( + n_samples=n_samples, random_state=random_state, shuffle=False +) transformation = [[0.6, -0.6], [-0.4, 0.8]] X_aniso = np.dot(X, transformation) aniso = (X_aniso, y) # blobs with varied variances varied = datasets.make_blobs( - n_samples=n_samples, cluster_std=[1.0, 2.5, 0.5], random_state=random_state, shuffle=False + n_samples=n_samples, + cluster_std=[1.0, 2.5, 0.5], + random_state=random_state, + shuffle=False, ) # ============ @@ -50,335 +63,229 @@ ( noisy_circles, { - "BayesianART": - { - "rho": 9e-5, - "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]]), - }, - "DualVigilanceART": - { - "FuzzyART": - { - "rho": 0.95, - - "alpha": 0.8, - "beta": 1.0 - }, - "rho_lower_bound": 0.9, - - }, - "EllipsoidART": - { - "rho": 0.1, - "alpha": 1.0, - "beta": 1.0, - "r_hat": 0.9, - "mu": 0.3 - }, - "FuzzyART": - { - "rho": 0.266, - "alpha": 0.15, - "beta": 1.0 - }, - "GaussianART": - { - "rho": 0.1, - "sigma_init": np.array([0.5, 0.5]), - }, - "HypersphereART": - { - "rho": 0.433, - "alpha": 0.7, - "beta": 1.0, - "r_hat": 0.8 - }, - "QuadraticNeuronART": - { - "rho": 0.99, - "s_init": 0.8, - "lr_b": 0.2, - "lr_w": 0.1, - "lr_s": 0.2 - }, + "BayesianART": { + "rho": 9e-5, + "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]]), + }, + "DualVigilanceART": { + "FuzzyART": {"rho": 0.95, "alpha": 0.8, "beta": 1.0}, + "rho_lower_bound": 0.9, + }, + "EllipsoidART": { + "rho": 0.1, + "alpha": 1.0, + "beta": 1.0, + "r_hat": 0.9, + "mu": 0.3, + }, + "FuzzyART": {"rho": 0.266, "alpha": 0.15, "beta": 1.0}, + "GaussianART": { + "rho": 0.1, + "sigma_init": np.array([0.5, 0.5]), + }, + "HypersphereART": { + "rho": 0.433, + "alpha": 0.7, + "beta": 1.0, + "r_hat": 0.8, + }, + "QuadraticNeuronART": { + "rho": 0.99, + "s_init": 0.8, + "lr_b": 0.2, + "lr_w": 0.1, + "lr_s": 0.2, + }, }, ), ( noisy_moons, { - "BayesianART": - { - "rho": 9e-5, - "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]]), - }, - "DualVigilanceART": - { - "FuzzyART": - { - "rho": 0.85, - - "alpha": 0.8, - "beta": 1.0 - }, - "rho_lower_bound": 0.78, - - }, - "EllipsoidART": - { - "rho": 0.166, - "alpha": 0.0, - "beta": 1.0, - "r_hat": 0.9, - "mu": 0.4 - }, - "FuzzyART": - { - "rho": 0.33, - "alpha": 0.0, - "beta": 1.0 - }, - "GaussianART": - { - "rho": 0.133, - "sigma_init": np.array([0.5, 0.5]), - }, - "HypersphereART": - { - "rho": 0.2, - "alpha": 0.7, - "beta": 1.0, - "r_hat": 0.7 - }, - "QuadraticNeuronART": - { - "rho": 0.9, - "s_init": 0.9, - "lr_b": 0.1, - "lr_w": 0.1, - "lr_s": 0.1 - }, + "BayesianART": { + "rho": 9e-5, + "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]]), + }, + "DualVigilanceART": { + "FuzzyART": {"rho": 0.85, "alpha": 0.8, "beta": 1.0}, + "rho_lower_bound": 0.78, + }, + "EllipsoidART": { + "rho": 0.166, + "alpha": 0.0, + "beta": 1.0, + "r_hat": 0.9, + "mu": 0.4, + }, + "FuzzyART": {"rho": 0.33, "alpha": 0.0, "beta": 1.0}, + "GaussianART": { + "rho": 0.133, + "sigma_init": np.array([0.5, 0.5]), + }, + "HypersphereART": { + "rho": 0.2, + "alpha": 0.7, + "beta": 1.0, + "r_hat": 0.7, + }, + "QuadraticNeuronART": { + "rho": 0.9, + "s_init": 0.9, + "lr_b": 0.1, + "lr_w": 0.1, + "lr_s": 0.1, + }, }, ), ( varied, { - "BayesianART": - { - "rho": 3e-4, - "cov_init": np.array([[1e-3, 0.0], [0.0, 1e-3]]), - }, - "DualVigilanceART": - { - "FuzzyART": - { - "rho": 0.95, - "alpha": 0.8, - "beta": 1.0 - }, - "rho_lower_bound": 0.88, - - }, - "EllipsoidART": - { - "rho": 0.05, - "alpha": 0.0, - "beta": 1.0, - "r_hat": 0.7, - "mu": 1.0 - }, - "FuzzyART": - { - "rho": 0.4, - "alpha": 0.1, - "beta": 1.0 - }, - "GaussianART": - { - "rho": 0.08, - "sigma_init": np.array([0.5, 0.5]), - }, - "HypersphereART": - { - "rho": 0.2, - "alpha": 0.0, - "beta": 1.0, - "r_hat": 0.3 - }, - "QuadraticNeuronART": - { - "rho": 0.933, - "s_init": 0.9, - "lr_b": 0.1, - "lr_w": 0.1, - "lr_s": 0.1 - }, + "BayesianART": { + "rho": 3e-4, + "cov_init": np.array([[1e-3, 0.0], [0.0, 1e-3]]), + }, + "DualVigilanceART": { + "FuzzyART": {"rho": 0.95, "alpha": 0.8, "beta": 1.0}, + "rho_lower_bound": 0.88, + }, + "EllipsoidART": { + "rho": 0.05, + "alpha": 0.0, + "beta": 1.0, + "r_hat": 0.7, + "mu": 1.0, + }, + "FuzzyART": {"rho": 0.4, "alpha": 0.1, "beta": 1.0}, + "GaussianART": { + "rho": 0.08, + "sigma_init": np.array([0.5, 0.5]), + }, + "HypersphereART": { + "rho": 0.2, + "alpha": 0.0, + "beta": 1.0, + "r_hat": 0.3, + }, + "QuadraticNeuronART": { + "rho": 0.933, + "s_init": 0.9, + "lr_b": 0.1, + "lr_w": 0.1, + "lr_s": 0.1, + }, }, ), ( aniso, { - "BayesianART": - { - "rho": 2e-5, - "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]]), - }, - "DualVigilanceART": - { - "FuzzyART": - { - "rho": 0.95, - - "alpha": 0.8, - "beta": 1.0 - }, - "rho_lower_bound": 0.9, - - }, - "EllipsoidART": - { - "rho": 5e-5, - "alpha": 0.0, - "beta": 1.0, - "r_hat": 0.9, - "mu": 0.9 - }, - "FuzzyART": - { - "rho": 0.5667, - "alpha": 0.4, - "beta": 1.0 - }, - "GaussianART": - { - "rho": 0.3, - "sigma_init": np.array([0.5, 0.5]), - }, - "HypersphereART": - { - "rho": 0.1, - "alpha": 0.05, - "beta": 1.0, - "r_hat": 0.4667 - }, - "QuadraticNeuronART": - { - "rho": 0.933, - "s_init": 0.8, - "lr_b": 0.4, - "lr_w": 0.1, - "lr_s": 0.2 - }, + "BayesianART": { + "rho": 2e-5, + "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]]), + }, + "DualVigilanceART": { + "FuzzyART": {"rho": 0.95, "alpha": 0.8, "beta": 1.0}, + "rho_lower_bound": 0.9, + }, + "EllipsoidART": { + "rho": 5e-5, + "alpha": 0.0, + "beta": 1.0, + "r_hat": 0.9, + "mu": 0.9, + }, + "FuzzyART": {"rho": 0.5667, "alpha": 0.4, "beta": 1.0}, + "GaussianART": { + "rho": 0.3, + "sigma_init": np.array([0.5, 0.5]), + }, + "HypersphereART": { + "rho": 0.1, + "alpha": 0.05, + "beta": 1.0, + "r_hat": 0.4667, + }, + "QuadraticNeuronART": { + "rho": 0.933, + "s_init": 0.8, + "lr_b": 0.4, + "lr_w": 0.1, + "lr_s": 0.2, + }, }, ), ( blobs, { - "BayesianART": - { - "rho": 6e-5, - "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]]), - }, - "DualVigilanceART": - { - "FuzzyART": - { - "rho": 0.91, - "alpha": 0.8, - "beta": 1.0 - }, - "rho_lower_bound": 0.82, - - }, - "EllipsoidART": - { - "rho": 0.01, - "alpha": 0.0, - "beta": 1.0, - "r_hat": 0.6, - "mu": 1.0 - }, - "FuzzyART": - { - "rho": 0.433, - "alpha": 0.2, - "beta": 1.0 - }, - "GaussianART": - { - "rho": 5e-4, - "sigma_init": np.array([0.5, 0.5]), - }, - "HypersphereART": - { - "rho": 0.1, - "alpha": 0.05, - "beta": 1.0, - "r_hat": 0.433 - }, - "QuadraticNeuronART": - { - "rho": 0.95, - "s_init": 0.8, - "lr_b": 0.05, - "lr_w": 0.1, - "lr_s": 0.05 - }, + "BayesianART": { + "rho": 6e-5, + "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]]), + }, + "DualVigilanceART": { + "FuzzyART": {"rho": 0.91, "alpha": 0.8, "beta": 1.0}, + "rho_lower_bound": 0.82, + }, + "EllipsoidART": { + "rho": 0.01, + "alpha": 0.0, + "beta": 1.0, + "r_hat": 0.6, + "mu": 1.0, + }, + "FuzzyART": {"rho": 0.433, "alpha": 0.2, "beta": 1.0}, + "GaussianART": { + "rho": 5e-4, + "sigma_init": np.array([0.5, 0.5]), + }, + "HypersphereART": { + "rho": 0.1, + "alpha": 0.05, + "beta": 1.0, + "r_hat": 0.433, + }, + "QuadraticNeuronART": { + "rho": 0.95, + "s_init": 0.8, + "lr_b": 0.05, + "lr_w": 0.1, + "lr_s": 0.05, + }, }, ), ( no_structure, { - "BayesianART": - { - "rho": 2e-5, - "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]]), - }, - "DualVigilanceART": - { - "FuzzyART": - { - "rho": 0.85, - - "alpha": 0.8, - "beta": 1.0 - }, - "rho_lower_bound": 0.78, - - }, - "EllipsoidART": - { - "rho": 0.2, - "alpha": 0.0, - "beta": 1.0, - "r_hat": 0.6, - "mu": 0.8 - }, - "FuzzyART": - { - "rho": 0.7, - "alpha": 0.0, - "beta": 1.0 - }, - "GaussianART": - { - "rho": 0.15, - "sigma_init": np.array([0.5, 0.5]), - }, - "HypersphereART": - { - "rho": 0.7, - "alpha": 0.0, - "beta": 1.0, - "r_hat": 0.8 - }, - "QuadraticNeuronART": - { - "rho": 0.9, - "s_init": 0.9, - "lr_b": 0.1, - "lr_w": 0.1, - "lr_s": 0.1 - }, + "BayesianART": { + "rho": 2e-5, + "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]]), + }, + "DualVigilanceART": { + "FuzzyART": {"rho": 0.85, "alpha": 0.8, "beta": 1.0}, + "rho_lower_bound": 0.78, + }, + "EllipsoidART": { + "rho": 0.2, + "alpha": 0.0, + "beta": 1.0, + "r_hat": 0.6, + "mu": 0.8, + }, + "FuzzyART": {"rho": 0.7, "alpha": 0.0, "beta": 1.0}, + "GaussianART": { + "rho": 0.15, + "sigma_init": np.array([0.5, 0.5]), + }, + "HypersphereART": { + "rho": 0.7, + "alpha": 0.0, + "beta": 1.0, + "r_hat": 0.8, + }, + "QuadraticNeuronART": { + "rho": 0.9, + "s_init": 0.9, + "lr_b": 0.1, + "lr_w": 0.1, + "lr_s": 0.1, + }, }, ), ] @@ -404,11 +311,9 @@ fuzzy = artlib.FuzzyART(**params["FuzzyART"]) fuzzyDV = artlib.DualVigilanceART( artlib.FuzzyART(**params["DualVigilanceART"]["FuzzyART"]), - rho_lower_bound=params["DualVigilanceART"]["rho_lower_bound"] + rho_lower_bound=params["DualVigilanceART"]["rho_lower_bound"], ) - - clustering_algorithms = ( ("Hypersphere\nART", hypersphere), ("Ellipsoid\nART", ellipsoid), @@ -488,4 +393,4 @@ ) plot_num += 1 -plt.savefig("img/comparison_of_elementary_methods.jpg") \ No newline at end of file +plt.savefig("img/comparison_of_elementary_methods.jpg") diff --git a/scripts/generate_model_results_snapshot.py b/scripts/generate_model_results_snapshot.py index 67e60bf..60e6fee 100644 --- a/scripts/generate_model_results_snapshot.py +++ b/scripts/generate_model_results_snapshot.py @@ -2,7 +2,17 @@ from pathlib import Path from sklearn.datasets import make_blobs import numpy as np -from artlib import ART1, ART2A, BayesianART, DualVigilanceART, EllipsoidART, FuzzyART, GaussianART, HypersphereART, QuadraticNeuronART +from artlib import ( + ART1, + ART2A, + BayesianART, + DualVigilanceART, + EllipsoidART, + FuzzyART, + GaussianART, + HypersphereART, + QuadraticNeuronART, +) def model_factory(model_class, params): @@ -20,20 +30,38 @@ def model_factory(model_class, params): def cluster_and_store_results(): # Generate blob data for clustering - data, _ = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + data, _ = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) print("Data has shape:", data.shape) # Define the models and their parameters in a list of tuples models_with_params = [ # (ART1, {"rho": 0.7, "beta": 1.0, "L": 1.0}), (ART2A, {"rho": 0.2, "alpha": 0.0, "beta": 1.0}), - (BayesianART, {"rho": 7e-5, "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]])}), - (DualVigilanceART, {"rho": 0.85, "alpha": 0.8, "beta": 1.0, "rho_lower_bound": 0.78}), - (EllipsoidART, {"rho": 0.01, "alpha": 0.0, "beta": 1.0, "r_hat": 0.65, "mu": 0.8}), + ( + BayesianART, + {"rho": 7e-5, "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]])}, + ), + ( + DualVigilanceART, + {"rho": 0.85, "alpha": 0.8, "beta": 1.0, "rho_lower_bound": 0.78}, + ), + ( + EllipsoidART, + {"rho": 0.01, "alpha": 0.0, "beta": 1.0, "r_hat": 0.65, "mu": 0.8}, + ), (FuzzyART, {"rho": 0.5, "alpha": 0.0, "beta": 1.0}), (GaussianART, {"rho": 0.05, "sigma_init": np.array([0.5, 0.5])}), (HypersphereART, {"rho": 0.5, "alpha": 0.0, "beta": 1.0, "r_hat": 0.8}), - (QuadraticNeuronART, {"rho": 0.9, "s_init": 0.9, "lr_b": 0.1, "lr_w": 0.1, "lr_s": 0.1}), + ( + QuadraticNeuronART, + {"rho": 0.9, "s_init": 0.9, "lr_b": 0.1, "lr_w": 0.1, "lr_s": 0.1}, + ), ] results = {} @@ -54,10 +82,11 @@ def cluster_and_store_results(): results[model_name] = {"params": params, "labels": labels} print(f"{cls.n_clusters} clusters found for {model_name}") - # Save the results to a pickle file current_file_path = Path(__file__).resolve().parent.parent - output_file = current_file_path / "unit_tests" / "cluster_results_snapshot.pkl" + output_file = ( + current_file_path / "unit_tests" / "cluster_results_snapshot.pkl" + ) # Ensure the output directory exists output_file.parent.mkdir(parents=True, exist_ok=True) @@ -68,5 +97,6 @@ def cluster_and_store_results(): print("Results saved to cluster_results.pkl") + if __name__ == "__main__": cluster_and_store_results() diff --git a/scripts/generate_references.py b/scripts/generate_references.py index 216cd5e..6a7a24f 100644 --- a/scripts/generate_references.py +++ b/scripts/generate_references.py @@ -1,19 +1,16 @@ import subprocess import os + def generate_references(): try: # Run cffconvert and capture the output result = subprocess.run( - [ - 'cffconvert', - '--format', 'bibtex', - '--infile', 'CITATION.cff' - ], + ["cffconvert", "--format", "bibtex", "--infile", "CITATION.cff"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True + text=True, ) bibtex_content = result.stdout @@ -22,20 +19,21 @@ def generate_references(): bibtex_lines = bibtex_content.splitlines() if bibtex_lines: # Replace the first line - bibtex_lines[0] = '@misc{Melton_AdaptiveResonanceLib_2024,' - modified_bibtex = '\n'.join(bibtex_lines) + bibtex_lines[0] = "@misc{Melton_AdaptiveResonanceLib_2024," + modified_bibtex = "\n".join(bibtex_lines) # Write the modified content to references.bib - output_path = os.path.join('docs', 'source', 'references.bib') - with open(output_path, 'w', encoding='utf-8') as f: + output_path = os.path.join("docs", "source", "references.bib") + with open(output_path, "w", encoding="utf-8") as f: f.write(modified_bibtex) else: - print('Error: Empty BibTeX content.') + print("Error: Empty BibTeX content.") except subprocess.CalledProcessError as e: print(f"An error occurred while running cffconvert: {e}") print(f"stdout: {e.stdout}") print(f"stderr: {e.stderr}") raise e -if __name__ == '__main__': + +if __name__ == "__main__": generate_references() diff --git a/scripts/optimization_of_methods.py b/scripts/optimization_of_methods.py index 5dff220..6ecfc2d 100644 --- a/scripts/optimization_of_methods.py +++ b/scripts/optimization_of_methods.py @@ -18,23 +18,36 @@ n_samples = 500 seed = 30 noisy_circles = datasets.make_circles( - n_samples=n_samples, factor=0.5, noise=0.05, random_state=seed, shuffle=False + n_samples=n_samples, + factor=0.5, + noise=0.05, + random_state=seed, + shuffle=False, +) +noisy_moons = datasets.make_moons( + n_samples=n_samples, noise=0.05, random_state=seed, shuffle=False +) +blobs = datasets.make_blobs( + n_samples=n_samples, random_state=seed, shuffle=False ) -noisy_moons = datasets.make_moons(n_samples=n_samples, noise=0.05, random_state=seed, shuffle=False) -blobs = datasets.make_blobs(n_samples=n_samples, random_state=seed, shuffle=False) rng = np.random.RandomState(seed) no_structure = rng.rand(n_samples, 2), None # Anisotropicly distributed data random_state = 170 -X, y = datasets.make_blobs(n_samples=n_samples, random_state=random_state, shuffle=False) +X, y = datasets.make_blobs( + n_samples=n_samples, random_state=random_state, shuffle=False +) transformation = [[0.6, -0.6], [-0.4, 0.8]] X_aniso = np.dot(X, transformation) aniso = (X_aniso, y) # blobs with varied variances varied = datasets.make_blobs( - n_samples=n_samples, cluster_std=[1.0, 2.5, 0.5], random_state=random_state, shuffle=False + n_samples=n_samples, + cluster_std=[1.0, 2.5, 0.5], + random_state=random_state, + shuffle=False, ) # ============ @@ -42,57 +55,35 @@ # ============ init_params = { - "BayesianART": - { - "rho": 2e-5, - "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]]), - }, - "DualVigilanceFuzzyART": - { - "FuzzyART": - { - "rho": 0.95, - - "alpha": 0.8, - "beta": 1.0 - }, - "rho_lower_bound": 0.5, - "base_module": artlib.FuzzyART(0.0, 0.0, 1.0), - }, - "EllipsoidART": - { - "rho": 0.2, - "alpha": 0.0, - "beta": 1.0, - "r_hat": 0.6, - "mu": 0.8 - }, - "FuzzyART": - { - "rho": 0.7, - "alpha": 0.0, - "beta": 1.0 - }, - "GaussianART": - { - "rho": 0.15, - "sigma_init": np.array([0.5, 0.5]), - }, - "HypersphereART": - { - "rho": 0.6, - "alpha": 0.0, - "beta": 1.0, - "r_hat": 0.8 - }, - "QuadraticNeuronART": - { - "rho": 0.99, - "s_init": 0.8, - "lr_b": 0.2, - "lr_w": 0.1, - "lr_s": 0.2 - }, + "BayesianART": { + "rho": 2e-5, + "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]]), + }, + "DualVigilanceFuzzyART": { + "FuzzyART": {"rho": 0.95, "alpha": 0.8, "beta": 1.0}, + "rho_lower_bound": 0.5, + "base_module": artlib.FuzzyART(0.0, 0.0, 1.0), + }, + "EllipsoidART": { + "rho": 0.2, + "alpha": 0.0, + "beta": 1.0, + "r_hat": 0.6, + "mu": 0.8, + }, + "FuzzyART": {"rho": 0.7, "alpha": 0.0, "beta": 1.0}, + "GaussianART": { + "rho": 0.15, + "sigma_init": np.array([0.5, 0.5]), + }, + "HypersphereART": {"rho": 0.6, "alpha": 0.0, "beta": 1.0, "r_hat": 0.8}, + "QuadraticNeuronART": { + "rho": 0.99, + "s_init": 0.8, + "lr_b": 0.2, + "lr_w": 0.1, + "lr_s": 0.2, + }, } rho_range = np.linspace(0.1, 1.0, 28) @@ -100,56 +91,43 @@ alpha_range = np.linspace(0.0, 1.0, 21) - param_grids = { - "BayesianART": - { - "rho": [1e-5, 2e-5, 3e-5, 4e-5, 5e-5, 6e-5, 7e-5, 8e-5, 9e-5], - "cov_init": [np.array([[1e-4, 0.0], [0.0, 1e-4]])], - }, - "DualVigilanceFuzzyART": - { - "base_module": [artlib.FuzzyART(0.1, 0.0, 1.0)], - "base_module__rho": rho_range, - "base_module__alpha": alpha_range, - "base_module__beta": [1.0], - "rho_lower_bound": rho_range, - - }, - "EllipsoidART": - { - "rho": rho_range, - "alpha": alpha_range, - "beta": [1.0], - "r_hat": np.linspace(0.1, 0.9, 17), - "mu": np.linspace(0.1, 1.0, 10) - }, - "FuzzyART": - { - "rho": rho_range, - "alpha":alpha_range, - "beta": [1.0] - }, - "GaussianART": - { - "rho": rho_range, - "sigma_init": [np.array([0.5, 0.5])], - }, - "HypersphereART": - { - "rho": rho_range, - "alpha": alpha_range, - "beta": [1.0], - "r_hat": np.linspace(0.1, 0.8, 22), - }, - "QuadraticNeuronART": - { - "rho": rho_range, - "s_init": np.linspace(0.1, 1.0, 10), - "lr_b": np.linspace(0.1, 0.6, 6), - "lr_w": np.linspace(0.1, 0.6, 6), - "lr_s": np.linspace(0.1, 0.6, 6) - }, + "BayesianART": { + "rho": [1e-5, 2e-5, 3e-5, 4e-5, 5e-5, 6e-5, 7e-5, 8e-5, 9e-5], + "cov_init": [np.array([[1e-4, 0.0], [0.0, 1e-4]])], + }, + "DualVigilanceFuzzyART": { + "base_module": [artlib.FuzzyART(0.1, 0.0, 1.0)], + "base_module__rho": rho_range, + "base_module__alpha": alpha_range, + "base_module__beta": [1.0], + "rho_lower_bound": rho_range, + }, + "EllipsoidART": { + "rho": rho_range, + "alpha": alpha_range, + "beta": [1.0], + "r_hat": np.linspace(0.1, 0.9, 17), + "mu": np.linspace(0.1, 1.0, 10), + }, + "FuzzyART": {"rho": rho_range, "alpha": alpha_range, "beta": [1.0]}, + "GaussianART": { + "rho": rho_range, + "sigma_init": [np.array([0.5, 0.5])], + }, + "HypersphereART": { + "rho": rho_range, + "alpha": alpha_range, + "beta": [1.0], + "r_hat": np.linspace(0.1, 0.8, 22), + }, + "QuadraticNeuronART": { + "rho": rho_range, + "s_init": np.linspace(0.1, 1.0, 10), + "lr_b": np.linspace(0.1, 0.6, 6), + "lr_w": np.linspace(0.1, 0.6, 6), + "lr_s": np.linspace(0.1, 0.6, 6), + }, } plot_num = 1 @@ -178,11 +156,10 @@ ( "no_structure", no_structure, - ) + ), ] for i_dataset, (dataset_name, dataset) in enumerate(datasets): - X, y = dataset # normalize dataset for easier parameter selection @@ -196,14 +173,15 @@ ellipsoid = artlib.EllipsoidART(**init_params["EllipsoidART"]) gaussian = artlib.GaussianART(**init_params["GaussianART"]) bayesian = artlib.BayesianART(**init_params["BayesianART"]) - quadratic_neuron = artlib.QuadraticNeuronART(**init_params["QuadraticNeuronART"]) + quadratic_neuron = artlib.QuadraticNeuronART( + **init_params["QuadraticNeuronART"] + ) fuzzy = artlib.FuzzyART(**init_params["FuzzyART"]) fuzzyDV = artlib.DualVigilanceART( artlib.FuzzyART(**init_params["FuzzyART"]), - rho_lower_bound=init_params["DualVigilanceFuzzyART"]["rho_lower_bound"] + rho_lower_bound=init_params["DualVigilanceFuzzyART"]["rho_lower_bound"], ) - clustering_algorithms = ( ("DualVigilanceFuzzyART", fuzzyDV), ("HypersphereART", hypersphere), @@ -223,16 +201,7 @@ verbose=0, n_jobs=-1, scoring="adjusted_rand_score", - cv=[ - ( - np.array( - list(range(len(X))) - ), - np.array( - list(range(len(X))) - ) - ) - ] + cv=[(np.array(list(range(len(X)))), np.array(list(range(len(X)))))], ) X_prepared = algorithm.prepare_data(X) grid.fit(X_prepared, y) diff --git a/templates/ART_template.py b/templates/ART_template.py index ba74056..a354252 100644 --- a/templates/ART_template.py +++ b/templates/ART_template.py @@ -3,6 +3,7 @@ from artlib import BaseART from artlib.common.utils import normalize + def prepare_data(data: np.ndarray) -> np.ndarray: normalized = normalize(data) return normalized @@ -32,7 +33,9 @@ def validate_data(self, X: np.ndarray): """ raise NotImplementedError - def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: + def category_choice( + self, i: np.ndarray, w: np.ndarray, params: dict + ) -> tuple[float, Optional[dict]]: """ get the activation of the cluster @@ -47,7 +50,13 @@ def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[f """ raise NotImplementedError - def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]: + 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 @@ -63,7 +72,13 @@ def match_criterion(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Opt """ raise NotImplementedError - def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> tuple[bool, dict]: + def match_criterion_bin( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + ) -> tuple[bool, dict]: """ get the binary match criterion of the cluster @@ -79,7 +94,13 @@ def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: """ raise NotImplementedError - def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: + def update( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + ) -> np.ndarray: """ get the updated cluster weight diff --git a/unit_tests/test_ART1.py b/unit_tests/test_ART1.py index 0a5dd67..27088e8 100644 --- a/unit_tests/test_ART1.py +++ b/unit_tests/test_ART1.py @@ -2,6 +2,7 @@ import numpy as np from artlib.elementary.ART1 import ART1 + # Fixture to initialize an ART1 instance for testing @pytest.fixture def art_model(): @@ -10,29 +11,28 @@ def art_model(): L = 2.0 return ART1(rho=rho, beta=beta, L=L) + def test_initialization(art_model): # Test that the model initializes correctly - assert art_model.params['rho'] == 0.7 - assert art_model.params['beta'] == 0.5 - assert art_model.params['L'] == 2.0 + assert art_model.params["rho"] == 0.7 + assert art_model.params["beta"] == 0.5 + assert art_model.params["L"] == 2.0 + def test_validate_params(): # Test the validate_params method - valid_params = { - "rho": 0.7, - "beta": 0.5, - "L": 2.0 - } + valid_params = {"rho": 0.7, "beta": 0.5, "L": 2.0} ART1.validate_params(valid_params) invalid_params = { "rho": -0.7, # Invalid vigilance parameter "beta": -0.5, # Invalid beta - "L": 0.5 # Invalid L (must be >= 1) + "L": 0.5, # Invalid L (must be >= 1) } with pytest.raises(AssertionError): ART1.validate_params(invalid_params) + def test_validate_data(art_model): # Test the validate_data method binary_data = np.array([[1, 0], [0, 1]]) @@ -42,39 +42,53 @@ def test_validate_data(art_model): with pytest.raises(AssertionError): art_model.validate_data(non_binary_data) + def test_category_choice(art_model): # Test the category_choice method art_model.dim_ = 2 i = np.array([1, 0]) - w = np.array([0.5, 0.5, 1, 0]) # Mock weight with both bottom-up and top-down weights + w = np.array( + [0.5, 0.5, 1, 0] + ) # Mock weight with both bottom-up and top-down weights params = {"rho": 0.7} activation, _ = art_model.category_choice(i, w, params) assert isinstance(activation, float) assert activation == 0.5 # np.dot(i, [0.5, 0.5]) = 0.5 + def test_match_criterion(art_model): # Test the match_criterion method art_model.dim_ = 2 i = np.array([1, 0]) - w = np.array([0.5, 0.5, 1, 0]) # Mock weight with both bottom-up and top-down weights + w = np.array( + [0.5, 0.5, 1, 0] + ) # Mock weight with both bottom-up and top-down weights params = {"rho": 0.7} cache = {} match_criterion, _ = art_model.match_criterion(i, w, params, cache=cache) assert isinstance(match_criterion, float) - assert match_criterion == 1.0 # Intersection of i and top-down weight w_td: [1, 0] matches exactly with i + assert ( + match_criterion == 1.0 + ) # Intersection of i and top-down weight w_td: [1, 0] matches exactly with i + def test_update(art_model): # Test the update method art_model.dim_ = 2 i = np.array([1, 0]) - w = np.array([0.5, 0.5, 1, 1]) # Mock weight with both bottom-up and top-down weights + w = np.array( + [0.5, 0.5, 1, 1] + ) # Mock weight with both bottom-up and top-down weights params = {"L": 2.0, "beta": 0.5} updated_weight = art_model.update(i, w, params) assert len(updated_weight) == 4 # Bottom-up and top-down weights - assert np.array_equal(updated_weight[2:], np.array([1, 0])) # Top-down weights should match input i + assert np.array_equal( + updated_weight[2:], np.array([1, 0]) + ) # Top-down weights should match input i + def test_new_weight(art_model): # Test the new_weight method @@ -84,7 +98,10 @@ def test_new_weight(art_model): new_weight = art_model.new_weight(i, params) assert len(new_weight) == 4 # Bottom-up and top-down weights - assert np.array_equal(new_weight[2:], i) # Top-down weights should be equal to input i + assert np.array_equal( + new_weight[2:], i + ) # Top-down weights should be equal to input i + def test_get_cluster_centers(art_model): # Test getting cluster centers @@ -95,6 +112,7 @@ def test_get_cluster_centers(art_model): assert np.array_equal(centers[0], np.array([1, 0])) assert np.array_equal(centers[1], np.array([0, 1])) + def test_fit(art_model): # Test fitting the model art_model.dim_ = 2 @@ -104,6 +122,7 @@ def test_fit(art_model): assert len(art_model.W) > 0 # Ensure that clusters were created + def test_partial_fit(art_model): # Test partial_fit method art_model.dim_ = 2 @@ -113,6 +132,7 @@ def test_partial_fit(art_model): assert len(art_model.W) > 0 # Ensure that clusters were partially fit + def test_predict(art_model): # Test predict method art_model.dim_ = 2 diff --git a/unit_tests/test_ART2.py b/unit_tests/test_ART2.py index 711fc8c..c2f0297 100644 --- a/unit_tests/test_ART2.py +++ b/unit_tests/test_ART2.py @@ -14,24 +14,20 @@ def art_model(): def test_initialization(art_model): # Test that the model initializes correctly - assert art_model.params['rho'] == 0.7 - assert art_model.params['alpha'] == 0.1 - assert art_model.params['beta'] == 0.5 + assert art_model.params["rho"] == 0.7 + assert art_model.params["alpha"] == 0.1 + assert art_model.params["beta"] == 0.5 def test_validate_params(): # Test the validate_params method - valid_params = { - "rho": 0.7, - "alpha": 0.1, - "beta": 0.5 - } + valid_params = {"rho": 0.7, "alpha": 0.1, "beta": 0.5} ART2A.validate_params(valid_params) invalid_params = { "rho": -0.7, # Invalid vigilance parameter "alpha": -0.1, # Invalid alpha - "beta": 1.5 # Invalid beta + "beta": 1.5, # Invalid beta } with pytest.raises(AssertionError): ART2A.validate_params(invalid_params) @@ -53,7 +49,7 @@ def test_category_choice(art_model): params = {"rho": 0.7} activation, cache = art_model.category_choice(i, w, params) - assert 'activation' in cache + assert "activation" in cache assert isinstance(activation, float) @@ -65,8 +61,12 @@ def test_match_criterion(art_model): params = {"alpha": 0.1} cache = {"activation": 0.5} - match_criterion, new_cache = art_model.match_criterion(i, w, params, cache=cache) - assert match_criterion == 0.5 # Since activation is higher than uncommitted activation + match_criterion, new_cache = art_model.match_criterion( + i, w, params, cache=cache + ) + assert ( + match_criterion == 0.5 + ) # Since activation is higher than uncommitted activation def test_update(art_model): @@ -79,7 +79,9 @@ def test_update(art_model): updated_weight = art_model.update(i, w, params, cache=cache) assert len(updated_weight) == 2 # Check that the weight is updated - assert np.allclose(updated_weight, (0.5 * i + 0.5 * w)) # Check the update formula + assert np.allclose( + updated_weight, (0.5 * i + 0.5 * w) + ) # Check the update formula def test_new_weight(art_model): diff --git a/unit_tests/test_ARTMAP.py b/unit_tests/test_ARTMAP.py index b731869..125f83e 100644 --- a/unit_tests/test_ARTMAP.py +++ b/unit_tests/test_ARTMAP.py @@ -4,6 +4,7 @@ from artlib.elementary.FuzzyART import FuzzyART from artlib.common.BaseART import BaseART + # Fixture to initialize an ARTMAP instance for testing @pytest.fixture def artmap_model(): @@ -11,17 +12,20 @@ def artmap_model(): module_b = FuzzyART(0.5, 0.01, 1.0) return ARTMAP(module_a=module_a, module_b=module_b) + def test_initialization(artmap_model): # Test that the model initializes correctly assert isinstance(artmap_model.module_a, BaseART) assert isinstance(artmap_model.module_b, BaseART) + def test_get_params(artmap_model): # Test the get_params method params = artmap_model.get_params() assert "module_a" in params assert "module_b" in params + def test_labels_properties(artmap_model): # Test the labels properties artmap_model.module_a.labels_ = np.array([0, 1, 1]) @@ -29,7 +33,11 @@ def test_labels_properties(artmap_model): assert np.array_equal(artmap_model.labels_a, artmap_model.module_a.labels_) assert np.array_equal(artmap_model.labels_b, artmap_model.module_b.labels_) - assert artmap_model.labels_ab == {"A": artmap_model.module_a.labels_, "B": artmap_model.module_b.labels_} + assert artmap_model.labels_ab == { + "A": artmap_model.module_a.labels_, + "B": artmap_model.module_b.labels_, + } + def test_validate_data(artmap_model): # Test the validate_data method @@ -45,6 +53,7 @@ def test_validate_data(artmap_model): with pytest.raises(AssertionError): artmap_model.validate_data(X_invalid, y) + def test_prepare_and_restore_data(artmap_model): # Test prepare_data and restore_data methods np.random.seed(42) @@ -57,6 +66,7 @@ def test_prepare_and_restore_data(artmap_model): assert np.allclose(X_restored, X) assert np.allclose(y_restored, y) + def test_fit(artmap_model): # Test the fit method np.random.seed(42) @@ -70,6 +80,7 @@ def test_fit(artmap_model): assert artmap_model.module_a.labels_.shape[0] == X.shape[0] assert artmap_model.module_b.labels_.shape[0] == y.shape[0] + def test_partial_fit(artmap_model): # Test the partial_fit method np.random.seed(42) @@ -83,6 +94,7 @@ def test_partial_fit(artmap_model): assert artmap_model.module_a.labels_.shape[0] == X.shape[0] assert artmap_model.module_b.labels_.shape[0] == y.shape[0] + def test_predict(artmap_model): # Test the predict method np.random.seed(42) @@ -96,6 +108,7 @@ def test_predict(artmap_model): predictions = artmap_model.predict(X_prep) assert predictions.shape[0] == X.shape[0] + def test_predict_ab(artmap_model): # Test the predict_ab method np.random.seed(42) @@ -110,6 +123,7 @@ def test_predict_ab(artmap_model): assert predictions_a.shape[0] == X.shape[0] assert predictions_b.shape[0] == X.shape[0] + def test_predict_regression(artmap_model): # Test the predict_regression method np.random.seed(42) diff --git a/unit_tests/test_BARTMAP.py b/unit_tests/test_BARTMAP.py index 4419b2c..aa6715a 100644 --- a/unit_tests/test_BARTMAP.py +++ b/unit_tests/test_BARTMAP.py @@ -5,6 +5,7 @@ from artlib.elementary.FuzzyART import FuzzyART from artlib.common.BaseART import BaseART + # Fixture to initialize a BARTMAP instance for testing @pytest.fixture def bartmap_model(): @@ -12,12 +13,14 @@ def bartmap_model(): module_b = FuzzyART(0.5, 0.01, 1.0) return BARTMAP(module_a=module_a, module_b=module_b, eta=0.01) + def test_initialization(bartmap_model): # Test that the model initializes correctly assert bartmap_model.params["eta"] == 0.01 assert isinstance(bartmap_model.module_a, BaseART) assert isinstance(bartmap_model.module_b, BaseART) + def test_validate_params(): # Test the validate_params method valid_params = {"eta": 0.5} @@ -27,6 +30,7 @@ def test_validate_params(): with pytest.raises(AssertionError): BARTMAP.validate_params(invalid_params) + def test_get_params(bartmap_model): # Test the get_params method params = bartmap_model.get_params() @@ -34,11 +38,13 @@ def test_get_params(bartmap_model): assert "module_a" in params assert "module_b" in params + def test_set_params(bartmap_model): # Test the set_params method bartmap_model.set_params(eta=0.7) assert bartmap_model.eta == 0.7 + def test_step_fit(bartmap_model): # Test the step_fit method X = np.random.rand(10, 10) @@ -57,6 +63,7 @@ def test_step_fit(bartmap_model): c_a = bartmap_model.step_fit(X_a, 0) assert isinstance(c_a, int) # Ensure the result is an integer cluster label + def test_match_criterion_bin(bartmap_model): # Test the match_criterion_bin method X = np.random.rand(100, 100) @@ -76,6 +83,7 @@ def test_match_criterion_bin(bartmap_model): result = bartmap_model.match_criterion_bin(X, 9, 0, {"eta": 0.5}) assert isinstance(result, bool) # Ensure the result is a boolean + def test_fit(bartmap_model): # Test the fit method X = np.random.rand(100, 100) @@ -87,6 +95,11 @@ def test_fit(bartmap_model): assert hasattr(bartmap_model, "columns_") # Check that the rows and columns shapes match the expected size - assert bartmap_model.rows_.shape[0] == bartmap_model.module_a.n_clusters * bartmap_model.module_b.n_clusters - assert bartmap_model.columns_.shape[0] == bartmap_model.module_a.n_clusters * bartmap_model.module_b.n_clusters - + assert ( + bartmap_model.rows_.shape[0] + == bartmap_model.module_a.n_clusters * bartmap_model.module_b.n_clusters + ) + assert ( + bartmap_model.columns_.shape[0] + == bartmap_model.module_a.n_clusters * bartmap_model.module_b.n_clusters + ) diff --git a/unit_tests/test_BayesianART.py b/unit_tests/test_BayesianART.py index dbd825d..f0fb4be 100644 --- a/unit_tests/test_BayesianART.py +++ b/unit_tests/test_BayesianART.py @@ -13,21 +13,20 @@ def art_model(): def test_initialization(art_model): # Test that the model initializes correctly - assert art_model.params['rho'] == 0.7 - assert np.array_equal(art_model.params['cov_init'], np.array([[1.0, 0.0], [0.0, 1.0]])) + assert art_model.params["rho"] == 0.7 + assert np.array_equal( + art_model.params["cov_init"], np.array([[1.0, 0.0], [0.0, 1.0]]) + ) def test_validate_params(): # Test the validate_params method - valid_params = { - "rho": 0.7, - "cov_init": np.array([[1.0, 0.0], [0.0, 1.0]]) - } + valid_params = {"rho": 0.7, "cov_init": np.array([[1.0, 0.0], [0.0, 1.0]])} BayesianART.validate_params(valid_params) invalid_params = { "rho": -0.5, # Invalid vigilance parameter - "cov_init": "not_a_matrix" # Invalid covariance matrix + "cov_init": "not_a_matrix", # Invalid covariance matrix } with pytest.raises(AssertionError): BayesianART.validate_params(invalid_params) @@ -45,13 +44,15 @@ def test_category_choice(art_model): # Test the category_choice method art_model.dim_ = 2 i = np.array([0.2, 0.3]) - w = np.array([0.25, 0.35, 1.0, 0.0, 0.0, 1.0, 5]) # Mock weight (mean, covariance, and sample count) + w = np.array( + [0.25, 0.35, 1.0, 0.0, 0.0, 1.0, 5] + ) # Mock weight (mean, covariance, and sample count) art_model.W = [w] params = {"rho": 0.7} activation, cache = art_model.category_choice(i, w, params) - assert 'cov' in cache - assert 'det_cov' in cache + assert "cov" in cache + assert "det_cov" in cache assert isinstance(activation, float) @@ -59,11 +60,15 @@ def test_match_criterion(art_model): # Test the match_criterion method art_model.dim_ = 2 i = np.array([0.2, 0.3]) - w = np.array([0.25, 0.35, 1.0, 0.0, 0.0, 1.0, 5]) # Mock weight (mean, covariance, and sample count) + w = np.array( + [0.25, 0.35, 1.0, 0.0, 0.0, 1.0, 5] + ) # Mock weight (mean, covariance, and sample count) params = {"rho": 0.7} cache = {} - match_criterion, new_cache = art_model.match_criterion(i, w, params, cache=cache) + match_criterion, new_cache = art_model.match_criterion( + i, w, params, cache=cache + ) assert isinstance(match_criterion, float) @@ -71,12 +76,16 @@ def test_update(art_model): # Test the update method art_model.dim_ = 2 i = np.array([0.2, 0.3]) - w = np.array([0.25, 0.35, 1.0, 0.0, 0.0, 1.0, 5]) # Mock weight (mean, covariance, and sample count) + w = np.array( + [0.25, 0.35, 1.0, 0.0, 0.0, 1.0, 5] + ) # Mock weight (mean, covariance, and sample count) params = {"rho": 0.7} cache = {} updated_weight = art_model.update(i, w, params, cache=cache) - assert len(updated_weight) == 7 # Mean (2D), covariance (4 values), and sample count + assert ( + len(updated_weight) == 7 + ) # Mean (2D), covariance (4 values), and sample count def test_new_weight(art_model): @@ -86,7 +95,9 @@ def test_new_weight(art_model): params = {"cov_init": np.array([[1.0, 0.0], [0.0, 1.0]])} new_weight = art_model.new_weight(i, params) - assert len(new_weight) == 7 # Mean (2D), covariance (4 values), and sample count + assert ( + len(new_weight) == 7 + ) # Mean (2D), covariance (4 values), and sample count assert new_weight[-1] == 1 # Initial sample count should be 1 diff --git a/unit_tests/test_CVIART.py b/unit_tests/test_CVIART.py index f0710a0..3211a1d 100644 --- a/unit_tests/test_CVIART.py +++ b/unit_tests/test_CVIART.py @@ -1,6 +1,10 @@ import pytest import numpy as np -from sklearn.metrics import calinski_harabasz_score, davies_bouldin_score, silhouette_score +from sklearn.metrics import ( + calinski_harabasz_score, + davies_bouldin_score, + silhouette_score, +) from artlib.elementary.FuzzyART import FuzzyART from artlib.common.BaseART import BaseART from artlib.cvi.CVIART import CVIART @@ -72,7 +76,14 @@ def test_cviart_CVI_match(cviart_model): cviart_model.base_module.W = w # Test that CVI_match correctly works with Calinski-Harabasz - result = cviart_model.CVI_match(x, w, 1, cviart_model.params, {"validity": CVIART.CALINSKIHARABASZ, "index": 0}, {}) + result = cviart_model.CVI_match( + x, + w, + 1, + cviart_model.params, + {"validity": CVIART.CALINSKIHARABASZ, "index": 0}, + {}, + ) assert isinstance(result, np.bool_) @@ -86,4 +97,3 @@ def test_cviart_get_cluster_centers(cviart_model): assert len(centers) == 1 assert np.allclose(centers[0], np.array([0.3, 0.5])) - diff --git a/unit_tests/test_DeepARTMAP.py b/unit_tests/test_DeepARTMAP.py index 03df0ee..b6669c1 100644 --- a/unit_tests/test_DeepARTMAP.py +++ b/unit_tests/test_DeepARTMAP.py @@ -138,5 +138,7 @@ def test_map_deep(deep_artmap_model): X_prep, _ = deep_artmap_model.prepare_data(X) deep_artmap_model.fit(X_prep, y, max_iter=1) - mapped_label = deep_artmap_model.map_deep(0, deep_artmap_model.layers[0].labels_a[0]) + mapped_label = deep_artmap_model.map_deep( + 0, deep_artmap_model.layers[0].labels_a[0] + ) assert isinstance(mapped_label.tolist(), int) diff --git a/unit_tests/test_DualVigilanceART.py b/unit_tests/test_DualVigilanceART.py index f9e10d3..2c480ce 100644 --- a/unit_tests/test_DualVigilanceART.py +++ b/unit_tests/test_DualVigilanceART.py @@ -4,6 +4,7 @@ from artlib.topological.DualVigilanceART import DualVigilanceART from artlib.common.BaseART import BaseART + # Mock BaseART class for testing purposes class MockBaseART(BaseART): def __init__(self): @@ -29,13 +30,28 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: def add_weight(self, w: np.ndarray): self.W.append(w) - def category_choice(self, i: np.ndarray, w: np.ndarray, params: dict) -> tuple[float, Optional[dict]]: + def category_choice( + self, i: np.ndarray, w: np.ndarray, params: dict + ) -> tuple[float, Optional[dict]]: return np.random.random(), {} - def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None, op=None) -> tuple[bool, dict]: + def match_criterion_bin( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + op=None, + ) -> tuple[bool, dict]: return True, {} - def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray: + def update( + self, + i: np.ndarray, + w: np.ndarray, + params: dict, + cache: Optional[dict] = None, + ) -> np.ndarray: return w def get_cluster_centers(self) -> list: @@ -44,46 +60,56 @@ def get_cluster_centers(self) -> list: def check_dimensions(self, X: np.ndarray): assert X.shape[1] == self.dim_ + @pytest.fixture def art_model(): base_module = MockBaseART() rho_lower_bound = 0.3 - return DualVigilanceART(base_module=base_module, rho_lower_bound=rho_lower_bound) + return DualVigilanceART( + base_module=base_module, rho_lower_bound=rho_lower_bound + ) + def test_initialization(art_model): # Test that the model initializes correctly - assert art_model.params['rho_lower_bound'] == 0.3 + assert art_model.params["rho_lower_bound"] == 0.3 assert isinstance(art_model.base_module, BaseART) + def test_prepare_data(art_model): # Test the prepare_data method X = np.array([[0.1, 0.2], [0.3, 0.4]]) prepared_X = art_model.prepare_data(X) assert np.array_equal(prepared_X, X) + def test_restore_data(art_model): # Test the restore_data method X = np.array([[0.1, 0.2], [0.3, 0.4]]) restored_X = art_model.restore_data(X) assert np.array_equal(restored_X, X) + def test_get_params(art_model): # Test the get_params method params = art_model.get_params(deep=True) assert "rho_lower_bound" in params assert "base_module" in params + def test_n_clusters(art_model): # Test the n_clusters property assert art_model.n_clusters == 0 # No clusters initially art_model.map = {0: 0, 1: 1} assert art_model.n_clusters == 2 # Two clusters + def test_check_dimensions(art_model): # Test the check_dimensions method X = np.array([[0.1, 0.2], [0.3, 0.4]]) art_model.check_dimensions(X) # Should pass without assertion errors + def test_validate_params(art_model): # Test the validate_params method valid_params = {"rho_lower_bound": 0.3} @@ -93,12 +119,14 @@ def test_validate_params(art_model): with pytest.raises(AssertionError): art_model.validate_params(invalid_params) + def test_step_fit(art_model): # Test the step_fit method x = np.array([0.1, 0.2]) cluster_label = art_model.step_fit(x) assert cluster_label == 0 # First sample should create a new cluster + def test_step_pred(art_model): # Test the step_pred method x = np.array([0.1, 0.2]) @@ -106,10 +134,10 @@ def test_step_pred(art_model): cluster_label = art_model.step_pred(x) assert cluster_label == 0 # Predict should return the correct cluster + def test_get_cluster_centers(art_model): # Test the get_cluster_centers method art_model.step_fit(np.array([0.1, 0.2])) # Create the first cluster centers = art_model.get_cluster_centers() assert len(centers) == 1 assert np.array_equal(centers[0], np.array([0.1, 0.2])) - diff --git a/unit_tests/test_EllipsoidART.py b/unit_tests/test_EllipsoidART.py index 570da9d..f250c0e 100644 --- a/unit_tests/test_EllipsoidART.py +++ b/unit_tests/test_EllipsoidART.py @@ -2,6 +2,7 @@ import numpy as np from artlib.elementary.EllipsoidART import EllipsoidART + # Fixture to initialize an EllipsoidART instance for testing @pytest.fixture def art_model(): @@ -12,13 +13,15 @@ def art_model(): r_hat = 1.0 return EllipsoidART(rho=rho, alpha=alpha, beta=beta, mu=mu, r_hat=r_hat) + def test_initialization(art_model): # Test that the model initializes correctly - assert art_model.params['rho'] == 0.7 - assert art_model.params['alpha'] == 1e-5 - assert art_model.params['beta'] == 0.1 - assert art_model.params['mu'] == 0.5 - assert art_model.params['r_hat'] == 1.0 + assert art_model.params["rho"] == 0.7 + assert art_model.params["alpha"] == 1e-5 + assert art_model.params["beta"] == 0.1 + assert art_model.params["mu"] == 0.5 + assert art_model.params["r_hat"] == 1.0 + def test_validate_params(): # Test the validate_params method @@ -27,7 +30,7 @@ def test_validate_params(): "alpha": 1e-5, "beta": 0.1, "mu": 0.5, - "r_hat": 1.0 + "r_hat": 1.0, } EllipsoidART.validate_params(valid_params) @@ -36,35 +39,38 @@ def test_validate_params(): "alpha": -1e-5, # Invalid alpha "beta": 1.1, # Invalid beta "mu": -0.5, # Invalid mu - "r_hat": -1.0 # Invalid r_hat + "r_hat": -1.0, # Invalid r_hat } with pytest.raises(AssertionError): EllipsoidART.validate_params(invalid_params) + def test_category_choice(art_model): # Test the category_choice method art_model.dim_ = 2 i = np.array([0.2, 0.3]) - w = np.array([0.25, 0.35, 0.5, 0.0, 0.0]) # Mock weight (centroid, major axis, and radius) - params = { - "rho": 0.7, - "alpha": 1e-5, - "mu": 0.5, - "r_hat": 1.0 - } + w = np.array( + [0.25, 0.35, 0.5, 0.0, 0.0] + ) # Mock weight (centroid, major axis, and radius) + params = {"rho": 0.7, "alpha": 1e-5, "mu": 0.5, "r_hat": 1.0} activation, cache = art_model.category_choice(i, w, params) - assert 'dist' in cache + assert "dist" in cache assert isinstance(activation, float) + def test_match_criterion(art_model): # Test the match_criterion method i = np.array([0.2, 0.3]) - w = np.array([0.25, 0.35, 0.5, 0.0, 0.0]) # Mock weight (centroid, major axis, and radius) + w = np.array( + [0.25, 0.35, 0.5, 0.0, 0.0] + ) # Mock weight (centroid, major axis, and radius) params = {"rho": 0.7, "r_hat": 1.0} cache = {"dist": 0.6} - match_criterion, new_cache = art_model.match_criterion(i, w, params, cache=cache) + match_criterion, new_cache = art_model.match_criterion( + i, w, params, cache=cache + ) expected_match_criterion = 1 - (0.0 + max(0.0, 0.6)) / 1.0 assert match_criterion == pytest.approx(expected_match_criterion, rel=1e-6) @@ -73,12 +79,16 @@ def test_update(art_model): # Test the update method art_model.dim_ = 2 i = np.array([0.2, 0.3]) - w = np.array([0.25, 0.35, 0.5, 0.0, 0.0]) # Mock weight (centroid, major axis, and radius) + w = np.array( + [0.25, 0.35, 0.5, 0.0, 0.0] + ) # Mock weight (centroid, major axis, and radius) params = {"beta": 0.1, "mu": 0.5, "r_hat": 1.0} cache = {"dist": 0.6} updated_weight = art_model.update(i, w, params, cache=cache) - assert updated_weight[-1] >= w[-1] # Ensure that the radius has not decreased + assert ( + updated_weight[-1] >= w[-1] + ) # Ensure that the radius has not decreased def test_new_weight(art_model): @@ -91,6 +101,7 @@ def test_new_weight(art_model): assert len(new_weight) == 5 # Centroid (2), major axis (2), and radius (1) assert new_weight[-1] == 0.0 # Initial radius should be 0 + def test_get_cluster_centers(art_model): # Test getting cluster centers art_model.dim_ = 2 @@ -99,6 +110,7 @@ def test_get_cluster_centers(art_model): assert len(centers) == 1 assert np.array_equal(centers[0], np.array([0.2, 0.3])) + def test_fit(art_model): # Test fitting the model art_model.dim_ = 2 @@ -108,6 +120,7 @@ def test_fit(art_model): assert len(art_model.W) > 0 # Ensure that clusters were created + def test_partial_fit(art_model): # Test partial_fit method art_model.dim_ = 2 @@ -117,6 +130,7 @@ def test_partial_fit(art_model): assert len(art_model.W) > 0 # Ensure that clusters were partially fit + def test_predict(art_model): # Test predict method art_model.dim_ = 2 diff --git a/unit_tests/test_FALCON.py b/unit_tests/test_FALCON.py index 813f431..aa55556 100644 --- a/unit_tests/test_FALCON.py +++ b/unit_tests/test_FALCON.py @@ -11,7 +11,12 @@ def falcon_model(): action_art = FuzzyART(0.7, 0.01, 1.0) reward_art = FuzzyART(0.9, 0.01, 1.0) channel_dims = [4, 4, 2] - return FALCON(state_art=state_art, action_art=action_art, reward_art=reward_art, channel_dims=channel_dims) + return FALCON( + state_art=state_art, + action_art=action_art, + reward_art=reward_art, + channel_dims=channel_dims, + ) def test_falcon_initialization(falcon_model): @@ -29,7 +34,9 @@ def test_falcon_fit(falcon_model): rewards = np.random.rand(10, 1) # Prepare data - states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) + states_prep, actions_prep, rewards_prep = falcon_model.prepare_data( + states, actions, rewards + ) falcon_model.fit(states_prep, actions_prep, rewards_prep) @@ -44,7 +51,9 @@ def test_falcon_partial_fit(falcon_model): rewards = np.random.rand(10, 1) # Prepare data - states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) + states_prep, actions_prep, rewards_prep = falcon_model.prepare_data( + states, actions, rewards + ) falcon_model.partial_fit(states_prep, actions_prep, rewards_prep) @@ -59,12 +68,16 @@ def test_falcon_get_actions_and_rewards(falcon_model): rewards = np.random.rand(10, 1) # Prepare data - states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) + states_prep, actions_prep, rewards_prep = falcon_model.prepare_data( + states, actions, rewards + ) falcon_model.fit(states_prep, actions_prep, rewards_prep) - print(states_prep[0,:]) + print(states_prep[0, :]) - action_space, rewards = falcon_model.get_actions_and_rewards(states_prep[0,:]) + action_space, rewards = falcon_model.get_actions_and_rewards( + states_prep[0, :] + ) assert action_space.shape[0] > 0 assert rewards.shape[0] > 0 @@ -77,11 +90,13 @@ def test_falcon_get_action(falcon_model): rewards = np.random.rand(10, 1) # Prepare data - states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) + states_prep, actions_prep, rewards_prep = falcon_model.prepare_data( + states, actions, rewards + ) falcon_model.fit(states_prep, actions_prep, rewards_prep) - action = falcon_model.get_action(states_prep[0,:]) + action = falcon_model.get_action(states_prep[0, :]) assert action.shape[0] == actions.shape[1] @@ -93,11 +108,13 @@ def test_falcon_get_probabilistic_action(falcon_model): rewards = np.random.rand(10, 1) # Prepare data - states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) + states_prep, actions_prep, rewards_prep = falcon_model.prepare_data( + states, actions, rewards + ) falcon_model.fit(states_prep, actions_prep, rewards_prep) - action = falcon_model.get_probabilistic_action(states_prep[0,:]) + action = falcon_model.get_probabilistic_action(states_prep[0, :]) assert isinstance(action.tolist(), float) @@ -109,7 +126,9 @@ def test_falcon_get_rewards(falcon_model): rewards = np.random.rand(10, 1) # Prepare data - states_prep, actions_prep, rewards_prep = falcon_model.prepare_data(states, actions, rewards) + states_prep, actions_prep, rewards_prep = falcon_model.prepare_data( + states, actions, rewards + ) falcon_model.fit(states_prep, actions_prep, rewards_prep) diff --git a/unit_tests/test_FusionART.py b/unit_tests/test_FusionART.py index 0621823..ea9be0d 100644 --- a/unit_tests/test_FusionART.py +++ b/unit_tests/test_FusionART.py @@ -12,14 +12,20 @@ def fusionart_model(): module_b = FuzzyART(0.7, 0.01, 1.0) gamma_values = np.array([0.5, 0.5]) channel_dims = [4, 4] - return FusionART(modules=[module_a, module_b], gamma_values=gamma_values, channel_dims=channel_dims) + return FusionART( + modules=[module_a, module_b], + gamma_values=gamma_values, + channel_dims=channel_dims, + ) def test_initialization(fusionart_model): # Test that the model initializes correctly assert isinstance(fusionart_model.modules[0], BaseART) assert isinstance(fusionart_model.modules[1], BaseART) - assert np.all(fusionart_model.params["gamma_values"] == np.array([0.5, 0.5])) + assert np.all( + fusionart_model.params["gamma_values"] == np.array([0.5, 0.5]) + ) assert fusionart_model.channel_dims == [4, 4] @@ -28,7 +34,9 @@ def test_validate_params(): valid_params = {"gamma_values": np.array([0.5, 0.5])} FusionART.validate_params(valid_params) - invalid_params = {"gamma_values": np.array([0.6, 0.6])} # sum of gamma_values must be 1.0 + invalid_params = { + "gamma_values": np.array([0.6, 0.6]) + } # sum of gamma_values must be 1.0 with pytest.raises(AssertionError): FusionART.validate_params(invalid_params) @@ -102,7 +110,9 @@ def test_step_fit(fusionart_model): # Run step_fit for the first sample label = fusionart_model.step_fit(X_prep[0]) - assert isinstance(label, int) # Ensure the result is an integer cluster label + assert isinstance( + label, int + ) # Ensure the result is an integer cluster label def test_step_pred(fusionart_model): diff --git a/unit_tests/test_FuzzyART.py b/unit_tests/test_FuzzyART.py index 8512756..e07c848 100644 --- a/unit_tests/test_FuzzyART.py +++ b/unit_tests/test_FuzzyART.py @@ -5,42 +5,48 @@ # Assuming BaseART is imported and available in the current namespace + @pytest.fixture def art_model(): # Fixture that sets up the model before each test params = { - 'rho': 0.5, - 'alpha': 0.0, - 'beta': 1.0, + "rho": 0.5, + "alpha": 0.0, + "beta": 1.0, } return FuzzyART(**params) + def test_initialization(art_model): # Test that the ART model initializes correctly - assert art_model.params == {'rho': 0.5, 'alpha': 0.0, 'beta': 1.0} + assert art_model.params == {"rho": 0.5, "alpha": 0.0, "beta": 1.0} assert art_model.sample_counter_ == 0 assert art_model.weight_sample_counter_ == [] + def test_set_get_params(art_model): # Test set_params and get_params functions - new_params = {'rho': 0.7, 'alpha': 0.05, 'beta': 0.9} + new_params = {"rho": 0.7, "alpha": 0.05, "beta": 0.9} art_model.set_params(**new_params) assert art_model.get_params() == new_params assert art_model.rho == 0.7 assert art_model.alpha == 0.05 assert art_model.beta == 0.9 + def test_attribute_access(art_model): # Test dynamic attribute access and setting using params assert art_model.rho == 0.5 art_model.rho = 0.8 assert art_model.rho == 0.8 + def test_invalid_attribute(art_model): # Test accessing an invalid attribute with pytest.raises(AttributeError): art_model.non_existing_attribute + def test_prepare_restore_data(art_model): # Test data normalization and denormalization X = np.array([[1, 2], [3, 4], [5, 6]]) @@ -48,6 +54,7 @@ def test_prepare_restore_data(art_model): restored_X = art_model.restore_data(normalized_X) np.testing.assert_array_almost_equal(restored_X, X) + def test_check_dimensions(art_model): # Test check_dimensions with valid data X = np.array([[1, 2], [3, 4]]) @@ -59,24 +66,36 @@ def test_check_dimensions(art_model): with pytest.raises(AssertionError): art_model.check_dimensions(X_invalid) + def test_match_tracking(art_model): # Test match tracking with different methods - cache = {'match_criterion': 0.5} - art_model._match_tracking(cache, epsilon=0.01, params=art_model.params, method='MT+') + cache = {"match_criterion": 0.5} + art_model._match_tracking( + cache, epsilon=0.01, params=art_model.params, method="MT+" + ) assert art_model.rho == 0.51 - art_model._match_tracking(cache, epsilon=0.01, params=art_model.params, method='MT-') + art_model._match_tracking( + cache, epsilon=0.01, params=art_model.params, method="MT-" + ) assert art_model.rho == 0.49 - art_model._match_tracking(cache, epsilon=0.01, params=art_model.params, method='MT0') + art_model._match_tracking( + cache, epsilon=0.01, params=art_model.params, method="MT0" + ) assert art_model.rho == 0.5 - art_model._match_tracking(cache, epsilon=0.01, params=art_model.params, method='MT~') + art_model._match_tracking( + cache, epsilon=0.01, params=art_model.params, method="MT~" + ) assert art_model.rho == 0.5 - art_model._match_tracking(cache, epsilon=0.01, params=art_model.params, method='MT1') + art_model._match_tracking( + cache, epsilon=0.01, params=art_model.params, method="MT1" + ) assert np.isinf(art_model.rho) + def test_step_fit(art_model): # Test step_fit for creating new clusters X = np.array([[0.1, 0.2], [0.3, 0.4]]) @@ -92,6 +111,7 @@ def test_step_fit(art_model): assert label == 0 art_model.add_weight.assert_called_once() + def test_partial_fit(art_model): # Test partial_fit X = np.array([[0.1, 0.2], [0.3, 0.4]]) @@ -105,6 +125,7 @@ def test_partial_fit(art_model): art_model.partial_fit(X) art_model.add_weight.assert_called() + def test_predict(art_model): # Test predict function X = np.array([[0.1, 0.2], [0.3, 0.4]]) @@ -117,20 +138,13 @@ def test_predict(art_model): def test_clustering(art_model): - new_params = {'rho': 0.9, 'alpha': 0.05, 'beta': 1.0} + new_params = {"rho": 0.9, "alpha": 0.05, "beta": 1.0} art_model.set_params(**new_params) data = np.array( - [ - [0.0, 0.0], - [0.0, 0.08], - [0.0, 1.0], - [1.0, 1.0], - [1.0, 0.0] - ] + [[0.0, 0.0], [0.0, 0.08], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]] ) data = art_model.prepare_data(data) labels = art_model.fit_predict(data) assert np.all(np.equal(labels, np.array([0, 0, 1, 2, 3]))) - diff --git a/unit_tests/test_GaussianART.py b/unit_tests/test_GaussianART.py index a8739ee..2e8f011 100644 --- a/unit_tests/test_GaussianART.py +++ b/unit_tests/test_GaussianART.py @@ -3,6 +3,7 @@ from unittest.mock import MagicMock from artlib.elementary.GaussianART import GaussianART + # Fixture to initialize a GaussianART instance for testing @pytest.fixture def art_model(): @@ -11,65 +12,78 @@ def art_model(): alpha = 1e-5 return GaussianART(rho=rho, sigma_init=sigma_init, alpha=alpha) + def test_initialization(art_model): # Test that the model initializes correctly - assert art_model.params['rho'] == 0.7 - assert np.array_equal(art_model.params['sigma_init'], np.array([0.5, 0.5])) - assert art_model.params['alpha'] == 1e-5 + assert art_model.params["rho"] == 0.7 + assert np.array_equal(art_model.params["sigma_init"], np.array([0.5, 0.5])) + assert art_model.params["alpha"] == 1e-5 assert art_model.sample_counter_ == 0 assert art_model.weight_sample_counter_ == [] + def test_validate_params(): # Test the validate_params method valid_params = { "rho": 0.5, "sigma_init": np.array([0.5, 0.5]), - "alpha": 1e-5 + "alpha": 1e-5, } GaussianART.validate_params(valid_params) invalid_params = { "rho": 1.5, # Invalid vigilance parameter "sigma_init": np.array([0.5, 0.5]), - "alpha": -1e-5 # Invalid alpha + "alpha": -1e-5, # Invalid alpha } with pytest.raises(AssertionError): GaussianART.validate_params(invalid_params) + def test_category_choice(art_model): # Test the category_choice method art_model.dim_ = 2 i = np.array([0.2, 0.3]) - w = np.array([0.25, 0.35, 2.0, 2.5, 0.5, 0.6, 1.2, 1.0, 5]) # Mock weight vector + w = np.array( + [0.25, 0.35, 2.0, 2.5, 0.5, 0.6, 1.2, 1.0, 5] + ) # Mock weight vector art_model.W = [w] - params = { - "rho": 0.7, - "alpha": 1e-5 - } + params = {"rho": 0.7, "alpha": 1e-5} activation, cache = art_model.category_choice(i, w, params) - assert 'exp_dist_sig_dist' in cache + assert "exp_dist_sig_dist" in cache assert isinstance(activation, float) + def test_match_criterion(art_model): # Test the match_criterion method cache = {"exp_dist_sig_dist": 0.8} i = np.array([0.2, 0.3]) - w = np.array([0.25, 0.35, 2.0, 2.5, 0.5, 0.6, 1.2, 1.0, 5]) # Mock weight vector + w = np.array( + [0.25, 0.35, 2.0, 2.5, 0.5, 0.6, 1.2, 1.0, 5] + ) # Mock weight vector params = {"rho": 0.7} - match_criterion, new_cache = art_model.match_criterion(i, w, params, cache=cache) + match_criterion, new_cache = art_model.match_criterion( + i, w, params, cache=cache + ) assert match_criterion == cache["exp_dist_sig_dist"] + def test_update(art_model): # Test the update method art_model.dim_ = 2 i = np.array([0.2, 0.3]) - w = np.array([0.25, 0.35, 2.0, 2.5, 0.5, 0.6, 1.2, 1.0, 5]) # Mock weight vector + w = np.array( + [0.25, 0.35, 2.0, 2.5, 0.5, 0.6, 1.2, 1.0, 5] + ) # Mock weight vector params = {"alpha": 1e-5} updated_weight = art_model.update(i, w, params) - assert updated_weight[-1] == 6 # Check that the sample count has been updated + assert ( + updated_weight[-1] == 6 + ) # Check that the sample count has been updated + def test_new_weight(art_model): # Test the new_weight method @@ -77,7 +91,10 @@ def test_new_weight(art_model): params = {"sigma_init": np.array([0.5, 0.5])} new_weight = art_model.new_weight(i, params) - assert len(new_weight) == 8 # Mean, sigma, inverse sigma, determinant, and count + assert ( + len(new_weight) == 8 + ) # Mean, sigma, inverse sigma, determinant, and count + def test_get_cluster_centers(art_model): # Test getting cluster centers @@ -96,6 +113,7 @@ def test_fit(art_model): assert len(art_model.W) > 0 # Ensure that clusters were created + def test_partial_fit(art_model): # Test partial_fit method X = np.array([[0.1, 0.2], [0.3, 0.4]]) @@ -104,6 +122,7 @@ def test_partial_fit(art_model): assert len(art_model.W) > 0 # Ensure that clusters were partially fit + def test_predict(art_model): # Test predict method X = np.array([[0.1, 0.2], [0.3, 0.4]]) diff --git a/unit_tests/test_HypersphereART.py b/unit_tests/test_HypersphereART.py index c45de66..5001728 100644 --- a/unit_tests/test_HypersphereART.py +++ b/unit_tests/test_HypersphereART.py @@ -2,6 +2,7 @@ import numpy as np from artlib.elementary.HypersphereART import HypersphereART + # Fixture to initialize a HypersphereART instance for testing @pytest.fixture def art_model(): @@ -11,47 +12,42 @@ def art_model(): r_hat = 1.0 return HypersphereART(rho=rho, alpha=alpha, beta=beta, r_hat=r_hat) + def test_initialization(art_model): # Test that the model initializes correctly - assert art_model.params['rho'] == 0.7 - assert art_model.params['alpha'] == 1e-5 - assert art_model.params['beta'] == 0.1 - assert art_model.params['r_hat'] == 1.0 + assert art_model.params["rho"] == 0.7 + assert art_model.params["alpha"] == 1e-5 + assert art_model.params["beta"] == 0.1 + assert art_model.params["r_hat"] == 1.0 + def test_validate_params(): # Test the validate_params method - valid_params = { - "rho": 0.7, - "alpha": 1e-5, - "beta": 0.1, - "r_hat": 1.0 - } + valid_params = {"rho": 0.7, "alpha": 1e-5, "beta": 0.1, "r_hat": 1.0} HypersphereART.validate_params(valid_params) invalid_params = { "rho": 1.5, # Invalid vigilance parameter "alpha": -1e-5, # Invalid alpha "beta": 1.1, # Invalid beta - "r_hat": -1.0 # Invalid r_hat + "r_hat": -1.0, # Invalid r_hat } with pytest.raises(AssertionError): HypersphereART.validate_params(invalid_params) + def test_category_choice(art_model): # Test the category_choice method i = np.array([0.2, 0.3]) w = np.array([0.25, 0.35, 0.5]) # Mock weight (centroid and radius) - params = { - "rho": 0.7, - "alpha": 1e-5, - "r_hat": 1.0 - } + params = {"rho": 0.7, "alpha": 1e-5, "r_hat": 1.0} activation, cache = art_model.category_choice(i, w, params) - assert 'max_radius' in cache - assert 'i_radius' in cache + assert "max_radius" in cache + assert "i_radius" in cache assert isinstance(activation, float) + def test_match_criterion(art_model): # Test the match_criterion method i = np.array([0.2, 0.3]) @@ -59,8 +55,11 @@ def test_match_criterion(art_model): params = {"rho": 0.7, "r_hat": 1.0} cache = {"max_radius": 0.6} - match_criterion, new_cache = art_model.match_criterion(i, w, params, cache=cache) - assert match_criterion == 1 - (max(0.5, 0.6)/1.0) + match_criterion, new_cache = art_model.match_criterion( + i, w, params, cache=cache + ) + assert match_criterion == 1 - (max(0.5, 0.6) / 1.0) + def test_update(art_model): # Test the update method @@ -70,9 +69,12 @@ def test_update(art_model): cache = {"max_radius": 0.6, "i_radius": 0.55} updated_weight = art_model.update(i, w, params, cache=cache) - assert len(updated_weight) == 3 # Check that the weight has a centroid and radius + assert ( + len(updated_weight) == 3 + ) # Check that the weight has a centroid and radius assert updated_weight[-1] > 0.5 # Check that the radius has been updated + def test_new_weight(art_model): # Test the new_weight method i = np.array([0.2, 0.3]) @@ -82,6 +84,7 @@ def test_new_weight(art_model): assert len(new_weight) == 3 # Centroid (2D) + radius assert new_weight[-1] == 0.0 # Initial radius should be 0 + def test_get_cluster_centers(art_model): # Test getting cluster centers art_model.W = [np.array([0.2, 0.3, 0.5])] @@ -89,6 +92,7 @@ def test_get_cluster_centers(art_model): assert len(centers) == 1 assert np.array_equal(centers[0], np.array([0.2, 0.3])) + def test_fit(art_model): # Test fitting the model X = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]) @@ -97,6 +101,7 @@ def test_fit(art_model): assert len(art_model.W) > 0 # Ensure that clusters were created + def test_partial_fit(art_model): # Test partial_fit method X = np.array([[0.1, 0.2], [0.3, 0.4]]) @@ -105,6 +110,7 @@ def test_partial_fit(art_model): assert len(art_model.W) > 0 # Ensure that clusters were partially fit + def test_predict(art_model): # Test predict method X = np.array([[0.1, 0.2], [0.3, 0.4]]) diff --git a/unit_tests/test_QuadraticNeuronART.py b/unit_tests/test_QuadraticNeuronART.py index d1147f0..380e351 100644 --- a/unit_tests/test_QuadraticNeuronART.py +++ b/unit_tests/test_QuadraticNeuronART.py @@ -2,6 +2,7 @@ import numpy as np from artlib.elementary.QuadraticNeuronART import QuadraticNeuronART + # Fixture to initialize a QuadraticNeuronART instance for testing @pytest.fixture def art_model(): @@ -10,15 +11,19 @@ def art_model(): lr_b = 0.1 lr_w = 0.1 lr_s = 0.05 - return QuadraticNeuronART(rho=rho, s_init=s_init, lr_b=lr_b, lr_w=lr_w, lr_s=lr_s) + return QuadraticNeuronART( + rho=rho, s_init=s_init, lr_b=lr_b, lr_w=lr_w, lr_s=lr_s + ) + def test_initialization(art_model): # Test that the model initializes correctly - assert art_model.params['rho'] == 0.7 - assert art_model.params['s_init'] == 0.5 - assert art_model.params['lr_b'] == 0.1 - assert art_model.params['lr_w'] == 0.1 - assert art_model.params['lr_s'] == 0.05 + assert art_model.params["rho"] == 0.7 + assert art_model.params["s_init"] == 0.5 + assert art_model.params["lr_b"] == 0.1 + assert art_model.params["lr_w"] == 0.1 + assert art_model.params["lr_s"] == 0.05 + def test_validate_params(): # Test the validate_params method @@ -27,7 +32,7 @@ def test_validate_params(): "s_init": 0.5, "lr_b": 0.1, "lr_w": 0.1, - "lr_s": 0.05 + "lr_s": 0.05, } QuadraticNeuronART.validate_params(valid_params) @@ -36,41 +41,49 @@ def test_validate_params(): "s_init": -0.5, # Invalid s_init "lr_b": 1.1, # Invalid learning rate for cluster mean "lr_w": -0.1, # Invalid learning rate for cluster weights - "lr_s": 1.5 # Invalid learning rate for activation parameter + "lr_s": 1.5, # Invalid learning rate for activation parameter } with pytest.raises(AssertionError): QuadraticNeuronART.validate_params(invalid_params) + def test_category_choice(art_model): # Test the category_choice method art_model.dim_ = 2 i = np.array([0.2, 0.3]) - w = np.array([1.0, 0.0, 0.0, 1.0, 0.25, 0.35, 0.5]) # Mock weight (matrix, centroid, and activation parameter) - params = { - "rho": 0.7, - "s_init": 0.5 - } + w = np.array( + [1.0, 0.0, 0.0, 1.0, 0.25, 0.35, 0.5] + ) # Mock weight (matrix, centroid, and activation parameter) + params = {"rho": 0.7, "s_init": 0.5} activation, cache = art_model.category_choice(i, w, params) - assert 'activation' in cache - assert 'l2norm2_z_b' in cache + assert "activation" in cache + assert "l2norm2_z_b" in cache assert isinstance(activation, float) + def test_match_criterion(art_model): # Test the match_criterion method i = np.array([0.2, 0.3]) - w = np.array([1.0, 0.0, 0.0, 1.0, 0.25, 0.35, 0.5]) # Mock weight (matrix, centroid, and activation parameter) + w = np.array( + [1.0, 0.0, 0.0, 1.0, 0.25, 0.35, 0.5] + ) # Mock weight (matrix, centroid, and activation parameter) params = {"rho": 0.7} cache = {"activation": 0.8} - match_criterion, new_cache = art_model.match_criterion(i, w, params, cache=cache) + match_criterion, new_cache = art_model.match_criterion( + i, w, params, cache=cache + ) assert match_criterion == cache["activation"] + def test_update(art_model): # Test the update method art_model.dim_ = 2 i = np.array([0.2, 0.3]) - w = np.array([1.0, 0.0, 0.0, 1.0, 0.25, 0.35, 0.5]) # Mock weight (matrix, centroid, and activation parameter) + w = np.array( + [1.0, 0.0, 0.0, 1.0, 0.25, 0.35, 0.5] + ) # Mock weight (matrix, centroid, and activation parameter) params = {"lr_b": 0.1, "lr_w": 0.1, "lr_s": 0.05} cache = { "s": 0.5, @@ -78,12 +91,17 @@ def test_update(art_model): "b": np.array([0.25, 0.35]), "z": np.array([0.2, 0.3]), "activation": 0.8, - "l2norm2_z_b": 0.02 + "l2norm2_z_b": 0.02, } updated_weight = art_model.update(i, w, params, cache=cache) - assert len(updated_weight) == 7 # Check that the weight has matrix, centroid, and activation parameter - assert updated_weight[-1] < 0.5 # Check that the activation parameter has been updated + assert ( + len(updated_weight) == 7 + ) # Check that the weight has matrix, centroid, and activation parameter + assert ( + updated_weight[-1] < 0.5 + ) # Check that the activation parameter has been updated + def test_new_weight(art_model): # Test the new_weight method @@ -92,8 +110,13 @@ def test_new_weight(art_model): params = {"s_init": 0.5} new_weight = art_model.new_weight(i, params) - assert len(new_weight) == 7 # Weight matrix (4 values), centroid (2 values), and activation parameter (1 value) - assert new_weight[-1] == 0.5 # Initial activation parameter should be s_init + assert ( + len(new_weight) == 7 + ) # Weight matrix (4 values), centroid (2 values), and activation parameter (1 value) + assert ( + new_weight[-1] == 0.5 + ) # Initial activation parameter should be s_init + def test_get_cluster_centers(art_model): # Test getting cluster centers @@ -103,6 +126,7 @@ def test_get_cluster_centers(art_model): assert len(centers) == 1 assert np.array_equal(centers[0], np.array([0.2, 0.3])) + def test_fit(art_model): # Test fitting the model art_model.dim_ = 2 @@ -112,6 +136,7 @@ def test_fit(art_model): assert len(art_model.W) > 0 # Ensure that clusters were created + def test_partial_fit(art_model): # Test partial_fit method art_model.dim_ = 2 @@ -121,6 +146,7 @@ def test_partial_fit(art_model): assert len(art_model.W) > 0 # Ensure that clusters were partially fit + def test_predict(art_model): # Test predict method art_model.dim_ = 2 diff --git a/unit_tests/test_SMART.py b/unit_tests/test_SMART.py index b6a3126..cc70426 100644 --- a/unit_tests/test_SMART.py +++ b/unit_tests/test_SMART.py @@ -5,6 +5,7 @@ from artlib.common.BaseART import BaseART from matplotlib.axes import Axes + # Fixture to initialize a SMART instance for testing @pytest.fixture def smart_model(): @@ -12,6 +13,7 @@ def smart_model(): rho_values = [0.2, 0.5, 0.7] return SMART(FuzzyART, rho_values, base_params) + def test_initialization(smart_model): # Test that the model initializes correctly assert len(smart_model.rho_values) == 3 @@ -19,6 +21,7 @@ def test_initialization(smart_model): assert isinstance(smart_model.modules[1], BaseART) assert isinstance(smart_model.modules[2], BaseART) + def test_prepare_and_restore_data(smart_model): # Test prepare_data and restore_data methods X = np.random.rand(10, 5) @@ -28,6 +31,7 @@ def test_prepare_and_restore_data(smart_model): X_restored = smart_model.restore_data(X_prep) assert np.allclose(X_restored, X) + def test_fit(smart_model): # Test the fit method X = np.random.rand(10, 5) @@ -38,6 +42,7 @@ def test_fit(smart_model): assert smart_model.modules[0].labels_.shape[0] == X.shape[0] + def test_partial_fit(smart_model): # Test the partial_fit method X = np.random.rand(10, 5) @@ -48,4 +53,3 @@ def test_partial_fit(smart_model): smart_model.partial_fit(X_prep) assert smart_model.modules[0].labels_.shape[0] == X.shape[0] - diff --git a/unit_tests/test_SimpleARTMAP.py b/unit_tests/test_SimpleARTMAP.py index 43be3c8..c5c2544 100644 --- a/unit_tests/test_SimpleARTMAP.py +++ b/unit_tests/test_SimpleARTMAP.py @@ -5,21 +5,25 @@ from artlib.common.BaseART import BaseART from sklearn.utils.validation import NotFittedError + # Fixture to initialize a SimpleARTMAP instance for testing @pytest.fixture def simple_artmap_model(): module_a = FuzzyART(0.5, 0.01, 1.0) return SimpleARTMAP(module_a=module_a) + def test_initialization(simple_artmap_model): # Test that the model initializes correctly assert isinstance(simple_artmap_model.module_a, BaseART) + def test_get_params(simple_artmap_model): # Test the get_params method params = simple_artmap_model.get_params() assert "module_a" in params + def test_validate_data(simple_artmap_model): # Test the validate_data method X = np.random.rand(10, 5) @@ -35,6 +39,7 @@ def test_validate_data(simple_artmap_model): with pytest.raises(ValueError): simple_artmap_model.validate_data(X_invalid, y) + def test_prepare_and_restore_data(simple_artmap_model): # Test prepare_data and restore_data methods X = np.random.rand(10, 5) @@ -44,6 +49,7 @@ def test_prepare_and_restore_data(simple_artmap_model): X_restored = simple_artmap_model.restore_data(X_prep) assert np.allclose(X_restored, X) + def test_fit(simple_artmap_model): # Test the fit method X = np.random.rand(10, 5) @@ -55,6 +61,7 @@ def test_fit(simple_artmap_model): assert simple_artmap_model.module_a.labels_.shape[0] == X.shape[0] + def test_partial_fit(simple_artmap_model): # Test the partial_fit method X = np.random.rand(10, 5) @@ -66,6 +73,7 @@ def test_partial_fit(simple_artmap_model): assert simple_artmap_model.module_a.labels_.shape[0] == X.shape[0] + def test_predict(simple_artmap_model): # Test the predict method X = np.random.rand(10, 5) @@ -78,6 +86,7 @@ def test_predict(simple_artmap_model): predictions = simple_artmap_model.predict(X_prep) assert predictions.shape[0] == X.shape[0] + def test_predict_ab(simple_artmap_model): # Test the predict_ab method X = np.random.rand(10, 5) @@ -91,6 +100,7 @@ def test_predict_ab(simple_artmap_model): assert predictions_a.shape[0] == X.shape[0] assert predictions_b.shape[0] == X.shape[0] + def test_predict_not_fitted(simple_artmap_model): # Test that predict raises an error if the model is not fitted X = np.random.rand(10, 5) @@ -98,6 +108,7 @@ def test_predict_not_fitted(simple_artmap_model): with pytest.raises(NotFittedError): simple_artmap_model.predict(X) + def test_step_fit(simple_artmap_model): # Test the step_fit method X = np.random.rand(10, 5) @@ -111,6 +122,7 @@ def test_step_fit(simple_artmap_model): c_a = simple_artmap_model.step_fit(X_prep[0], y[0]) assert isinstance(c_a, int) # Ensure the result is an integer cluster label + def test_step_pred(simple_artmap_model): # Test the step_pred method X = np.random.rand(10, 5) diff --git a/unit_tests/test_TD_FALCON.py b/unit_tests/test_TD_FALCON.py index b5f01d1..0018ee5 100644 --- a/unit_tests/test_TD_FALCON.py +++ b/unit_tests/test_TD_FALCON.py @@ -11,7 +11,12 @@ def td_falcon_model(): action_art = FuzzyART(0.7, 0.01, 1.0) reward_art = FuzzyART(0.9, 0.01, 1.0) channel_dims = [4, 4, 2] - return TD_FALCON(state_art=state_art, action_art=action_art, reward_art=reward_art, channel_dims=channel_dims) + return TD_FALCON( + state_art=state_art, + action_art=action_art, + reward_art=reward_art, + channel_dims=channel_dims, + ) def test_td_falcon_initialization(td_falcon_model): @@ -41,12 +46,16 @@ def test_td_falcon_partial_fit(td_falcon_model): rewards = np.random.rand(10, 1) # Prepare data - states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data(states, actions, rewards) + states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data( + states, actions, rewards + ) td_falcon_model.partial_fit(states_prep, actions_prep, rewards_prep) assert len(td_falcon_model.fusion_art.W) > 0 - assert td_falcon_model.fusion_art.labels_.shape[0] == states_prep.shape[0]-1 + assert ( + td_falcon_model.fusion_art.labels_.shape[0] == states_prep.shape[0] - 1 + ) def test_td_falcon_calculate_SARSA(td_falcon_model): @@ -55,17 +64,29 @@ def test_td_falcon_calculate_SARSA(td_falcon_model): actions = np.random.rand(10, 2) rewards = np.random.rand(10, 1) - states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data(states, actions, rewards) + states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data( + states, actions, rewards + ) # Test with multiple samples - states_fit, actions_fit, sarsa_rewards_fit = td_falcon_model.calculate_SARSA(states_prep, actions_prep, rewards_prep) + ( + states_fit, + actions_fit, + sarsa_rewards_fit, + ) = td_falcon_model.calculate_SARSA(states_prep, actions_prep, rewards_prep) assert states_fit.shape == (9, 4) # Last sample is discarded for SARSA assert actions_fit.shape == (9, 4) assert sarsa_rewards_fit.shape == (9, 2) # Test with single sample - states_fit, actions_fit, sarsa_rewards_fit = td_falcon_model.calculate_SARSA(states_prep[:1], actions_prep[:1], rewards_prep[:1]) + ( + states_fit, + actions_fit, + sarsa_rewards_fit, + ) = td_falcon_model.calculate_SARSA( + states_prep[:1], actions_prep[:1], rewards_prep[:1] + ) assert states_fit.shape == (1, 4) assert actions_fit.shape == (1, 4) @@ -79,11 +100,15 @@ def test_td_falcon_get_actions_and_rewards(td_falcon_model): rewards = np.random.rand(10, 1) # Prepare data - states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data(states, actions, rewards) + states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data( + states, actions, rewards + ) td_falcon_model.partial_fit(states_prep, actions_prep, rewards_prep) - action_space, rewards = td_falcon_model.get_actions_and_rewards(states_prep[0, :]) + action_space, rewards = td_falcon_model.get_actions_and_rewards( + states_prep[0, :] + ) assert action_space.shape[0] > 0 assert rewards.shape[0] > 0 @@ -96,7 +121,9 @@ def test_td_falcon_get_action(td_falcon_model): rewards = np.random.rand(10, 1) # Prepare data - states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data(states, actions, rewards) + states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data( + states, actions, rewards + ) td_falcon_model.partial_fit(states_prep, actions_prep, rewards_prep) @@ -112,7 +139,9 @@ def test_td_falcon_get_probabilistic_action(td_falcon_model): rewards = np.random.rand(10, 1) # Prepare data - states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data(states, actions, rewards) + states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data( + states, actions, rewards + ) td_falcon_model.partial_fit(states_prep, actions_prep, rewards_prep) @@ -128,7 +157,9 @@ def test_td_falcon_get_rewards(td_falcon_model): rewards = np.random.rand(10, 1) # Prepare data - states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data(states, actions, rewards) + states_prep, actions_prep, rewards_prep = td_falcon_model.prepare_data( + states, actions, rewards + ) td_falcon_model.partial_fit(states_prep, actions_prep, rewards_prep) diff --git a/unit_tests/test_TopoART.py b/unit_tests/test_TopoART.py index c80dede..3627846 100644 --- a/unit_tests/test_TopoART.py +++ b/unit_tests/test_TopoART.py @@ -3,11 +3,13 @@ from artlib.topological.TopoART import TopoART from artlib.elementary.FuzzyART import FuzzyART + @pytest.fixture def topoart_model(): base_module = FuzzyART(0.5, 0.01, 1.0) return TopoART(base_module, beta_lower=0.5, tau=10, phi=5) + def test_initialization(topoart_model): # Test that the model initializes correctly assert isinstance(topoart_model.base_module, FuzzyART) @@ -15,18 +17,28 @@ def test_initialization(topoart_model): assert topoart_model.params["tau"] == 10 assert topoart_model.params["phi"] == 5 + def test_validate_params(): # Test the validate_params method valid_params = {"beta": 0.8, "beta_lower": 0.5, "tau": 10, "phi": 5} TopoART.validate_params(valid_params) - invalid_params = {"beta": 0.4, "beta_lower": 0.5, "tau": 10, "phi": 5} # beta must be >= beta_lower + invalid_params = { + "beta": 0.4, + "beta_lower": 0.5, + "tau": 10, + "phi": 5, + } # beta must be >= beta_lower with pytest.raises(AssertionError): TopoART.validate_params(invalid_params) + def test_get_cluster_centers(topoart_model): # Test the get_cluster_centers method - topoart_model.base_module.W = [np.array([0.5, 1.0, 0.5, 1.0]), np.array([0.1, 0.4, 0.5, 0.4])] + topoart_model.base_module.W = [ + np.array([0.5, 1.0, 0.5, 1.0]), + np.array([0.1, 0.4, 0.5, 0.4]), + ] topoart_model.base_module.d_min_ = np.array([0.0, 0.0]) topoart_model.base_module.d_max_ = np.array([1.0, 1.0]) @@ -36,6 +48,7 @@ def test_get_cluster_centers(topoart_model): assert np.allclose(centers[0], np.array([0.5, 0.5])) assert np.allclose(centers[1], np.array([0.3, 0.5])) + def test_prepare_and_restore_data(topoart_model): # Test prepare_data and restore_data methods X = np.random.rand(10, 2) @@ -45,6 +58,7 @@ def test_prepare_and_restore_data(topoart_model): X_restored = topoart_model.restore_data(X_prep) assert np.allclose(X_restored, X) + def test_step_fit(topoart_model): # Test the step_fit method with base_module's internal methods X = np.random.rand(10, 2) @@ -52,16 +66,19 @@ def test_step_fit(topoart_model): topoart_model.validate_data(X_prep) topoart_model.base_module.W = [] - label = topoart_model.step_fit(X_prep[0,:]) + label = topoart_model.step_fit(X_prep[0, :]) - assert isinstance(label, int) # Ensure the result is an integer cluster label + assert isinstance( + label, int + ) # Ensure the result is an integer cluster label assert label == 0 # First label should be 0 # Add more data and check the adjacency matrix and labels for i in range(1, 10): - label = topoart_model.step_fit(X_prep[i,:]) + label = topoart_model.step_fit(X_prep[i, :]) assert isinstance(label, int) + def test_adjacency_matrix(topoart_model): # Test that the adjacency matrix updates correctly np.random.seed(42) @@ -70,25 +87,34 @@ def test_adjacency_matrix(topoart_model): topoart_model.validate_data(X_prep) topoart_model.base_module.W = [] - topoart_model.step_fit(X_prep[0,:]) + topoart_model.step_fit(X_prep[0, :]) assert topoart_model.adjacency.shape == (1, 1) - topoart_model.step_fit(X_prep[1,:]) + topoart_model.step_fit(X_prep[1, :]) assert topoart_model.adjacency.shape == (1, 1) # Add more data and check the adjacency matrix - topoart_model.step_fit(X_prep[2,:]) + topoart_model.step_fit(X_prep[2, :]) assert topoart_model.adjacency.shape == (2, 2) + def test_prune(topoart_model): # Test the pruning mechanism np.random.seed(42) X = np.random.rand(10, 2) topoart_model.base_module.W = [np.random.rand(2) for _ in range(5)] - topoart_model.weight_sample_counter_ = [2, 6, 6, 20, 25] # Sample counter for pruning + topoart_model.weight_sample_counter_ = [ + 2, + 6, + 6, + 20, + 25, + ] # Sample counter for pruning topoart_model._permanent_mask = np.zeros((5,), dtype=bool) - topoart_model.adjacency = np.random.randint(0,10, (5,5)) - topoart_model.labels_ = np.random.randint(0,5,(10,)) + topoart_model.adjacency = np.random.randint(0, 10, (5, 5)) + topoart_model.labels_ = np.random.randint(0, 5, (10,)) topoart_model.prune(X) - assert len(topoart_model.W) == 4 # W should have 4 remaining weights after pruning + assert ( + len(topoart_model.W) == 4 + ) # W should have 4 remaining weights after pruning diff --git a/unit_tests/test_clustering_consistency.py b/unit_tests/test_clustering_consistency.py index 882aeaa..10d8533 100644 --- a/unit_tests/test_clustering_consistency.py +++ b/unit_tests/test_clustering_consistency.py @@ -2,8 +2,17 @@ import pytest import numpy as np from pathlib import Path -from artlib import ART1, ART2A, BayesianART, DualVigilanceART, EllipsoidART, FuzzyART, GaussianART, HypersphereART, \ - QuadraticNeuronART +from artlib import ( + ART1, + ART2A, + BayesianART, + DualVigilanceART, + EllipsoidART, + FuzzyART, + GaussianART, + HypersphereART, + QuadraticNeuronART, +) # Factory function to initialize models, handling special cases like DualVigilanceART @@ -19,7 +28,9 @@ def model_factory(model_class, params): def cluster_results(): # Define the path to the pickle file current_file_path = Path(__file__).resolve().parent.parent - pickle_file = current_file_path / "unit_tests" / "cluster_results_snapshot.pkl" + pickle_file = ( + current_file_path / "unit_tests" / "cluster_results_snapshot.pkl" + ) # Load the results with open(pickle_file, "rb") as f: @@ -52,7 +63,14 @@ def test_clustering_consistency(model_name, cluster_results): # Generate blob data (same data used when saving the pickle file) from sklearn.datasets import make_blobs - data, _ = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False) + + data, _ = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) # Prepare the data X = model_instance.prepare_data(data) @@ -61,5 +79,6 @@ def test_clustering_consistency(model_name, cluster_results): predicted_labels = model_instance.fit_predict(X) # Check that the predicted labels match the stored labels - assert np.array_equal(predicted_labels, stored_labels), f"Labels for {model_name} do not match!" - + assert np.array_equal( + predicted_labels, stored_labels + ), f"Labels for {model_name} do not match!" diff --git a/unit_tests/test_iCVI_FuzzyART.py b/unit_tests/test_iCVI_FuzzyART.py index c31e642..865f4e5 100644 --- a/unit_tests/test_iCVI_FuzzyART.py +++ b/unit_tests/test_iCVI_FuzzyART.py @@ -8,21 +8,31 @@ @pytest.fixture def icvi_fuzzyart_model(): # Initialize iCVIFuzzyART with Calinski-Harabasz validity index and offline mode - return iCVIFuzzyART(rho=0.5, alpha=0.01, beta=1.0, validity=iCVIFuzzyART.CALINSKIHARABASZ, offline=True) + return iCVIFuzzyART( + rho=0.5, + alpha=0.01, + beta=1.0, + validity=iCVIFuzzyART.CALINSKIHARABASZ, + offline=True, + ) def test_icvi_fuzzyart_initialization(icvi_fuzzyart_model): # Test that the model initializes correctly assert isinstance(icvi_fuzzyart_model, iCVIFuzzyART) - assert icvi_fuzzyart_model.params['validity'] == iCVIFuzzyART.CALINSKIHARABASZ + assert ( + icvi_fuzzyart_model.params["validity"] == iCVIFuzzyART.CALINSKIHARABASZ + ) assert icvi_fuzzyart_model.offline is True def test_icvi_fuzzyart_validate_params(icvi_fuzzyart_model): # Test if validity parameter is validated correctly - assert 'validity' in icvi_fuzzyart_model.params - assert isinstance(icvi_fuzzyart_model.params['validity'], int) - assert icvi_fuzzyart_model.params['validity'] == iCVIFuzzyART.CALINSKIHARABASZ + assert "validity" in icvi_fuzzyart_model.params + assert isinstance(icvi_fuzzyart_model.params["validity"], int) + assert ( + icvi_fuzzyart_model.params["validity"] == iCVIFuzzyART.CALINSKIHARABASZ + ) def test_icvi_fuzzyart_prepare_and_restore_data(icvi_fuzzyart_model): @@ -60,7 +70,9 @@ def test_icvi_fuzzyart_iCVI_match(icvi_fuzzyart_model): w = icvi_fuzzyart_model.W[0] # Test iCVI_match functionality - result = icvi_fuzzyart_model.iCVI_match(x, w, 0, icvi_fuzzyart_model.params, {}) + result = icvi_fuzzyart_model.iCVI_match( + x, w, 0, icvi_fuzzyart_model.params, {} + ) assert isinstance(result, np.bool_) From ea4752b19dc779aac18aa7f6ce26e2d2decaac9f Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 22:03:52 -0500 Subject: [PATCH 088/139] test pre-commit --- .pre-commit-config.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bebb43b..c86e5e1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,13 +26,13 @@ repos: rev: 23.9.1 # Use the latest stable version hooks: - id: black -# -# - repo: https://github.com/PyCQA/flake8 -# rev: 6.1.0 # Use the latest stable version -# hooks: -# - id: flake8 -# args: [--max-line-length=80] -# additional_dependencies: [flake8-docstrings] + + - repo: https://github.com/PyCQA/flake8 + rev: 6.1.0 # Use the latest stable version + hooks: + - id: flake8 + args: [--max-line-length=80] + additional_dependencies: [flake8-docstrings] # # - repo: https://github.com/PyCQA/pydocstyle # rev: 6.3.0 From baa6ad05c8cdf795b60c01e980a0902f2b7b304d Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 22:14:21 -0500 Subject: [PATCH 089/139] docformatter --- .pre-commit-config.yaml | 37 +++---- artlib/__init__.py | 17 ++-- artlib/biclustering/BARTMAP.py | 43 +++------ artlib/biclustering/__init__.py | 12 +-- artlib/common/BaseART.py | 80 ++++++--------- artlib/common/BaseARTMAP.py | 31 ++---- artlib/common/VAT.py | 3 +- artlib/common/__init__.py | 6 +- artlib/common/utils.py | 21 ++-- artlib/common/visualization.py | 13 ++- artlib/cvi/CVIART.py | 45 +++------ artlib/cvi/__init__.py | 16 +-- artlib/cvi/iCVIFuzzyArt.py | 25 +++-- artlib/cvi/iCVIs/CalinkskiHarabasz.py | 52 +++++----- artlib/elementary/ART1.py | 46 ++++----- artlib/elementary/ART2.py | 26 ++--- artlib/elementary/BayesianART.py | 53 +++++----- artlib/elementary/EllipsoidART.py | 63 +++++------- artlib/elementary/FuzzyART.py | 53 ++++------ artlib/elementary/GaussianART.py | 32 +++--- artlib/elementary/HypersphereART.py | 50 ++++------ artlib/elementary/QuadraticNeuronART.py | 47 ++++----- artlib/elementary/__init__.py | 6 +- artlib/fusion/FusionART.py | 123 ++++++++++++------------ artlib/fusion/__init__.py | 19 ++-- artlib/hierarchical/DeepARTMAP.py | 75 ++++++++------- artlib/hierarchical/SMART.py | 56 +++++------ artlib/hierarchical/__init__.py | 12 +-- artlib/reinforcement/FALCON.py | 83 ++++++++-------- artlib/reinforcement/__init__.py | 19 ++-- artlib/supervised/ARTMAP.py | 56 +++++------ artlib/supervised/SimpleARTMAP.py | 88 ++++++++--------- artlib/topological/DualVigilanceART.py | 79 +++++++-------- artlib/topological/TopoART.py | 92 +++++++++--------- artlib/topological/__init__.py | 19 ++-- 35 files changed, 677 insertions(+), 821 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c86e5e1..b1d4dd4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,30 +9,31 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace -# - repo: local -# hooks: -# - id: docformatter -# name: docformatter -# entry: docformatter -# language: python -# types: [python] -# additional_dependencies: [docformatter==1.7.5] -# args: -# - --black -# - --style=numpy -# - --blank + - repo: local + hooks: + - id: docformatter + name: docformatter + entry: docformatter + language: python + types: [python] + additional_dependencies: [docformatter==1.7.5] + args: + - --black + - --style=numpy + - --blank + - --in-place - repo: https://github.com/psf/black rev: 23.9.1 # Use the latest stable version hooks: - id: black - - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 # Use the latest stable version - hooks: - - id: flake8 - args: [--max-line-length=80] - additional_dependencies: [flake8-docstrings] +# - repo: https://github.com/PyCQA/flake8 +# rev: 6.1.0 # Use the latest stable version +# hooks: +# - id: flake8 +# args: [--max-line-length=88] +# additional_dependencies: [flake8-docstrings] # # - repo: https://github.com/PyCQA/pydocstyle # rev: 6.3.0 diff --git a/artlib/__init__.py b/artlib/__init__.py index 36342e7..3dd8c07 100644 --- a/artlib/__init__.py +++ b/artlib/__init__.py @@ -1,12 +1,13 @@ -""" -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." +"""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. +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 `_ diff --git a/artlib/biclustering/BARTMAP.py b/artlib/biclustering/BARTMAP.py index 90d76b2..7a07208 100644 --- a/artlib/biclustering/BARTMAP.py +++ b/artlib/biclustering/BARTMAP.py @@ -20,8 +20,7 @@ class BARTMAP(BaseEstimator, BiclusterMixin): - """ - BARTMAP for Biclustering + """BARTMAP for Biclustering. This class implements BARTMAP as first published in: Xu, R., & Wunsch II, D. C. (2011). @@ -41,8 +40,7 @@ class BARTMAP(BaseEstimator, BiclusterMixin): columns_: np.ndarray # bool def __init__(self, module_a: BaseART, module_b: BaseART, eta: float): - """ - Initialize the BARTMAP model. + """Initialize the BARTMAP model. Parameters ---------- @@ -78,8 +76,7 @@ def __setattr__(self, key, value): super().__setattr__(key, value) def get_params(self, deep: bool = True) -> dict: - """ - Get parameters for this estimator. + """Get parameters for this estimator. Parameters ---------- @@ -105,8 +102,7 @@ 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. @@ -152,8 +148,7 @@ def set_params(self, **params): @staticmethod def validate_params(params: dict): - """ - Validate clustering parameters. + """Validate clustering parameters. Parameters ---------- @@ -181,8 +176,7 @@ def n_column_clusters(self): return self.module_b.n_clusters 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 ---------- @@ -203,8 +197,7 @@ def _get_x_cb(self, x: np.ndarray, c_b: int): @staticmethod def _pearsonr(a: np.ndarray, b: np.ndarray) -> float: - """ - Get the Pearson correlation between two vectors. + """Get the Pearson correlation between two vectors. Parameters ---------- @@ -223,8 +216,8 @@ def _pearsonr(a: np.ndarray, b: np.ndarray) -> float: return r def _average_pearson_corr(self, X: np.ndarray, k: int, c_b: int) -> float: - """ - Get the average Pearson correlation for a sample across all features in cluster b. + """Get the average Pearson correlation for a sample across all features in + cluster b. Parameters ---------- @@ -253,8 +246,7 @@ def _average_pearson_corr(self, X: np.ndarray, k: int, c_b: int) -> float: return float(mean_r) def validate_data(self, X_a: np.ndarray, X_b: np.ndarray): - """ - Validate the data prior to clustering. + """Validate the data prior to clustering. Parameters ---------- @@ -270,8 +262,7 @@ 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. + """Get the binary match criterion of the cluster. Parameters ---------- @@ -302,8 +293,7 @@ def match_reset_func( extra: dict, cache: Optional[dict] = None, ) -> bool: - """ - Permit external factors to influence cluster creation. + """Permit external factors to influence cluster creation. Parameters ---------- @@ -333,8 +323,7 @@ def match_reset_func( return False 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 ---------- @@ -356,8 +345,7 @@ def step_fit(self, X: np.ndarray, k: int) -> int: return c_a def fit(self, X: np.ndarray, max_iter=1): - """ - Fit the model to the data. + """Fit the model to the data. Parameters ---------- @@ -405,8 +393,7 @@ def fit(self, X: np.ndarray, max_iter=1): return self def visualize(self, cmap: Optional[Colormap] = None): - """ - Visualize the clustering of the data. + """Visualize the clustering of the data. Parameters ---------- diff --git a/artlib/biclustering/__init__.py b/artlib/biclustering/__init__.py index 542f8ec..36face3 100644 --- a/artlib/biclustering/__init__.py +++ b/artlib/biclustering/__init__.py @@ -1,9 +1,9 @@ -""" -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. +"""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. diff --git a/artlib/common/BaseART.py b/artlib/common/BaseART.py index bcc2170..fbe9f0f 100644 --- a/artlib/common/BaseART.py +++ b/artlib/common/BaseART.py @@ -11,9 +11,7 @@ class BaseART(BaseEstimator, ClusterMixin): - """ - Generic implementation of Adaptive Resonance Theory (ART) - """ + """Generic implementation of Adaptive Resonance Theory (ART)""" def __init__(self, params: dict): """ @@ -64,8 +62,7 @@ def get_params(self, deep: bool = True) -> dict: 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. @@ -109,8 +106,7 @@ def set_params(self, **params): return self def prepare_data(self, X: np.ndarray) -> np.ndarray: - """ - Prepare data for clustering. + """Prepare data for clustering. Parameters ---------- @@ -127,8 +123,7 @@ def prepare_data(self, X: np.ndarray) -> np.ndarray: 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 ---------- @@ -145,8 +140,7 @@ def restore_data(self, X: np.ndarray) -> np.ndarray: @property def n_clusters(self) -> int: - """ - Get the current number of clusters. + """Get the current number of clusters. Returns ------- @@ -161,8 +155,7 @@ def n_clusters(self) -> int: @staticmethod def validate_params(params: dict): - """ - Validate clustering parameters. + """Validate clustering parameters. Parameters ---------- @@ -173,8 +166,7 @@ def validate_params(params: dict): raise NotImplementedError def check_dimensions(self, X: np.ndarray): - """ - Check the data has the correct dimensions. + """Check the data has the correct dimensions. Parameters ---------- @@ -188,8 +180,7 @@ def check_dimensions(self, X: np.ndarray): assert X.shape[1] == self.dim_ def validate_data(self, X: np.ndarray): - """ - validates the data prior to clustering + """Validates the data prior to clustering. Parameters: - X: data set @@ -202,8 +193,7 @@ 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 ---------- @@ -229,8 +219,7 @@ def match_criterion( params: dict, cache: Optional[dict] = None, ) -> tuple[float, dict]: - """ - Get the match criterion of the cluster. + """Get the match criterion of the cluster. Parameters ---------- @@ -259,8 +248,7 @@ def match_criterion_bin( 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 ---------- @@ -294,8 +282,7 @@ def update( params: dict, cache: Optional[dict] = None, ) -> np.ndarray: - """ - Get the updated cluster weight. + """Get the updated cluster weight. Parameters ---------- @@ -317,8 +304,7 @@ def update( raise NotImplementedError def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: - """ - Generate a new cluster weight. + """Generate a new cluster weight. Parameters ---------- @@ -336,8 +322,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: raise NotImplementedError def add_weight(self, new_w: np.ndarray): - """ - Add a new cluster weight. + """Add a new cluster weight. Parameters ---------- @@ -349,8 +334,7 @@ def add_weight(self, new_w: np.ndarray): self.W.append(new_w) 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 ---------- @@ -412,8 +396,7 @@ def step_fit( 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 ---------- @@ -487,8 +470,7 @@ def step_fit( return c_new def step_pred(self, x) -> int: - """ - Predict the label for a single sample. + """Predict the label for a single sample. Parameters ---------- @@ -508,8 +490,8 @@ def step_pred(self, x) -> int: return c_ 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 ---------- @@ -521,8 +503,7 @@ def pre_step_fit(self, X: np.ndarray): pass 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 ---------- @@ -534,8 +515,7 @@ def post_step_fit(self, X: np.ndarray): pass 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 ---------- @@ -556,8 +536,7 @@ def fit( epsilon: float = 0.0, verbose: bool = False, ): - """ - Fit the model to the data. + """Fit the model to the data. Parameters ---------- @@ -610,8 +589,7 @@ def partial_fit( 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 ---------- @@ -648,8 +626,7 @@ def partial_fit( return self def predict(self, X: np.ndarray) -> np.ndarray: - """ - Predict labels for the data. + """Predict labels for the data. Parameters ---------- @@ -677,8 +654,7 @@ def shrink_clusters(self, shrink_ratio: float = 0.1): return self 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 ---------- @@ -693,8 +669,7 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): raise NotImplementedError def get_cluster_centers(self) -> List[np.ndarray]: - """ - Undefined function for getting centers of each cluster. Used for regression. + """Undefined function for getting centers of each cluster. Used for regression. Returns ------- @@ -713,8 +688,7 @@ def visualize( linewidth: int = 1, colors: Optional[Iterable] = None, ): - """ - Visualize the clustering of the data. + """Visualize the clustering of the data. Parameters ---------- diff --git a/artlib/common/BaseARTMAP.py b/artlib/common/BaseARTMAP.py index abe718c..63ad2df 100644 --- a/artlib/common/BaseARTMAP.py +++ b/artlib/common/BaseARTMAP.py @@ -6,16 +6,13 @@ class BaseARTMAP(BaseEstimator, ClassifierMixin, ClusterMixin): - """ - Generic implementation of Adaptive Resonance Theory MAP (ARTMAP) - """ + """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. @@ -59,8 +56,7 @@ def set_params(self, **params): return self 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 ---------- @@ -79,8 +75,7 @@ def map_a2b(self, y_a: Union[np.ndarray, int]) -> Union[np.ndarray, int]: return np.array([self.map[x] for x in u], dtype=int)[inv].reshape(y_a.shape) def validate_data(self, X: np.ndarray, y: np.ndarray): - """ - Validate the data prior to clustering. + """Validate the data prior to clustering. Parameters ---------- @@ -100,8 +95,7 @@ def fit( 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 ---------- @@ -126,8 +120,7 @@ def partial_fit( 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 ---------- @@ -144,8 +137,7 @@ def partial_fit( raise NotImplementedError def predict(self, X: np.ndarray) -> np.ndarray: - """ - Predict labels for the data. + """Predict labels for the data. Parameters ---------- @@ -161,8 +153,7 @@ def predict(self, X: np.ndarray) -> np.ndarray: 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 ---------- @@ -178,8 +169,7 @@ def predict_ab(self, X: np.ndarray) -> tuple[np.ndarray, np.ndarray]: raise NotImplementedError def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): - """ - Visualize the bounds of each cluster. + """Visualize the bounds of each cluster. Parameters ---------- @@ -202,8 +192,7 @@ def visualize( linewidth: int = 1, colors: Optional[Iterable] = None, ): - """ - Visualize the clustering of the data. + """Visualize the clustering of the data. Parameters ---------- diff --git a/artlib/common/VAT.py b/artlib/common/VAT.py index 46e89bc..5e38921 100644 --- a/artlib/common/VAT.py +++ b/artlib/common/VAT.py @@ -9,8 +9,7 @@ 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. + """Visual Assessment of Cluster Tendency (VAT) algorithm. Parameters ---------- diff --git a/artlib/common/__init__.py b/artlib/common/__init__.py index 2d3288d..ef31922 100644 --- a/artlib/common/__init__.py +++ b/artlib/common/__init__.py @@ -1,4 +1,2 @@ -""" -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. -""" +"""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.""" diff --git a/artlib/common/utils.py b/artlib/common/utils.py index cb45e38..bd20c33 100644 --- a/artlib/common/utils.py +++ b/artlib/common/utils.py @@ -7,8 +7,7 @@ def normalize( d_max: Optional[np.ndarray] = None, d_min: Optional[np.ndarray] = None, ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: - """ - Normalize data column-wise between 0 and 1. + """Normalize data column-wise between 0 and 1. Parameters ---------- @@ -39,8 +38,7 @@ def normalize( def de_normalize(data: np.ndarray, d_max: np.ndarray, d_min: np.ndarray) -> np.ndarray: - """ - Restore column-wise normalized data to original scale. + """Restore column-wise normalized data to original scale. Parameters ---------- @@ -61,8 +59,7 @@ def de_normalize(data: np.ndarray, d_max: np.ndarray, d_min: np.ndarray) -> np.n def compliment_code(data: np.ndarray) -> np.ndarray: - """ - Compliment code the data. + """Compliment code the data. Parameters ---------- @@ -80,8 +77,7 @@ def compliment_code(data: np.ndarray) -> np.ndarray: def de_compliment_code(data: np.ndarray) -> np.ndarray: - """ - Find the centroid of compliment coded data. + """Find the centroid of compliment coded data. Parameters ---------- @@ -114,8 +110,7 @@ def de_compliment_code(data: np.ndarray) -> np.ndarray: def l1norm(x: np.ndarray) -> float: - """ - Get the L1 norm of a vector. + """Get the L1 norm of a vector. Parameters ---------- @@ -132,8 +127,7 @@ def l1norm(x: np.ndarray) -> float: def l2norm2(data: np.ndarray) -> float: - """ - Get the squared L2 norm of a vector. + """Get the squared L2 norm of a vector. Parameters ---------- @@ -150,8 +144,7 @@ def l2norm2(data: np.ndarray) -> float: def fuzzy_and(x: np.ndarray, y: np.ndarray) -> np.ndarray: - """ - Get the fuzzy AND operation between two vectors. + """Get the fuzzy AND operation between two vectors. Parameters ---------- diff --git a/artlib/common/visualization.py b/artlib/common/visualization.py index a2fd8bd..81d2fd6 100644 --- a/artlib/common/visualization.py +++ b/artlib/common/visualization.py @@ -11,8 +11,8 @@ def plot_gaussian_contours_fading( sigma_steps: float = 0.25, linewidth: int = 1, ): - """ - Plot 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. Parameters ---------- @@ -73,9 +73,9 @@ def plot_gaussian_contours_covariance( sigma_steps: float = 0.25, linewidth: int = 1, ): - """ - 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. + """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 ---------- @@ -145,8 +145,7 @@ def plot_weight_matrix_as_ellipse( color: np.ndarray, linewidth: int = 1, ): - """ - Plot the transformation of a unit circle by the weight matrix W as an ellipse. + """Plot the transformation of a unit circle by the weight matrix W as an ellipse. Parameters ---------- diff --git a/artlib/cvi/CVIART.py b/artlib/cvi/CVIART.py index 066a30a..d3ff9cc 100644 --- a/artlib/cvi/CVIART.py +++ b/artlib/cvi/CVIART.py @@ -7,10 +7,10 @@ class CVIART(BaseART): - """CVI Art Classification + """CVI Art Classification. - Expanded version of Art that uses Cluster Validity Indicies to help with cluster selection. - PBM is not implemented, can be seen here. + Expanded version of Art that uses Cluster Validity Indicies to help with cluster + selection. PBM is not implemented, can be seen here. https://git.mst.edu/acil-group/CVI-Fuzzy-ART/-/blob/master/PBM_index.m?ref_type=heads Note, the default step_fit function in base ART evaluates the matching function even if @@ -24,8 +24,7 @@ class CVIART(BaseART): # PBM = 4 def __init__(self, base_module: BaseART, validity: int): - """ - Initialize the CVIART model. + """Initialize the CVIART model. Parameters ---------- @@ -41,8 +40,7 @@ def __init__(self, base_module: BaseART, validity: int): print(self.params) def validate_params(self, params: dict): - """ - Validate clustering parameters. + """Validate clustering parameters. Parameters ---------- @@ -60,8 +58,7 @@ def validate_params(self, params: dict): ] def prepare_data(self, X: np.ndarray) -> np.ndarray: - """ - Prepare data for clustering. + """Prepare data for clustering. Parameters ---------- @@ -77,8 +74,7 @@ def prepare_data(self, X: np.ndarray) -> np.ndarray: 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 ---------- @@ -110,8 +106,7 @@ def labels_(self, new_labels_): self.base_module.labels_ = new_labels_ def CVI_match(self, x, w, c_, params, extra, cache): - """ - Evaluate the cluster validity index (CVI) for a match. + """Evaluate the cluster validity index (CVI) for a match. Parameters ---------- @@ -162,8 +157,7 @@ def _match_tracking( params: dict, method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"], ) -> bool: - """ - Adjust the vigilance parameter (rho) based on the match tracking method. + """Adjust the vigilance parameter (rho) based on the match tracking method. Parameters ---------- @@ -215,8 +209,7 @@ def fit( match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ): - """ - Fit the model to the data. + """Fit the model to the data. Parameters ---------- @@ -284,8 +277,7 @@ def fit( self.post_step_fit(X) def pre_step_fit(self, X: np.ndarray): - """ - Preprocessing step before fitting each sample. + """Preprocessing step before fitting each sample. Parameters ---------- @@ -296,8 +288,7 @@ def pre_step_fit(self, X: np.ndarray): return self.base_module.pre_step_fit(X) def post_step_fit(self, X: np.ndarray): - """ - Postprocessing step after fitting each sample. + """Postprocessing step after fitting each sample. Parameters ---------- @@ -314,8 +305,7 @@ def step_fit( 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 ---------- @@ -338,8 +328,7 @@ def step_fit( raise NotImplementedError def step_pred(self, x: np.ndarray) -> int: - """ - Predict the label for a single sample. + """Predict the label for a single sample. Parameters ---------- @@ -355,8 +344,7 @@ def step_pred(self, x: np.ndarray) -> int: return self.base_module.step_pred(x) def get_cluster_centers(self) -> List[np.ndarray]: - """ - Get the centers of the clusters. + """Get the centers of the clusters. Returns ------- @@ -367,8 +355,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: 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. + """Plot the boundaries of each cluster. Parameters ---------- diff --git a/artlib/cvi/__init__.py b/artlib/cvi/__init__.py index c6aa5d0..af3ca55 100644 --- a/artlib/cvi/__init__.py +++ b/artlib/cvi/__init__.py @@ -1,12 +1,12 @@ -""" -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. +"""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. +This module implements CVI-driven ART modules which utilize the CVI to inform +clustering; often resulting in objectively superior results. `Cluster validity indices `_ diff --git a/artlib/cvi/iCVIFuzzyArt.py b/artlib/cvi/iCVIFuzzyArt.py index a0c715d..94029dc 100644 --- a/artlib/cvi/iCVIFuzzyArt.py +++ b/artlib/cvi/iCVIFuzzyArt.py @@ -1,10 +1,12 @@ -""" -Add Reference in correct format. -The original matlab code can be found at https://github.com/ACIL-Group/iCVI-toolbox/tree/master -The formulation is available at -https://scholarsmine.mst.edu/cgi/viewcontent.cgi?article=3833&context=doctoral_dissertations Pages 314-316 and 319-320 -Extended icvi offline mode can be found at +"""Add Reference in correct format. + +The original matlab code can be found at +https://github.com/ACIL-Group/iCVI-toolbox/tree/master + The formulation is available at +https://scholarsmine.mst.edu/cgi/viewcontent.cgi?article=3833&context=doctoral_dissertations + Pages 314-316 and 319-320 Extended icvi offline mode can be found at https://ieeexplore.ieee.org/document/9745260 + """ import numpy as np from typing import Optional, Literal, Callable @@ -13,7 +15,7 @@ class iCVIFuzzyART(FuzzyART): - """iCVI Fuzzy Art For Clustering""" + """ICVI Fuzzy Art For Clustering.""" CALINSKIHARABASZ = 1 @@ -25,8 +27,7 @@ def __init__( validity: int, offline: bool = True, ): - """ - Initialize the iCVIFuzzyART model. + """Initialize the iCVIFuzzyART model. Parameters ---------- @@ -53,8 +54,7 @@ def __init__( assert isinstance(self.params["validity"], int) def iCVI_match(self, x, w, c_, params, cache): - """ - Apply iCVI (incremental Cluster Validity Index) matching criteria. + """Apply iCVI (incremental Cluster Validity Index) matching criteria. Parameters ---------- @@ -93,8 +93,7 @@ def fit( match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ): - """ - Fit the model to the data. + """Fit the model to the data. Parameters ---------- diff --git a/artlib/cvi/iCVIs/CalinkskiHarabasz.py b/artlib/cvi/iCVIs/CalinkskiHarabasz.py index 20029c7..4dbd115 100644 --- a/artlib/cvi/iCVIs/CalinkskiHarabasz.py +++ b/artlib/cvi/iCVIs/CalinkskiHarabasz.py @@ -1,13 +1,14 @@ -""" -Some things to consider in the future. +"""Some things to consider in the future. Removing entire labels from the dataset should be possible. -Right now I think its not possible for Fuzzy Art to delete a cluster, so no need to do so now. +Right now I think its not possible for Fuzzy Art to delete a cluster, so no need to do +so now. + +Creating functions to explictly add a sample, remove or switch, instead of requiring the +update call would be nice Or at least change the function names for add, remove, and +switch, since they imply that its done, not that it will update after update is called. -Creating functions to explictly add a sample, remove or switch, instead of requiring the update call would be nice -Or at least change the function names for add, remove, and switch, since they imply that its done, not that it will -update after update is called. """ import numpy as np @@ -15,8 +16,7 @@ def delta_add_sample_to_average( average: float, sample: float, total_samples: int ) -> float: - """ - Calculate the new average if a sample is added. + """Calculate the new average if a sample is added. Parameters ---------- @@ -39,8 +39,7 @@ def delta_add_sample_to_average( def delta_remove_sample_from_average( average: float, sample: float, total_samples: int ) -> float: - """ - Calculate the new average if a sample is removed. + """Calculate the new average if a sample is removed. Parameters ---------- @@ -63,11 +62,14 @@ def delta_remove_sample_from_average( class iCVI_CH: """Implementation of the Calinski Harabasz Validity Index in incremental form. - Expanded implementation of the incremental version of the Calinski Harabasz Cluster Validity Index. + Expanded implementation of the incremental version of the Calinski Harabasz Cluster + Validity Index. - The original matlab code can be found at https://github.com/ACIL-Group/iCVI-toolbox/blob/master/classes/CVI_CH.m - The formulation is available at - https://scholarsmine.mst.edu/cgi/viewcontent.cgi?article=3833&context=doctoral_dissertations Pages 314-316 and 319-320 + The original matlab code can be found at + https://github.com/ACIL-Group/iCVI-toolbox/blob/master/classes/CVI_CH.m + The formulation is available at + https://scholarsmine.mst.edu/cgi/viewcontent.cgi?article=3833&context=doctoral_dissertations + Pages 314-316 and 319-320 This implementation returns a dictionary of updated parameters when calling functions, which can then be passed with the update function to accept the changes. This allows for testing changes/additions to the categories without doing a @@ -78,11 +80,11 @@ class iCVI_CH: samples in the dataset. For the Calinski Harabasz validity Index, larger values represent better clusters. + """ def __init__(self, x: np.ndarray) -> None: - """ - Create the iCVI_CH object. + """Create the iCVI_CH object. Parameters ---------- @@ -98,8 +100,7 @@ def __init__(self, x: np.ndarray) -> None: self.criterion_value = 0 # calcualted CH index def add_sample(self, x: np.ndarray, label: int) -> dict: - """ - Calculate the result of adding a new sample with a given label. + """Calculate the result of adding a new sample with a given label. Parameters ---------- @@ -181,11 +182,10 @@ def add_sample(self, x: np.ndarray, label: int) -> dict: return newP 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 + """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. Parameters ---------- @@ -203,8 +203,7 @@ def update(self, params: dict) -> None: self.WGSS += params["CP_diff2"] def switch_label(self, x: np.ndarray, label_old: int, label_new: int) -> dict: - """ - Calculate the parameters when a sample has its label changed. + """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 @@ -303,8 +302,7 @@ def switch_label(self, x: np.ndarray, label_old: int, label_new: int) -> dict: 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. + """Remove a sample from the clusters. Parameters ---------- diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index 33cd84a..1afdfad 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -1,7 +1,9 @@ -""" -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. +"""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. + """ import numpy as np @@ -11,19 +13,18 @@ class ART1(BaseART): - """ART1 for Clustering + """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. + 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. """ def __init__(self, rho: float, beta: float, L: float): - """ - Initialize the ART1 model. + """Initialize the ART1 model. Parameters ---------- @@ -40,8 +41,7 @@ def __init__(self, rho: float, beta: float, L: float): @staticmethod def validate_params(params: dict): - """ - Validate clustering parameters. + """Validate clustering parameters. Parameters ---------- @@ -60,8 +60,7 @@ def validate_params(params: dict): assert isinstance(params["L"], float) def validate_data(self, X: np.ndarray): - """ - Validate the data prior to clustering. + """Validate the data prior to clustering. Parameters ---------- @@ -75,8 +74,7 @@ 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 ---------- @@ -105,8 +103,7 @@ def match_criterion( params: dict, cache: Optional[dict] = None, ) -> tuple[float, dict]: - """ - Get the match criterion of the cluster. + """Get the match criterion of the cluster. Parameters ---------- @@ -137,8 +134,7 @@ def update( params: dict, cache: Optional[dict] = None, ) -> np.ndarray: - """ - Get the updated cluster weight. + """Get the updated cluster weight. Parameters ---------- @@ -164,8 +160,7 @@ def update( return np.concatenate([w_bu_new, w_td_new]) def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: - """ - Generate a new cluster weight. + """Generate a new cluster weight. Parameters ---------- @@ -185,8 +180,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: return np.concatenate([w_bu_new, w_td_new]) def get_cluster_centers(self) -> List[np.ndarray]: - """ - Get the centers of each cluster, used for regression. + """Get the centers of each cluster, used for regression. Returns ------- diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index 2b0435f..e5a0278 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -24,7 +24,7 @@ class ART2A(BaseART): - """ART2-A for Clustering + """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. @@ -34,8 +34,7 @@ class ART2A(BaseART): """ def __init__(self, rho: float, alpha: float, beta: float): - """ - Initialize the ART2-A model. + """Initialize the ART2-A model. Parameters ---------- @@ -60,8 +59,7 @@ def __init__(self, rho: float, alpha: float, beta: float): @staticmethod def validate_params(params: dict): - """ - Validate clustering parameters. + """Validate clustering parameters. Parameters ---------- @@ -80,8 +78,7 @@ def validate_params(params: dict): assert isinstance(params["beta"], float) def check_dimensions(self, X: np.ndarray): - """ - Check that the data has the correct dimensions. + """Check that the data has the correct dimensions. Parameters ---------- @@ -98,8 +95,7 @@ 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 ---------- @@ -129,8 +125,7 @@ def match_criterion( params: dict, cache: Optional[dict] = None, ) -> tuple[float, dict]: - """ - Get the match criterion of the cluster. + """Get the match criterion of the cluster. Parameters ---------- @@ -169,8 +164,7 @@ def update( params: dict, cache: Optional[dict] = None, ) -> np.ndarray: - """ - Get the updated cluster weight. + """Get the updated cluster weight. Parameters ---------- @@ -192,8 +186,7 @@ def update( 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 ---------- @@ -211,8 +204,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: return i def get_cluster_centers(self) -> List[np.ndarray]: - """ - Get the centers of each cluster, used for regression. + """Get the centers of each cluster, used for regression. Returns ------- diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index 7121844..624dad1 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -1,7 +1,8 @@ -""" -Vigdor, B., & Lerner, B. (2007). -The Bayesian ARTMAP. -IEEE Transactions on Neural Networks, 18, 1628–1644. doi:10.1109/TNN.2007.900234. +"""Vigdor, B., & Lerner, B. + +(2007). The Bayesian ARTMAP. IEEE Transactions on Neural Networks, 18, 1628–1644. +doi:10.1109/TNN.2007.900234. + """ import numpy as np from typing import Optional, Iterable, List, Callable, Literal, Tuple @@ -12,20 +13,20 @@ class BayesianART(BaseART): - """Bayesian ART for Clustering + """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. + 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. """ pi2 = np.pi * 2 def __init__(self, rho: float, cov_init: np.ndarray): - """ - Initialize the Bayesian ART model. + """Initialize the Bayesian ART model. Parameters ---------- @@ -43,8 +44,7 @@ def __init__(self, rho: float, cov_init: np.ndarray): @staticmethod def validate_params(params: dict): - """ - Validate clustering parameters. + """Validate clustering parameters. Parameters ---------- @@ -59,8 +59,7 @@ def validate_params(params: dict): assert isinstance(params["cov_init"], np.ndarray) def check_dimensions(self, X: np.ndarray): - """ - Check that the data has the correct dimensions. + """Check that the data has the correct dimensions. Parameters ---------- @@ -78,8 +77,7 @@ 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 ---------- @@ -124,8 +122,7 @@ def match_criterion( params: dict, cache: Optional[dict] = None, ) -> tuple[float, dict]: - """ - Get the match criterion of the cluster. + """Get the match criterion of the cluster. Parameters ---------- @@ -164,8 +161,7 @@ def match_criterion_bin( 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 ---------- @@ -206,8 +202,7 @@ def _match_tracking( params: dict, method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"], ) -> bool: - """ - Adjust match tracking based on the method and epsilon value. + """Adjust match tracking based on the method and epsilon value. Parameters ---------- @@ -252,8 +247,7 @@ def update( params: dict, cache: Optional[dict] = None, ) -> np.ndarray: - """ - Get the updated cluster weight. + """Get the updated cluster weight. Parameters ---------- @@ -293,8 +287,7 @@ def update( return np.concatenate([mean_new, cov_new.flatten(), [n_new]]) def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: - """ - Generate a new cluster weight. + """Generate a new cluster weight. Parameters ---------- @@ -312,8 +305,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: return np.concatenate([i, params["cov_init"].flatten(), [1]]) def get_cluster_centers(self) -> List[np.ndarray]: - """ - Get the centers of each cluster, used for regression. + """Get the centers of each cluster, used for regression. Returns ------- @@ -324,8 +316,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: return [w[: self.dim_] for w in self.W] def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): - """ - Visualize the bounds of each cluster. + """Visualize the bounds of each cluster. Parameters ---------- diff --git a/artlib/elementary/EllipsoidART.py b/artlib/elementary/EllipsoidART.py index b45b392..4d9f257 100644 --- a/artlib/elementary/EllipsoidART.py +++ b/artlib/elementary/EllipsoidART.py @@ -1,13 +1,12 @@ -""" -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. - -Anagnostopoulos, G. C., & Georgiopoulos, M. (2001b). -Ellipsoid ART and ARTMAP for incremental unsupervised and supervised learning. -In Aerospace/Defense Sensing, Simulation, and Controls (pp. 293– 304). -International Society for Optics and Photonics. doi:10.1117/12.421180. +"""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. + +Anagnostopoulos, G. C., & Georgiopoulos, M. (2001b). Ellipsoid ART and ARTMAP for +incremental unsupervised and supervised learning. In Aerospace/Defense Sensing, +Simulation, and Controls (pp. 293– 304). International Society for Optics and Photonics. +doi:10.1117/12.421180. + """ import numpy as np from typing import Optional, Iterable, List @@ -17,20 +16,19 @@ class EllipsoidART(BaseART): - """Ellipsoid ART for Clustering + """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. + 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. """ def __init__(self, rho: float, alpha: float, beta: float, mu: float, r_hat: float): - """ - Initialize the Ellipsoid ART model. + """Initialize the Ellipsoid ART model. Parameters ---------- @@ -57,8 +55,7 @@ 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 ---------- @@ -88,8 +85,7 @@ def category_distance( major_axis: np.ndarray, params: dict, ) -> float: - """ - Calculate the distance between a sample and the cluster centroid. + """Calculate the distance between a sample and the cluster centroid. Parameters ---------- @@ -122,8 +118,7 @@ def category_distance( 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 ---------- @@ -160,8 +155,7 @@ def match_criterion( params: dict, cache: Optional[dict] = None, ) -> tuple[float, dict]: - """ - Get the match criterion of the cluster. + """Get the match criterion of the cluster. Parameters ---------- @@ -196,8 +190,7 @@ def update( params: dict, cache: Optional[dict] = None, ) -> np.ndarray: - """ - Get the updated cluster weight. + """Get the updated cluster weight. Parameters ---------- @@ -237,8 +230,7 @@ def update( return np.concatenate([centroid_new, major_axis_new, [radius_new]]) def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: - """ - Generate a new cluster weight. + """Generate a new cluster weight. Parameters ---------- @@ -256,8 +248,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: return np.concatenate([i, np.zeros_like(i), [0.0]]) def get_2d_ellipsoids(self) -> list[tuple]: - """ - Get the 2D ellipsoids for visualization. + """Get the 2D ellipsoids for visualization. Returns ------- @@ -280,8 +271,7 @@ def get_2d_ellipsoids(self) -> list[tuple]: return ellipsoids def get_cluster_centers(self) -> List[np.ndarray]: - """ - Get the centers of each cluster, used for regression. + """Get the centers of each cluster, used for regression. Returns ------- @@ -292,8 +282,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: return [w[: self.dim_] for w in self.W] def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): - """ - Visualize the bounds of each cluster. + """Visualize the bounds of each cluster. Parameters ---------- diff --git a/artlib/elementary/FuzzyART.py b/artlib/elementary/FuzzyART.py index 140b3d8..454d36f 100644 --- a/artlib/elementary/FuzzyART.py +++ b/artlib/elementary/FuzzyART.py @@ -1,7 +1,9 @@ -""" -Carpenter, G. A., Grossberg, S., & Rosen, D. B. (1991c). +"""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. + """ import numpy as np from typing import Optional, Iterable, List @@ -19,8 +21,7 @@ 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 ---------- @@ -54,7 +55,7 @@ def get_bounding_box( class FuzzyART(BaseART): - """Fuzzy ART for Clustering + """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. @@ -63,8 +64,7 @@ class FuzzyART(BaseART): """ def __init__(self, rho: float, alpha: float, beta: float): - """ - Initialize the Fuzzy ART model. + """Initialize the Fuzzy ART model. Parameters ---------- @@ -84,8 +84,7 @@ def __init__(self, rho: float, alpha: float, beta: float): super().__init__(params) def prepare_data(self, X: np.ndarray) -> np.ndarray: - """ - Prepare data for clustering. + """Prepare data for clustering. Parameters ---------- @@ -103,8 +102,7 @@ def prepare_data(self, X: np.ndarray) -> np.ndarray: return cc_data def restore_data(self, X: np.ndarray) -> np.ndarray: - """ - Restore data to its state prior to preparation. + """Restore data to its state prior to preparation. Parameters ---------- @@ -122,8 +120,7 @@ def restore_data(self, X: np.ndarray) -> np.ndarray: @staticmethod def validate_params(params: dict): - """ - Validate clustering parameters. + """Validate clustering parameters. Parameters ---------- @@ -142,8 +139,7 @@ def validate_params(params: dict): assert isinstance(params["beta"], float) def check_dimensions(self, X: np.ndarray): - """ - Check that the data has the correct dimensions. + """Check that the data has the correct dimensions. Parameters ---------- @@ -158,8 +154,7 @@ def check_dimensions(self, X: np.ndarray): assert X.shape[1] == self.dim_ def validate_data(self, X: np.ndarray): - """ - Validate the data prior to clustering. + """Validate the data prior to clustering. Parameters ---------- @@ -178,8 +173,7 @@ 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 ---------- @@ -207,8 +201,7 @@ def match_criterion( params: dict, cache: Optional[dict] = None, ) -> tuple[float, dict]: - """ - Get the match criterion of the cluster. + """Get the match criterion of the cluster. Parameters ---------- @@ -238,8 +231,7 @@ def update( params: dict, cache: Optional[dict] = None, ) -> np.ndarray: - """ - Get the updated cluster weight. + """Get the updated cluster weight. Parameters ---------- @@ -264,8 +256,7 @@ def update( return b * fuzzy_and(i, w) + (1 - b) * w def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: - """ - Generate a new cluster weight. + """Generate a new cluster weight. Parameters ---------- @@ -285,8 +276,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: def get_bounding_boxes( self, n: Optional[int] = None ) -> List[tuple[list[int], list[int]]]: - """ - Get the bounding boxes for each cluster. + """Get the bounding boxes for each cluster. Parameters ---------- @@ -302,8 +292,7 @@ def get_bounding_boxes( return list(map(lambda w: get_bounding_box(w, n=n), self.W)) def get_cluster_centers(self) -> List[np.ndarray]: - """ - Get the centers of each cluster, used for regression. + """Get the centers of each cluster, used for regression. Returns ------- @@ -314,8 +303,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: 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. + """Shrink the clusters by adjusting the bounding box. Parameters ---------- @@ -340,8 +328,7 @@ def shrink_clusters(self, shrink_ratio: float = 0.1): return self def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): - """ - Visualize the bounds of each cluster. + """Visualize the bounds of each cluster. Parameters ---------- diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index ce2dc8a..a2a6450 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -1,7 +1,9 @@ -""" -Williamson, J. R. (1996). +"""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. + """ import numpy as np @@ -13,7 +15,7 @@ class GaussianART(BaseART): - """Gaussian ART for Clustering + """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. @@ -25,8 +27,7 @@ class GaussianART(BaseART): """ def __init__(self, rho: float, sigma_init: np.ndarray, alpha: float = 1e-10): - """ - Initialize the Gaussian ART model. + """Initialize the Gaussian ART model. Parameters ---------- @@ -43,8 +44,7 @@ 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 ---------- @@ -63,8 +63,7 @@ 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 ---------- @@ -108,8 +107,7 @@ def match_criterion( params: dict, cache: Optional[dict] = None, ) -> tuple[float, dict]: - """ - Get the match criterion of the cluster. + """Get the match criterion of the cluster. Parameters ---------- @@ -142,8 +140,7 @@ def update( params: dict, cache: Optional[dict] = None, ) -> np.ndarray: - """ - Get the updated cluster weight. + """Get the updated cluster weight. Parameters ---------- @@ -180,8 +177,7 @@ def update( return np.concatenate([mean_new, sigma_new, inv_sig, [det_sig], [n_new]]) def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: - """ - Generate a new cluster weight. + """Generate a new cluster weight. Parameters ---------- @@ -204,8 +200,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: ) def get_cluster_centers(self) -> List[np.ndarray]: - """ - Get the centers of each cluster, used for regression. + """Get the centers of each cluster, used for regression. Returns ------- @@ -216,8 +211,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: return [w[: self.dim_] for w in self.W] def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): - """ - Visualize the bounds of each cluster. + """Visualize the bounds of each cluster. Parameters ---------- diff --git a/artlib/elementary/HypersphereART.py b/artlib/elementary/HypersphereART.py index ff37c9b..a1eb354 100644 --- a/artlib/elementary/HypersphereART.py +++ b/artlib/elementary/HypersphereART.py @@ -1,8 +1,9 @@ -""" -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. +"""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. + """ import numpy as np from typing import Optional, Iterable, List @@ -12,19 +13,18 @@ class HypersphereART(BaseART): - """Hypersphere ART for Clustering + """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. + 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. """ def __init__(self, rho: float, alpha: float, beta: float, r_hat: float): - """ - Initialize the Hypersphere ART model. + """Initialize the Hypersphere ART model. Parameters ---------- @@ -48,8 +48,7 @@ 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 ---------- @@ -73,8 +72,7 @@ def validate_params(params: dict): def category_distance( i: np.ndarray, centroid: np.ndarray, radius: float, params ) -> float: - """ - Compute the category distance between a data sample and a centroid. + """Compute the category distance between a data sample and a centroid. Parameters ---------- @@ -98,8 +96,7 @@ def category_distance( 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 ---------- @@ -139,8 +136,7 @@ def match_criterion( params: dict, cache: Optional[dict] = None, ) -> tuple[float, dict]: - """ - Get the match criterion of the cluster. + """Get the match criterion of the cluster. Parameters ---------- @@ -175,8 +171,7 @@ def update( params: dict, cache: Optional[dict] = None, ) -> np.ndarray: - """ - Get the updated cluster weight. + """Get the updated cluster weight. Parameters ---------- @@ -210,8 +205,7 @@ def update( return np.concatenate([centroid_new, [radius_new]]) def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: - """ - Generate a new cluster weight. + """Generate a new cluster weight. Parameters ---------- @@ -229,8 +223,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: return np.concatenate([i, [0.0]]) def get_cluster_centers(self) -> List[np.ndarray]: - """ - Get the centers of each cluster, used for regression. + """Get the centers of each cluster, used for regression. Returns ------- @@ -241,8 +234,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: return [w[:-1] for w in self.W] def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): - """ - Visualize the bounds of each cluster. + """Visualize the bounds of each cluster. Parameters ---------- diff --git a/artlib/elementary/QuadraticNeuronART.py b/artlib/elementary/QuadraticNeuronART.py index 8c4d596..98124c5 100644 --- a/artlib/elementary/QuadraticNeuronART.py +++ b/artlib/elementary/QuadraticNeuronART.py @@ -1,11 +1,10 @@ -""" -Su, M.-C., & Liu, T.-K. (2001). -Application of neural networks using quadratic junctions in cluster analysis. -Neurocomputing, 37, 165 – 175. doi:10.1016/S0925-2312(00)00343-X. +"""Su, M.-C., & Liu, T.-K. (2001). Application of neural networks using quadratic +junctions in cluster analysis. Neurocomputing, 37, 165 – 175. +doi:10.1016/S0925-2312(00)00343-X. -Su, M.-C., & Liu, Y.-C. (2005). -A new approach to clustering data with arbitrary shapes. +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. + """ import numpy as np @@ -17,21 +16,20 @@ class QuadraticNeuronART(BaseART): - """Quadratic Neuron ART for Clustering + """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. + 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. """ def __init__( self, rho: float, s_init: float, lr_b: float, lr_w: float, lr_s: float ): - """ - Initialize the Quadratic Neuron ART model. + """Initialize the Quadratic Neuron ART model. Parameters ---------- @@ -58,8 +56,7 @@ def __init__( @staticmethod def validate_params(params: dict): - """ - Validate clustering parameters. + """Validate clustering parameters. Parameters ---------- @@ -85,8 +82,7 @@ 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 ---------- @@ -130,8 +126,7 @@ def match_criterion( params: dict, cache: Optional[dict] = None, ) -> tuple[float, dict]: - """ - Get the match criterion of the cluster. + """Get the match criterion of the cluster. Parameters ---------- @@ -163,8 +158,7 @@ def update( params: dict, cache: Optional[dict] = None, ) -> np.ndarray: - """ - Get the updated cluster weight. + """Get the updated cluster weight. Parameters ---------- @@ -201,8 +195,7 @@ def update( return np.concatenate([w_new.flatten(), b_new, [s_new]]) def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: - """ - Generate a new cluster weight. + """Generate a new cluster weight. Parameters ---------- @@ -221,8 +214,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: return np.concatenate([w_new.flatten(), i, [params["s_init"]]]) def get_cluster_centers(self) -> List[np.ndarray]: - """ - Get the centers of each cluster, used for regression. + """Get the centers of each cluster, used for regression. Returns ------- @@ -234,8 +226,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: return [w[dim2:-1] for w in self.W] def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): - """ - Visualize the bounds of each cluster. + """Visualize the bounds of each cluster. Parameters ---------- diff --git a/artlib/elementary/__init__.py b/artlib/elementary/__init__.py index df4099f..0a7bbdd 100644 --- a/artlib/elementary/__init__.py +++ b/artlib/elementary/__init__.py @@ -1,4 +1,2 @@ -""" -This module contains elementary ART modules which are those ART modules that do not implement an abstraction layer on -top of another module. -""" +"""This module contains elementary ART modules which are those ART modules that do not +implement an abstraction layer on top of another module.""" diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index 7731118..f8b03b1 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -1,10 +1,12 @@ -""" -Tan, A.-H., Carpenter, G. A., & Grossberg, S. (2007). +"""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. + """ import numpy as np from typing import Optional, Union, Callable, List, Literal @@ -17,8 +19,7 @@ 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. + """Generate the start and end positions for each channel in the input data. Parameters ---------- @@ -29,6 +30,7 @@ def get_channel_position_tuples( ------- list of tuple of int A list of tuples where each tuple represents the start and end index for a channel. + """ positions = [] start = 0 @@ -40,7 +42,7 @@ def get_channel_position_tuples( class FusionART(BaseART): - """Fusion ART for Data Fusion and Regression + """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). @@ -63,8 +65,7 @@ def __init__( gamma_values: Union[List[float], np.ndarray], channel_dims: Union[List[int], np.ndarray], ): - """ - Initialize the FusionART instance. + """Initialize the FusionART instance. Parameters ---------- @@ -74,6 +75,7 @@ def __init__( 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} @@ -85,8 +87,7 @@ def __init__( self.dim_ = sum(channel_dims) def get_params(self, deep: bool = True) -> dict: - """ - Get the parameters of the FusionART model. + """Get the parameters of the FusionART model. Parameters ---------- @@ -97,6 +98,7 @@ def get_params(self, deep: bool = True) -> dict: ------- dict Parameter names mapped to their values. + """ out = self.params for i, module in enumerate(self.modules): @@ -107,25 +109,25 @@ def get_params(self, deep: bool = True) -> dict: @property def n_clusters(self) -> int: - """ - Return the number of clusters in the first ART module. + """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. + """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([self.modules[k].W[i] for k in range(self.n)]) @@ -135,13 +137,13 @@ def W(self): @W.setter def W(self, new_W): - """ - Set the weights for each module by splitting the input weights. + """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: @@ -153,13 +155,13 @@ def W(self, new_W): @staticmethod def validate_params(params: dict): - """ - Validate clustering parameters. + """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"]]) @@ -167,13 +169,13 @@ def validate_params(params: dict): assert isinstance(params["gamma_values"], np.ndarray) def validate_data(self, X: np.ndarray): - """ - Validate the input data for clustering. + """Validate the input data for clustering. Parameters ---------- X : np.ndarray The input dataset. + """ self.check_dimensions(X) for k in range(self.n): @@ -181,19 +183,19 @@ def validate_data(self, X: np.ndarray): self.modules[k].validate_data(X_k) def check_dimensions(self, X: np.ndarray): - """ - Ensure that the input data has the correct dimensions. + """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 the input data by processing each channel's data through its respective ART module. + """Prepare the input data by processing each channel's data through its + respective ART module. Parameters ---------- @@ -204,6 +206,7 @@ def prepare_data(self, channel_data: List[np.ndarray]) -> np.ndarray: ------- np.ndarray Processed and concatenated data. + """ prepared_channel_data = [ self.modules[i].prepare_data(channel_data[i]) for i in range(self.n) @@ -211,8 +214,7 @@ def prepare_data(self, channel_data: List[np.ndarray]) -> np.ndarray: return self.join_channel_data(prepared_channel_data) def restore_data(self, X: np.ndarray) -> List[np.ndarray]: - """ - Restore data to its original state before preparation. + """Restore data to its original state before preparation. Parameters ---------- @@ -223,6 +225,7 @@ def restore_data(self, X: np.ndarray) -> List[np.ndarray]: ------- np.ndarray Restored data for each channel. + """ channel_data = self.split_channel_data(X) restored_channel_data = [ @@ -237,8 +240,7 @@ def category_choice( params: dict, skip_channels: List[int] = [], ) -> tuple[float, Optional[dict]]: - """ - Get the activation of the cluster. + """Get the activation of the cluster. Parameters ---------- @@ -255,6 +257,7 @@ def category_choice( ------- tuple Cluster activation and cache for further processing. + """ activations, caches = zip( *[ @@ -282,8 +285,7 @@ def match_criterion( cache: Optional[dict] = None, skip_channels: List[int] = [], ) -> tuple[list[float], dict]: - """ - Get the match criterion for the cluster. + """Get the match criterion for the cluster. Parameters ---------- @@ -302,6 +304,7 @@ def match_criterion( ------- tuple List of match criteria for each channel and the updated cache. + """ if cache is None: raise ValueError("No cache provided") @@ -330,8 +333,7 @@ def match_criterion_bin( skip_channels: List[int] = [], op: Callable = operator.ge, ) -> tuple[bool, dict]: - """ - Get the binary match criterion for the cluster. + """Get the binary match criterion for the cluster. Parameters ---------- @@ -352,6 +354,7 @@ def match_criterion_bin( ------- tuple Binary match criterion and cache for further processing. + """ if cache is None: raise ValueError("No cache provided") @@ -379,8 +382,7 @@ def _match_tracking( params: List[dict], method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"], ) -> bool: - """ - Perform match tracking for all channels using the specified method. + """Perform match tracking for all channels using the specified method. Parameters ---------- @@ -397,6 +399,7 @@ def _match_tracking( ------- bool Whether to continue searching for a match across all channels. + """ keep_searching = [] for i in range(len(cache)): @@ -410,25 +413,25 @@ def _match_tracking( return all(keep_searching) def _set_params(self, new_params: List[dict]): - """ - Set the parameters for each module in FusionART. + """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) -> dict: - """ - Create a deep copy of the parameters for each module. + """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)} @@ -439,8 +442,7 @@ def partial_fit( 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 ---------- @@ -452,6 +454,7 @@ def partial_fit( 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) @@ -476,8 +479,7 @@ def partial_fit( return self 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 ---------- @@ -490,6 +492,7 @@ def step_pred(self, x, skip_channels: List[int] = []) -> int: ------- int Predicted cluster label for the input sample. + """ assert len(self.W) >= 0, "ART module is not fit." @@ -505,8 +508,7 @@ def step_pred(self, x, skip_channels: List[int] = []) -> int: return c_ def predict(self, X: np.ndarray, skip_channels: List[int] = []) -> np.ndarray: - """ - Predict labels for the input data. + """Predict labels for the input data. Parameters ---------- @@ -519,6 +521,7 @@ def predict(self, X: np.ndarray, skip_channels: List[int] = []) -> np.ndarray: ------- np.ndarray Predicted labels for the input data. + """ check_is_fitted(self) @@ -538,8 +541,7 @@ def update( params: dict, cache: Optional[dict] = None, ) -> np.ndarray: - """ - Update the cluster weight. + """Update the cluster weight. Parameters ---------- @@ -556,6 +558,7 @@ def update( ------- np.ndarray Updated cluster weight. + """ W = [ self.modules[k].update( @@ -569,8 +572,7 @@ def update( return np.concatenate(W) def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: - """ - Generate a new cluster weight. + """Generate a new cluster weight. Parameters ---------- @@ -583,6 +585,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: ------- np.ndarray New cluster weight. + """ W = [ self.modules[k].new_weight( @@ -594,8 +597,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: return np.concatenate(W) 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 @@ -606,8 +608,7 @@ def add_weight(self, new_w: np.ndarray): self.modules[k].add_weight(new_w_k) 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 @@ -619,13 +620,13 @@ def set_weight(self, idx: int, new_w: np.ndarray): self.modules[k].set_weight(idx, new_w_k) def get_cluster_centers(self) -> List[np.ndarray]: - """ - Get the center points for each cluster. + """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 = [ @@ -635,8 +636,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: return centers def get_channel_centers(self, channel: int) -> List[np.ndarray]: - """ - Get the center points of clusters for a specific channel. + """Get the center points of clusters for a specific channel. Parameters ---------- @@ -647,14 +647,14 @@ def get_channel_centers(self, channel: int) -> List[np.ndarray]: ------- list of 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. + """Predict regression values for the input data using the target channels. Parameters ---------- @@ -669,6 +669,7 @@ def predict_regression( 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) @@ -681,8 +682,7 @@ def predict_regression( 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. + """Concatenate data from different channels into a single array. Parameters ---------- @@ -695,6 +695,7 @@ def join_channel_data( ------- 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] @@ -722,8 +723,7 @@ def join_channel_data( def split_channel_data( self, joined_data: np.ndarray, skip_channels: List[int] = [] ) -> List[np.ndarray]: - """ - Split the concatenated data into its original channels. + """Split the concatenated data into its original channels. Parameters ---------- @@ -736,6 +736,7 @@ def split_channel_data( ------- 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] diff --git a/artlib/fusion/__init__.py b/artlib/fusion/__init__.py index 6323a30..41374a6 100644 --- a/artlib/fusion/__init__.py +++ b/artlib/fusion/__init__.py @@ -1,14 +1,17 @@ -""" -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. +"""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. +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 `_ + """ diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index cf5dedb..d99c8cf 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -1,7 +1,9 @@ -""" -Carpenter, G. A., Grossberg, S., & Reynolds, J. H. (1991a). +"""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. + """ import numpy as np from sklearn.base import BaseEstimator, ClassifierMixin, ClusterMixin @@ -15,19 +17,19 @@ class DeepARTMAP(BaseEstimator, ClassifierMixin, ClusterMixin): - """DeepARTMAP for Hierachical Supervised and Unsupervised Learning + """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. + 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. """ def __init__(self, modules: list[BaseART]): - """ - Initialize the DeepARTMAP model. + """Initialize the DeepARTMAP model. Parameters ---------- @@ -38,6 +40,7 @@ def __init__(self, modules: list[BaseART]): ------ AssertionError If no ART modules are provided. + """ assert len(modules) >= 1, "Must provide at least one ART module" self.modules = modules @@ -45,8 +48,7 @@ def __init__(self, modules: list[BaseART]): self.is_supervised: Optional[bool] = None def get_params(self, deep: bool = True) -> dict: - """ - Get parameters for this estimator. + """Get parameters for this estimator. Parameters ---------- @@ -57,6 +59,7 @@ def get_params(self, deep: bool = True) -> dict: ------- dict Parameter names mapped to their values. + """ out = dict() for i, module in enumerate(self.modules): @@ -67,8 +70,7 @@ 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. Parameters ---------- @@ -79,6 +81,7 @@ def set_params(self, **params): ------- self : DeepARTMAP The estimator instance. + """ if not params: @@ -110,25 +113,25 @@ def set_params(self, **params): @property def labels_(self) -> np.ndarray: - """ - Get the labels from the first layer. + """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) -> np.ndarray: - """ - Get the deep labels from all layers. + """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)) for layer in self.layers] @@ -138,33 +141,32 @@ def labels_deep_(self) -> np.ndarray: @property def n_modules(self) -> int: - """ - Get the number of ART modules. + """Get the number of ART modules. Returns ------- int The number of ART modules. + """ return len(self.modules) @property def n_layers(self) -> int: - """ - Get the number of layers. + """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. + """Map a label from one arbitrary level to the highest (B) level. Parameters ---------- @@ -177,6 +179,7 @@ def map_deep( ------- np.ndarray or int The cluster label(s) at the highest level (B). + """ if level < 0: level += len(self.layers) @@ -187,8 +190,7 @@ def map_deep( return y_b def validate_data(self, X: list[np.ndarray], y: Optional[np.ndarray] = None): - """ - Validate the data before clustering. + """Validate the data before clustering. Parameters ---------- @@ -201,6 +203,7 @@ def validate_data(self, X: list[np.ndarray], y: Optional[np.ndarray] = None): ------ AssertionError If the input data is inconsistent or does not match the expected format. + """ assert ( len(X) == self.n_modules @@ -216,8 +219,7 @@ def validate_data(self, X: list[np.ndarray], y: Optional[np.ndarray] = None): def prepare_data( self, X: list[np.ndarray], y: Optional[np.ndarray] = None ) -> Tuple[list[np.ndarray], Optional[np.ndarray]]: - """ - Prepare the data for clustering. + """Prepare the data for clustering. Parameters ---------- @@ -230,14 +232,14 @@ def prepare_data( ------- 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 the data to its original state before preparation. + """Restore the data to its original state before preparation. Parameters ---------- @@ -250,6 +252,7 @@ def restore_data( ------- 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 @@ -261,8 +264,7 @@ def fit( match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ): - """ - Fit the DeepARTMAP model to the data. + """Fit the DeepARTMAP model to the data. Parameters ---------- @@ -281,6 +283,7 @@ def fit( ------- DeepARTMAP The fitted DeepARTMAP model. + """ self.validate_data(X, y) if y is not None: @@ -331,8 +334,7 @@ def partial_fit( match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ): - """ - Partially fit the DeepARTMAP model to the data. + """Partially fit the DeepARTMAP model to the data. Parameters ---------- @@ -349,6 +351,7 @@ def partial_fit( ------- DeepARTMAP The partially fitted DeepARTMAP model. + """ self.validate_data(X, y) if y is not None: @@ -401,8 +404,7 @@ def partial_fit( return self def predict(self, X: Union[np.ndarray, list[np.ndarray]]) -> list[np.ndarray]: - """ - Predict the labels for the input data. + """Predict the labels for the input data. Parameters ---------- @@ -413,6 +415,7 @@ def predict(self, X: Union[np.ndarray, list[np.ndarray]]) -> list[np.ndarray]: ------- 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 70b96da..0e6fed2 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -1,8 +1,9 @@ -""" -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. +"""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. + """ import numpy as np @@ -13,16 +14,15 @@ class SMART(DeepARTMAP): - """SMART for Hierachical Clustering + """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. + 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. """ @@ -33,8 +33,7 @@ def __init__( base_params: dict, **kwargs ): - """ - Initialize the SMART model. + """Initialize the SMART model. Parameters ---------- @@ -46,6 +45,7 @@ def __init__( 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( @@ -66,8 +66,7 @@ def __init__( super().__init__(modules) def prepare_data(self, X: np.ndarray) -> np.ndarray: - """ - Prepare data for clustering. + """Prepare data for clustering. Parameters ---------- @@ -78,13 +77,13 @@ def prepare_data(self, X: np.ndarray) -> np.ndarray: ------- 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 its original form before preparation. + """Restore data to its original form before preparation. Parameters ---------- @@ -95,6 +94,7 @@ def restore_data(self, X: np.ndarray) -> np.ndarray: ------- np.ndarray Restored data. + """ X_, _ = super(SMART, self).restore_data([X] * self.n_modules) return X_[0] @@ -107,8 +107,7 @@ def fit( match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ): - """ - Fit the SMART model to the data. + """Fit the SMART model to the data. Parameters ---------- @@ -127,6 +126,7 @@ def fit( ------- SMART Fitted SMART model. + """ X_list = [X] * self.n_modules return super().fit( @@ -143,8 +143,7 @@ def partial_fit( match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ): - """ - Partial fit the SMART model to the data. + """Partial fit the SMART model to the data. Parameters ---------- @@ -161,6 +160,7 @@ def partial_fit( ------- SMART Partially fitted SMART model. + """ X_list = [X] * self.n_modules return super(SMART, self).partial_fit( @@ -168,8 +168,7 @@ def partial_fit( ) def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): - """ - Visualize the cluster boundaries. + """Visualize the cluster boundaries. Parameters ---------- @@ -183,6 +182,7 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): Returns ------- None + """ for j in range(len(self.modules)): layer_colors = [] @@ -202,8 +202,7 @@ def visualize( linewidth: int = 1, colors: Optional[Iterable] = None, ): - """ - Visualize the clustering of the data with cluster boundaries. + """Visualize the clustering of the data with cluster boundaries. Parameters ---------- @@ -223,6 +222,7 @@ def visualize( Returns ------- None + """ import matplotlib.pyplot as plt diff --git a/artlib/hierarchical/__init__.py b/artlib/hierarchical/__init__.py index b3006a6..32264ef 100644 --- a/artlib/hierarchical/__init__.py +++ b/artlib/hierarchical/__init__.py @@ -1,9 +1,9 @@ -""" -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. +"""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 diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index 531f647..3bccf35 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -1,11 +1,11 @@ -""" -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. (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 -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 @@ -16,7 +16,7 @@ class FALCON: - """FALCON for Reinforcement Learning + """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 @@ -35,8 +35,7 @@ def __init__( gamma_values: Union[List[float], np.ndarray] = np.array([0.33, 0.33, 0.34]), channel_dims: Union[List[int], np.ndarray] = list[int], ): - """ - Initialize the FALCON model. + """Initialize the FALCON model. Parameters ---------- @@ -50,6 +49,7 @@ def __init__( 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], @@ -60,8 +60,7 @@ 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. + """Prepare data for clustering. Parameters ---------- @@ -76,6 +75,7 @@ def prepare_data( ------- tuple of np.ndarray Normalized state, action, and reward data. + """ return ( self.fusion_art.modules[0].prepare_data(states), @@ -86,8 +86,7 @@ def prepare_data( def restore_data( self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: - """ - Restore data to its original form before preparation. + """Restore data to its original form before preparation. Parameters ---------- @@ -102,6 +101,7 @@ def restore_data( ------- tuple of np.ndarray Restored state, action, and reward data. + """ return ( self.fusion_art.modules[0].restore_data(states), @@ -110,8 +110,7 @@ def restore_data( ) def fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray): - """ - Fit the FALCON model to the data. + """Fit the FALCON model to the data. Parameters ---------- @@ -126,14 +125,14 @@ def fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray): ------- 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. + """Partially fit the FALCON model to the data. Parameters ---------- @@ -148,6 +147,7 @@ def partial_fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarr ------- 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) @@ -156,8 +156,7 @@ def partial_fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarr 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. + """Get possible actions and their associated rewards for a given state. Parameters ---------- @@ -170,6 +169,7 @@ def get_actions_and_rewards( ------- 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: @@ -194,8 +194,7 @@ def get_action( action_space: Optional[np.ndarray] = None, optimality: Literal["min", "max"] = "max", ) -> np.ndarray: - """ - Get the best action for a given state based on optimality. + """Get the best action for a given state based on optimality. Parameters ---------- @@ -210,6 +209,7 @@ def get_action( ------- np.ndarray The optimal action. + """ action_space, rewards = self.get_actions_and_rewards(state, action_space) if optimality == "max": @@ -225,8 +225,7 @@ def get_probabilistic_action( offset: float = 0.1, optimality: Literal["min", "max"] = "max", ) -> np.ndarray: - """ - Get a probabilistic action for a given state based on reward distribution. + """Get a probabilistic action for a given state based on reward distribution. Parameters ---------- @@ -243,6 +242,7 @@ def get_probabilistic_action( ------- 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))) @@ -261,8 +261,7 @@ def get_probabilistic_action( 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. + """Get the rewards for given states and actions. Parameters ---------- @@ -275,6 +274,7 @@ def get_rewards(self, states: np.ndarray, actions: np.ndarray) -> np.ndarray: ------- 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]) @@ -283,14 +283,15 @@ def get_rewards(self, states: np.ndarray, actions: np.ndarray) -> np.ndarray: class TD_FALCON(FALCON): - """TD-FALCON for Reinforcement Learning + """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. + 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. """ @@ -304,8 +305,7 @@ def __init__( td_alpha: float = 1.0, td_lambda: float = 1.0, ): - """ - Initialize the TD-FALCON model. + """Initialize the TD-FALCON model. Parameters ---------- @@ -323,6 +323,7 @@ def __init__( 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 @@ -331,13 +332,13 @@ def __init__( ) def fit(self, states: np.ndarray, actions: np.ndarray, rewards: np.ndarray): - """ - Fit the TD-FALCON model to the data. + """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") @@ -348,8 +349,7 @@ def calculate_SARSA( rewards: np.ndarray, single_sample_reward: Optional[float] = None, ): - """ - Calculate the SARSA values for reinforcement learning. + """Calculate the SARSA values for reinforcement learning. Parameters ---------- @@ -366,6 +366,7 @@ def calculate_SARSA( ------- 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) @@ -405,8 +406,7 @@ def partial_fit( rewards: np.ndarray, single_sample_reward: Optional[float] = None, ): - """ - Partially fit the TD-FALCON model using SARSA. + """Partially fit the TD-FALCON model using SARSA. Parameters ---------- @@ -423,6 +423,7 @@ def partial_fit( ------- TD_FALCON The partially fitted TD-FALCON model. + """ states_fit, actions_fit, sarsa_rewards_fit = self.calculate_SARSA( states, actions, rewards, single_sample_reward diff --git a/artlib/reinforcement/__init__.py b/artlib/reinforcement/__init__.py index 2c86b7d..93dc41b 100644 --- a/artlib/reinforcement/__init__.py +++ b/artlib/reinforcement/__init__.py @@ -1,13 +1,14 @@ -""" -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. +"""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. diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index f8c7542..5d06d65 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -1,7 +1,9 @@ -""" -Carpenter, G. A., Grossberg, S., & Reynolds, J. H. (1991a). +"""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. + """ import numpy as np from typing import Literal, Tuple, Dict @@ -11,7 +13,7 @@ class ARTMAP(SimpleARTMAP): - """ARTMAP for Classification and Regression + """ARTMAP for Classification and Regression. This module implements ARTMAP as first published in Carpenter, G. A., Grossberg, S., & Reynolds, J. H. (1991a). @@ -27,8 +29,7 @@ class ARTMAP(SimpleARTMAP): """ def __init__(self, module_a: BaseART, module_b: BaseART): - """ - Initialize the ARTMAP model with two ART modules. + """Initialize the ARTMAP model with two ART modules. Parameters ---------- @@ -36,13 +37,13 @@ def __init__(self, module_a: BaseART, module_b: 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. + """Get the parameters of the ARTMAP model. Parameters ---------- @@ -53,6 +54,7 @@ def get_params(self, deep: bool = True) -> dict: ------- dict Parameter names mapped to their values. + """ out = { "module_a": self.module_a, @@ -69,43 +71,42 @@ def get_params(self, deep: bool = True) -> dict: @property def labels_a(self) -> np.ndarray: - """ - Get the labels generated by the A-side ART module. + """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) -> np.ndarray: - """ - Get the labels generated by the B-side ART module. + """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) -> Dict[str, np.ndarray]: - """ - Get the labels generated by both the A-side and B-side ART modules. + """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): - """ - Validate the input data prior to clustering. + """Validate the input data prior to clustering. Parameters ---------- @@ -113,6 +114,7 @@ def validate_data(self, X: np.ndarray, y: 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) @@ -120,8 +122,7 @@ def validate_data(self, X: np.ndarray, y: np.ndarray): def prepare_data( self, X: np.ndarray, y: np.ndarray ) -> Tuple[np.ndarray, np.ndarray]: - """ - Prepare data for clustering by normalizing and transforming. + """Prepare data for clustering by normalizing and transforming. Parameters ---------- @@ -134,14 +135,14 @@ def prepare_data( ------- 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 its original state before preparation. + """Restore data to its original state before preparation. Parameters ---------- @@ -154,6 +155,7 @@ def restore_data( ------- tuple of np.ndarray Restored data for both channels. + """ return self.module_a.restore_data(X), self.module_b.restore_data(y) @@ -165,8 +167,7 @@ def fit( match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10, ): - """ - Fit the ARTMAP model to the data. + """Fit the ARTMAP model to the data. Parameters ---------- @@ -185,6 +186,7 @@ def fit( ------- self : ARTMAP Fitted ARTMAP model. + """ # Check that X and y have correct shape self.validate_data(X, y) @@ -215,8 +217,7 @@ def partial_fit( match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10, ): - """ - Partially fit the ARTMAP model to the data. + """Partially fit the ARTMAP model to the data. Parameters ---------- @@ -233,6 +234,7 @@ def partial_fit( ------- self : ARTMAP Partially fitted ARTMAP model. + """ self.validate_data(X, y) self.module_b.partial_fit( @@ -247,8 +249,7 @@ def partial_fit( return self def predict(self, X: np.ndarray) -> np.ndarray: - """ - Predict the labels for the given data. + """Predict the labels for the given data. Parameters ---------- @@ -259,13 +260,13 @@ def predict(self, X: np.ndarray) -> np.ndarray: ------- 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 both A-side and B-side labels for the given data. + """Predict both A-side and B-side labels for the given data. Parameters ---------- @@ -276,6 +277,7 @@ def predict_ab(self, X: np.ndarray) -> tuple[np.ndarray, np.ndarray]: ------- tuple of np.ndarray A labels and B labels for the data. + """ check_is_fitted(self) return super(ARTMAP, self).predict_ab(X) diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index ac3a74f..0ce0b97 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -1,7 +1,9 @@ -""" -Serrano-Gotarredona, T., Linares-Barranco, B., & Andreou, A. G. (1998). +"""Serrano-Gotarredona, T., Linares-Barranco, B., & Andreou, A. + +G. (1998). Adaptive Resonance Theory Microchips: Circuit Design Techniques. Norwell, MA, USA: Kluwer Academic Publishers. + """ import numpy as np from typing import Optional, Iterable, Literal, Dict @@ -13,7 +15,7 @@ class SimpleARTMAP(BaseARTMAP): - """SimpleARTMAP for Classification + """SimpleARTMAP for Classification. This module implements SimpleARTMAP as first published in Serrano-Gotarredona, T., Linares-Barranco, B., & Andreou, A. G. (1998). @@ -28,13 +30,13 @@ class SimpleARTMAP(BaseARTMAP): """ def __init__(self, module_a: BaseART): - """ - Initialize SimpleARTMAP. + """Initialize SimpleARTMAP. Parameters ---------- module_a : BaseART The instantiated ART module used for clustering the independent channel. + """ self.module_a = module_a super().__init__() @@ -48,8 +50,7 @@ def match_reset_func( extra: dict, cache: Optional[dict] = None, ) -> bool: - """ - Permits external factors to influence cluster creation. + """Permits external factors to influence cluster creation. Parameters ---------- @@ -70,6 +71,7 @@ def match_reset_func( ------- 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: @@ -77,8 +79,7 @@ def match_reset_func( return True def get_params(self, deep: bool = True) -> dict: - """ - Get parameters of the model. + """Get parameters of the model. Parameters ---------- @@ -89,6 +90,7 @@ def get_params(self, deep: bool = True) -> dict: ------- dict Parameter names mapped to their values. + """ out = {"module_a": self.module_a} if deep: @@ -99,8 +101,7 @@ def get_params(self, deep: bool = True) -> dict: def validate_data( self, X: np.ndarray, y: np.ndarray ) -> tuple[np.ndarray, np.ndarray]: - """ - Validate data prior to clustering. + """Validate data prior to clustering. Parameters ---------- @@ -113,14 +114,14 @@ def validate_data( ------- 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) return X, y def prepare_data(self, X: np.ndarray) -> np.ndarray: - """ - Prepare data for clustering. + """Prepare data for clustering. Parameters ---------- @@ -131,12 +132,12 @@ def prepare_data(self, X: np.ndarray) -> np.ndarray: ------- 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 ---------- @@ -147,6 +148,7 @@ def restore_data(self, X: np.ndarray) -> np.ndarray: ------- np.ndarray Restored data. + """ return self.module_a.restore_data(X) @@ -157,8 +159,7 @@ def step_fit( match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10, ) -> int: - """ - Fit the model to a single sample. + """Fit the model to a single sample. Parameters ---------- @@ -175,6 +176,7 @@ def step_fit( ------- int Side A cluster label. + """ match_reset_func = lambda i, w, cluster, params, cache: self.match_reset_func( i, @@ -205,8 +207,7 @@ def fit( epsilon: float = 1e-10, verbose: bool = False, ): - """ - Fit the model to the data. + """Fit the model to the data. Parameters ---------- @@ -227,6 +228,7 @@ def fit( ------- self : SimpleARTMAP The fitted model. + """ # Check that X and y have correct shape SimpleARTMAP.validate_data(self, X, y) @@ -263,8 +265,7 @@ def partial_fit( 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 ---------- @@ -281,6 +282,7 @@ def partial_fit( ------- self : SimpleARTMAP The partially fitted model. + """ SimpleARTMAP.validate_data(self, X, y) if not hasattr(self, "labels_"): @@ -306,79 +308,78 @@ def partial_fit( @property def labels_a(self) -> np.ndarray: - """ - Get labels from side A (module A). + """Get labels from side A (module A). Returns ------- np.ndarray Labels from module A. + """ return self.module_a.labels_ @property def labels_b(self) -> np.ndarray: - """ - Get labels from side B. + """Get labels from side B. Returns ------- np.ndarray Labels from side B. + """ return self.labels_ @property def labels_ab(self) -> Dict[str, np.ndarray]: - """ - Get labels from both A-side and B-side. + """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) -> int: - """ - Get the number of clusters in side A. + """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) -> int: - """ - Get the number of clusters in side A. + """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) -> int: - """ - Get the number of clusters in side B. + """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. + """Predict the label for a single sample. Parameters ---------- @@ -389,14 +390,14 @@ def step_pred(self, x: np.ndarray) -> tuple[int, int]: ------- 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] return c_a, c_b def predict(self, X: np.ndarray) -> np.ndarray: - """ - Predict labels for the data. + """Predict labels for the data. Parameters ---------- @@ -407,6 +408,7 @@ def predict(self, X: np.ndarray) -> np.ndarray: ------- np.ndarray B labels for the data. + """ check_is_fitted(self) y_b = np.zeros((X.shape[0],), dtype=int) @@ -416,8 +418,7 @@ def predict(self, X: np.ndarray) -> np.ndarray: return y_b 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 ---------- @@ -428,6 +429,7 @@ def predict_ab(self, X: np.ndarray) -> tuple[np.ndarray, np.ndarray]: ------- 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) @@ -439,8 +441,7 @@ def predict_ab(self, X: np.ndarray) -> tuple[np.ndarray, np.ndarray]: return y_a, y_b def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): - """ - Visualize the cluster boundaries. + """Visualize the cluster boundaries. Parameters ---------- @@ -450,6 +451,7 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): Colors to use for each cluster. linewidth : int, default=1 Width of boundary lines. + """ colors_a = [] for k_a in range(self.n_clusters): @@ -465,8 +467,7 @@ def visualize( linewidth: int = 1, colors: Optional[Iterable] = None, ): - """ - Visualize the clustering of the data. + """Visualize the clustering of the data. Parameters ---------- @@ -482,6 +483,7 @@ def visualize( 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/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index d3c7b2c..998fef1 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -1,7 +1,8 @@ -""" -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. +"""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. + """ import numpy as np from typing import Optional, Callable, Iterable, List, Literal @@ -12,22 +13,23 @@ class DualVigilanceART(BaseART): - """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. + """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. """ def __init__(self, base_module: BaseART, rho_lower_bound: float): - """ - Initialize the Dual Vigilance ART model. + """Initialize the Dual Vigilance ART model. Parameters ---------- @@ -55,8 +57,7 @@ def __init__(self, base_module: BaseART, rho_lower_bound: float): self.map: dict[int, int] = dict() def prepare_data(self, X: np.ndarray) -> np.ndarray: - """ - Prepare data for clustering. + """Prepare data for clustering. Parameters ---------- @@ -72,8 +73,7 @@ def prepare_data(self, X: np.ndarray) -> np.ndarray: return self.base_module.prepare_data(X) def restore_data(self, X: np.ndarray) -> np.ndarray: - """ - Restore data to its state prior to preparation. + """Restore data to its state prior to preparation. Parameters ---------- @@ -89,8 +89,7 @@ def restore_data(self, X: np.ndarray) -> np.ndarray: return self.base_module.restore_data(X) def get_params(self, deep: bool = True) -> dict: - """ - Get the parameters of the estimator. + """Get the parameters of the estimator. Parameters ---------- @@ -115,8 +114,7 @@ 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 ------- @@ -128,8 +126,7 @@ def n_clusters(self) -> int: @property def dim_(self): - """ - Get the dimensionality of the data from the base module. + """Get the dimensionality of the data from the base module. Returns ------- @@ -145,8 +142,7 @@ def dim_(self, new_dim): @property def labels_(self): - """ - Get the labels from the base module. + """Get the labels from the base module. Returns ------- @@ -166,8 +162,7 @@ def W(self): @W.setter def W(self, new_W: list[np.ndarray]): - """ - Get the weights from the base module. + """Get the weights from the base module. Returns ------- @@ -178,8 +173,7 @@ def W(self, new_W: list[np.ndarray]): self.base_module.W = new_W def check_dimensions(self, X: np.ndarray): - """ - Check that the data has the correct dimensions. + """Check that the data has the correct dimensions. Parameters ---------- @@ -190,8 +184,7 @@ def check_dimensions(self, X: np.ndarray): self.base_module.check_dimensions(X) def validate_data(self, X: np.ndarray): - """ - Validate the data prior to clustering. + """Validate the data prior to clustering. Parameters ---------- @@ -203,8 +196,7 @@ def validate_data(self, X: np.ndarray): self.check_dimensions(X) def validate_params(self, params: dict): - """ - Validate clustering parameters. + """Validate clustering parameters. Parameters ---------- @@ -226,8 +218,7 @@ def _match_tracking( params: dict, method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"], ) -> bool: - """ - Adjust match tracking based on the method and epsilon value. + """Adjust match tracking based on the method and epsilon value. Parameters ---------- @@ -277,8 +268,7 @@ def step_fit( 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 ---------- @@ -376,8 +366,7 @@ def step_fit( return self.map[c_new] def step_pred(self, x) -> int: - """ - Predict the label for a single sample. + """Predict the label for a single sample. Parameters ---------- @@ -401,8 +390,7 @@ def step_pred(self, x) -> int: return self.map[c_] def get_cluster_centers(self) -> List[np.ndarray]: - """ - Get the centers of each cluster, used for regression. + """Get the centers of each cluster, used for regression. Returns ------- @@ -413,8 +401,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: return self.base_module.get_cluster_centers() def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): - """ - Visualize the bounds of each cluster. + """Visualize the bounds of each cluster. Parameters ---------- diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index 7dc11b3..6630b9f 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -1,5 +1,6 @@ -""" -Tscherepanow, M. (2010). +"""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). @@ -18,7 +19,7 @@ class TopoART(BaseART): - """Topo ART for Topological Clustering + """Topo ART for Topological Clustering. This module implements Topo ART as first published in Tscherepanow, M. (2010). @@ -34,8 +35,7 @@ class TopoART(BaseART): """ def __init__(self, base_module: BaseART, beta_lower: float, tau: int, phi: int): - """ - Initialize TopoART. + """Initialize TopoART. Parameters ---------- @@ -47,6 +47,7 @@ def __init__(self, base_module: BaseART, beta_lower: float, tau: int, phi: 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"): @@ -65,8 +66,7 @@ def __init__(self, base_module: BaseART, beta_lower: float, tau: int, phi: int): @staticmethod def validate_params(params: dict): - """ - Validate clustering parameters. + """Validate clustering parameters. Parameters ---------- @@ -77,6 +77,7 @@ def validate_params(params: dict): ------ AssertionError If the required parameters are not provided or are invalid. + """ assert ( "beta" in params @@ -93,42 +94,41 @@ def validate_params(params: dict): @property def W(self) -> List[np.ndarray]: - """ - Get the weight matrix of the base module. + """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. + """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): - """ - Validate the data prior to clustering. + """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 ---------- @@ -139,12 +139,12 @@ def prepare_data(self, X: np.ndarray) -> np.ndarray: ------- np.ndarray Prepared (normalized) data. + """ return self.base_module.prepare_data(X) def restore_data(self, X: np.ndarray) -> np.ndarray: - """ - Restore data to the state prior to preparation. + """Restore data to the state prior to preparation. Parameters ---------- @@ -155,14 +155,14 @@ def restore_data(self, X: np.ndarray) -> np.ndarray: ------- 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. + """Get the activation of the cluster. Parameters ---------- @@ -177,6 +177,7 @@ def category_choice( ------- tuple[float, Optional[dict]] Cluster activation and cache used for later processing. + """ return self.base_module.category_choice(i, w, params) @@ -187,8 +188,7 @@ def match_criterion( params: dict, cache: Optional[dict] = None, ) -> tuple[float, dict]: - """ - Get the match criterion of the cluster. + """Get the match criterion of the cluster. Parameters ---------- @@ -205,6 +205,7 @@ def match_criterion( ------- tuple[float, dict] Cluster match criterion and cache used for later processing. + """ return self.base_module.match_criterion(i, w, params, cache) @@ -216,8 +217,7 @@ def match_criterion_bin( 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 ---------- @@ -236,6 +236,7 @@ def match_criterion_bin( ------- tuple[bool, dict] Binary match criterion and cache used for later processing. + """ return self.base_module.match_criterion_bin(i, w, params, cache, op) @@ -246,8 +247,7 @@ def update( params: dict, cache: Optional[dict] = None, ) -> np.ndarray: - """ - Update the cluster weight. + """Update the cluster weight. Parameters ---------- @@ -264,14 +264,14 @@ def update( ------- 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. + """Generate a new cluster weight. Parameters ---------- @@ -284,18 +284,19 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: ------- np.ndarray Newly generated cluster weight. + """ return self.base_module.new_weight(i, params) def add_weight(self, new_w: np.ndarray): - """ - Add a new cluster weight. + """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)) @@ -306,13 +307,13 @@ def add_weight(self, new_w: np.ndarray): self.W.append(new_w) def prune(self, X: np.ndarray): - """ - Prune clusters based on the number of associated samples. + """Prune clusters based on the number of associated samples. Parameters ---------- X : np.ndarray The input dataset. + """ a = ( np.array(self.weight_sample_counter_).reshape( @@ -354,13 +355,14 @@ def prune(self, X: np.ndarray): self.labels_[i] = -1 def post_step_fit(self, X: np.ndarray): - """ - Perform post-fit operations, such as cluster pruning, after fitting each sample. + """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) @@ -372,8 +374,7 @@ def _match_tracking( params: dict, method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"], ) -> bool: - """ - Adjust the vigilance parameter based on match tracking methods. + """Adjust the vigilance parameter based on match tracking methods. Parameters ---------- @@ -390,6 +391,7 @@ def _match_tracking( ------- bool True if the match tracking continues, False otherwise. + """ M = cache["match_criterion"] if method == "MT+": @@ -410,24 +412,24 @@ def _match_tracking( raise ValueError(f"Invalid Match Tracking Method: {method}") def _set_params(self, new_params): - """ - Set new parameters for the base module. + """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: - """ - Create a deep copy of the parameters. + """Create a deep copy of the parameters. Returns ------- dict Deep copy of the parameters. + """ return deepcopy(self.base_module.params) @@ -438,8 +440,7 @@ def step_fit( 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 ---------- @@ -456,6 +457,7 @@ def step_fit( ------- int Cluster label of the input sample. + """ base_params = self._deep_copy_params() mt_operator = self._match_tracking_operator(match_reset_method) @@ -534,19 +536,18 @@ def step_fit( return resonant_c def get_cluster_centers(self) -> List[np.ndarray]: - """ - Get the centers of each cluster. + """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): - """ - Visualize the boundaries of each cluster. + """Visualize the boundaries of each cluster. Parameters ---------- @@ -556,6 +557,7 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): Colors to use for each cluster. linewidth : int, default=1 Width of boundary lines. + """ try: self.base_module.plot_cluster_bounds( diff --git a/artlib/topological/__init__.py b/artlib/topological/__init__.py index 352263b..72f9273 100644 --- a/artlib/topological/__init__.py +++ b/artlib/topological/__init__.py @@ -1,13 +1,14 @@ -""" -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. +"""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 herein 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 +The two modules herein 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 `_ From a48b11477344cd5963e5ee3ecb651ddd0fa7a61d Mon Sep 17 00:00:00 2001 From: niklas melton Date: Wed, 16 Oct 2024 23:45:38 -0500 Subject: [PATCH 090/139] pass flake8 and black --- .pre-commit-config.yaml | 14 +++---- artlib/__init__.py | 3 ++ artlib/biclustering/BARTMAP.py | 47 +++++++++++++++++++---- artlib/common/BaseART.py | 19 +++++++-- artlib/common/BaseARTMAP.py | 3 +- artlib/common/VAT.py | 14 +++++-- artlib/common/utils.py | 1 + artlib/common/visualization.py | 7 +++- artlib/cvi/CVIART.py | 34 +++++++++++++---- artlib/cvi/__init__.py | 3 +- artlib/cvi/iCVIFuzzyArt.py | 23 ++++++----- artlib/cvi/iCVIs/CalinkskiHarabasz.py | 51 +++++++++++++------------ artlib/cvi/iCVIs/__init__.py | 1 + artlib/elementary/ART1.py | 11 +++--- artlib/elementary/ART2.py | 26 +++++++------ artlib/elementary/BayesianART.py | 14 ++++--- artlib/elementary/EllipsoidART.py | 7 +++- artlib/elementary/FuzzyART.py | 17 ++++++--- artlib/elementary/GaussianART.py | 21 +++++----- artlib/elementary/HypersphereART.py | 9 +++-- artlib/elementary/QuadraticNeuronART.py | 7 ++-- artlib/elementary/__init__.py | 8 +++- artlib/fusion/FusionART.py | 40 ++++++++++--------- artlib/hierarchical/DeepARTMAP.py | 33 ++++++++-------- artlib/hierarchical/SMART.py | 9 +++-- artlib/reinforcement/FALCON.py | 20 ++++++---- artlib/supervised/ARTMAP.py | 28 ++++++++------ artlib/supervised/SimpleARTMAP.py | 19 +++++---- artlib/supervised/__init__.py | 14 ++++--- artlib/topological/DualVigilanceART.py | 39 ++++++++++--------- artlib/topological/TopoART.py | 23 ++++++----- 31 files changed, 354 insertions(+), 211 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b1d4dd4..9f24e67 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,12 +28,12 @@ repos: hooks: - id: black -# - repo: https://github.com/PyCQA/flake8 -# rev: 6.1.0 # Use the latest stable version -# hooks: -# - id: flake8 -# args: [--max-line-length=88] -# additional_dependencies: [flake8-docstrings] + - repo: https://github.com/PyCQA/flake8 + rev: 6.1.0 # Use the latest stable version + hooks: + - id: flake8 + args: [--max-line-length=88, '--ignore=D205,D400,D105,E731,W503,D401,E203,D209'] + additional_dependencies: [flake8-docstrings] # # - repo: https://github.com/PyCQA/pydocstyle # rev: 6.3.0 @@ -47,4 +47,4 @@ repos: # - id: mypy # args: [--strict] -exclude: ^(unit_tests/|scripts/|artlib/experimental/|examples/|templates/) +exclude: ^(unit_tests/|scripts/|artlib/experimental/|examples/|templates/|docs/) diff --git a/artlib/__init__.py b/artlib/__init__.py index 3dd8c07..aee1c79 100644 --- a/artlib/__init__.py +++ b/artlib/__init__.py @@ -56,6 +56,9 @@ "BaseARTMAP", "normalize", "compliment_code", + "de_compliment_code", + "de_normalize", + "VAT", "ART1", "ART2A", "BayesianART", diff --git a/artlib/biclustering/BARTMAP.py b/artlib/biclustering/BARTMAP.py index 7a07208..8aab640 100644 --- a/artlib/biclustering/BARTMAP.py +++ b/artlib/biclustering/BARTMAP.py @@ -1,4 +1,5 @@ -""" +"""BARTMAP. + 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. @@ -8,6 +9,7 @@ U.S. Patent 9,043,326 Filed January 28, 2012, claiming priority to Provisional U.S. Patent Application, January 28, 2011, issued May 26, 2015. + """ import numpy as np @@ -117,7 +119,6 @@ def set_params(self, **params): The estimator instance. """ - if not params: # Simple optimization to gain speed (inspect is slow) return self @@ -160,19 +161,51 @@ def validate_params(params: dict): assert isinstance(params["eta"], float) @property - def column_labels_(self): + def column_labels_(self) -> np.ndarray: + """Cluster labels for the columns. + + Returns + ------- + column_labels_ : ndarray of shape (n_columns,) + Array of cluster labels assigned to each column. + + """ return self.module_b.labels_ @property - def row_labels_(self): + def row_labels_(self) -> np.ndarray: + """Cluster labels for the rows. + + Returns + ------- + row_labels_ : ndarray of shape (n_rows,) + Array of cluster labels assigned to each row. + + """ return self.module_a.labels_ @property - def n_row_clusters(self): + def n_row_clusters(self) -> int: + """Number of row clusters. + + Returns + ------- + n_row_clusters : int + The number of clusters for the rows. + + """ return self.module_a.n_clusters @property - def n_column_clusters(self): + def n_column_clusters(self) -> int: + """Number of column clusters. + + Returns + ------- + n_column_clusters : int + The number of clusters for the columns. + + """ return self.module_b.n_clusters def _get_x_cb(self, x: np.ndarray, c_b: int): @@ -404,8 +437,6 @@ def visualize(self, cmap: Optional[Colormap] = None): import matplotlib.pyplot as plt if cmap is None: - from matplotlib.pyplot import cm - cmap = plt.cm.Blues plt.matshow( diff --git a/artlib/common/BaseART.py b/artlib/common/BaseART.py index fbe9f0f..ec0c628 100644 --- a/artlib/common/BaseART.py +++ b/artlib/common/BaseART.py @@ -1,5 +1,6 @@ +"""Base class for all ART objects.""" import numpy as np -from typing import Optional, Callable, Iterable, Literal, List, Tuple +from typing import Optional, Callable, Iterable, Literal, List from copy import deepcopy from collections import defaultdict from matplotlib.axes import Axes @@ -603,7 +604,6 @@ def partial_fit( Epsilon value used for adjusting match criterion. """ - self.validate_data(X) self.check_dimensions(X) self.is_fitted_ = True @@ -639,7 +639,6 @@ def predict(self, X: np.ndarray) -> np.ndarray: Labels for the data. """ - check_is_fitted(self) self.validate_data(X) self.check_dimensions(X) @@ -651,6 +650,20 @@ def predict(self, X: np.ndarray) -> np.ndarray: return y def shrink_clusters(self, shrink_ratio: float = 0.1): + """Shrink the clusters by a specified ratio. + + Parameters + ---------- + shrink_ratio : float, optional + The ratio by which to shrink the clusters. Must be between 0 and 1. + Default is 0.1. + + Returns + ------- + self : object + Returns the instance with shrunken clusters. + + """ return self def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): diff --git a/artlib/common/BaseARTMAP.py b/artlib/common/BaseARTMAP.py index 63ad2df..ee29540 100644 --- a/artlib/common/BaseARTMAP.py +++ b/artlib/common/BaseARTMAP.py @@ -1,3 +1,4 @@ +"""Base class for all ARTMAP objects.""" import numpy as np from typing import Union, Optional, Iterable, Literal from collections import defaultdict @@ -9,6 +10,7 @@ class BaseARTMAP(BaseEstimator, ClassifierMixin, ClusterMixin): """Generic implementation of Adaptive Resonance Theory MAP (ARTMAP)""" def __init__(self): + """Instantiate the BaseARTMAP object.""" self.map: dict[int, int] = dict() def set_params(self, **params): @@ -27,7 +29,6 @@ def set_params(self, **params): Estimator instance. """ - if not params: # Simple optimization to gain speed (inspect is slow) return self diff --git a/artlib/common/VAT.py b/artlib/common/VAT.py index 5e38921..3ae48b8 100644 --- a/artlib/common/VAT.py +++ b/artlib/common/VAT.py @@ -1,5 +1,11 @@ -# Bezdek, J. C., & Hathaway, R. J. (2002). VAT: A tool for visual assessment of cluster tendency. -# Proceedings of the 2002 International Joint Conference on Neural Networks. doi:10.1109/IJCNN.2002.1007487 +"""VAT. + +Bezdek, J. C., & Hathaway, R. J. (2002). +VAT: A tool for visual assessment of cluster tendency. +Proceedings of the 2002 International Joint Conference on Neural Networks. +doi:10.1109/IJCNN.2002.1007487 + +""" import numpy as np from typing import Optional, Tuple, Callable from scipy.spatial.distance import pdist, squareform @@ -16,8 +22,8 @@ def VAT( 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. + Callable function to calculate pairwise distances. Defaults to Euclidean + distance using `pdist`. If None, assumes data is a pre-computed distance matrix. Returns ------- diff --git a/artlib/common/utils.py b/artlib/common/utils.py index bd20c33..82ef009 100644 --- a/artlib/common/utils.py +++ b/artlib/common/utils.py @@ -1,3 +1,4 @@ +"""General utilities used throughout ARTLib.""" import numpy as np from typing import Tuple, Optional diff --git a/artlib/common/visualization.py b/artlib/common/visualization.py index 81d2fd6..0d499ce 100644 --- a/artlib/common/visualization.py +++ b/artlib/common/visualization.py @@ -1,3 +1,4 @@ +"""Collection of visualization utilities.""" import numpy as np from matplotlib.axes import Axes @@ -23,7 +24,8 @@ def plot_gaussian_contours_fading( 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. + 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 @@ -86,7 +88,8 @@ def plot_gaussian_contours_covariance( 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. + 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 diff --git a/artlib/cvi/CVIART.py b/artlib/cvi/CVIART.py index d3ff9cc..104c51d 100644 --- a/artlib/cvi/CVIART.py +++ b/artlib/cvi/CVIART.py @@ -1,3 +1,4 @@ +"""CVIART.""" import numpy as np from copy import deepcopy import sklearn.metrics as metrics @@ -11,10 +12,11 @@ class CVIART(BaseART): Expanded version of Art that uses Cluster Validity Indicies to help with cluster selection. PBM is not implemented, can be seen here. - https://git.mst.edu/acil-group/CVI-Fuzzy-ART/-/blob/master/PBM_index.m?ref_type=heads + git.mst.edu/acil-group/CVI-Fuzzy-ART/-/blob/master/PBM_index.m?ref_type=heads - 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. + 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. """ @@ -90,7 +92,15 @@ def restore_data(self, X: np.ndarray) -> np.ndarray: return self.base_module.restore_data(X) @property - def W(self): + def W(self) -> List: + """Get the base module weights. + + Returns + ------- + list of np.ndarray + base module weights + + """ return self.base_module.W @W.setter @@ -98,7 +108,15 @@ def W(self, new_W): self.base_module.W = new_W @property - def labels_(self): + def labels_(self) -> np.ndarray: + """Get the base module labels. + + Returns + ------- + np.ndarray + base module labels + + """ return self.base_module.labels_ @labels_.setter @@ -218,7 +236,8 @@ def fit( 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. + 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. @@ -312,7 +331,8 @@ def step_fit( 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. + 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. diff --git a/artlib/cvi/__init__.py b/artlib/cvi/__init__.py index af3ca55..47f8e87 100644 --- a/artlib/cvi/__init__.py +++ b/artlib/cvi/__init__.py @@ -8,6 +8,7 @@ This module implements CVI-driven ART modules which utilize the CVI to inform clustering; often resulting in objectively superior results. -`Cluster validity indices `_ +`Cluster validity indices +`_ """ diff --git a/artlib/cvi/iCVIFuzzyArt.py b/artlib/cvi/iCVIFuzzyArt.py index 94029dc..d69dd0d 100644 --- a/artlib/cvi/iCVIFuzzyArt.py +++ b/artlib/cvi/iCVIFuzzyArt.py @@ -1,9 +1,9 @@ -"""Add Reference in correct format. +"""TODO: Add Reference in correct format. The original matlab code can be found at https://github.com/ACIL-Group/iCVI-toolbox/tree/master The formulation is available at -https://scholarsmine.mst.edu/cgi/viewcontent.cgi?article=3833&context=doctoral_dissertations +scholarsmine.mst.edu/cgi/viewcontent.cgi?article=3833&context=doctoral_dissertations Pages 314-316 and 319-320 Extended icvi offline mode can be found at https://ieeexplore.ieee.org/document/9745260 @@ -36,7 +36,8 @@ def __init__( 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. + 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 @@ -48,9 +49,7 @@ def __init__( "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 "validity" in self.params assert isinstance(self.params["validity"], int) def iCVI_match(self, x, w, c_, params, cache): @@ -72,18 +71,21 @@ def iCVI_match(self, x, w, c_, params, cache): Returns ------- bool - True if the new criterion value is better than the previous one, False otherwise. + 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: new = self.iCVI.add_sample(x, c_) - # Eventually this should be an icvi function that you pass the params, and it handles if this is true or false. + # Eventually this should be an icvi function that you pass the params, + # and it handles if this is true or false. return new["criterion_value"] > self.iCVI.criterion_value # return self.iCVI.evalLabel(x, c_) This except pass params instead. - # Could add max epochs back in, but only if offline is true, or do something special... + # Could add max epochs back in, but only if offline is true, + # or do something special... def fit( self, X: np.ndarray, @@ -102,7 +104,8 @@ def fit( 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. + 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. diff --git a/artlib/cvi/iCVIs/CalinkskiHarabasz.py b/artlib/cvi/iCVIs/CalinkskiHarabasz.py index 4dbd115..277bafb 100644 --- a/artlib/cvi/iCVIs/CalinkskiHarabasz.py +++ b/artlib/cvi/iCVIs/CalinkskiHarabasz.py @@ -68,16 +68,18 @@ class iCVI_CH: The original matlab code can be found at https://github.com/ACIL-Group/iCVI-toolbox/blob/master/classes/CVI_CH.m The formulation is available at - https://scholarsmine.mst.edu/cgi/viewcontent.cgi?article=3833&context=doctoral_dissertations + scholarsmine.mst.edu/cgi/viewcontent.cgi?article=3833&context=doctoral_dissertations Pages 314-316 and 319-320 - This implementation returns a dictionary of updated parameters when calling functions, which can then be passed with - the update function to accept the changes. This allows for testing changes/additions to the categories without doing a - deep copy of the object. + This implementation returns a dictionary of updated parameters when calling + functions, which can then be passed with the update function to accept the changes. + This allows for testing changes/additions to the categories without doing a deep + copy of the object. - In addition, the calculations for removing a sample, or switching the label of a sample from the dataset are included. - This allows for very efficient calculations on clustering algorithms that would like to prune or adjust the labels of - samples in the dataset. + In addition, the calculations for removing a sample, or switching the label of a + sample from the dataset are included. This allows for very efficient calculations + on clustering algorithms that would like to prune or adjust the labels of samples + in the dataset. For the Calinski Harabasz validity Index, larger values represent better clusters. @@ -143,7 +145,8 @@ def add_sample(self, x: np.ndarray, label: int) -> dict: Data = self.CD[label] CD["n"] = Data["n"] + 1 - # The paper defines deltaV = Vold - Vnew, so I need to switch this sign. Consider changing functions to do this. + # The paper defines deltaV = Vold - Vnew, so I need to switch this sign. + # Consider changing functions to do this. deltaV = -1 * delta_add_sample_to_average(Data["v"], x, CD["n"]) CD["v"] = Data["v"] - deltaV # Vnew = Vold - deltaV diff_x_v = x - CD["v"] @@ -171,9 +174,8 @@ def add_sample(self, x: np.ndarray, label: int) -> dict: WGSS = self.WGSS + newP["CP_diff"] BGSS = sum(SEP) # between-group sum of squares - if ( - WGSS == 0 - ): # this can be 0 if all samples in different clusters, which is a divide by 0 error. + if WGSS == 0: # this can be 0 if all samples in different clusters, + # which is a divide by 0 error. newP["criterion_value"] = 0 else: newP["criterion_value"] = ( @@ -205,12 +207,14 @@ def update(self, params: dict) -> None: 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 + 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 that are needed. - 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. + 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. Parameters ---------- @@ -268,9 +272,7 @@ def switch_label(self, x: np.ndarray, label_old: int, label_new: int) -> dict: else: n_clusters = len(self.CD) - if ( - n_clusters < 2 - ): # I don't think this can ever happen with remove label not deleting categories. + if n_clusters < 2: # I don't think this can ever happen newP["criterion_value"] = 0 else: for i in self.CD: @@ -289,9 +291,8 @@ def switch_label(self, x: np.ndarray, label_old: int, label_new: int) -> dict: WGSS = self.WGSS + newP["CP_diff"] WGSS += newP["CP_diff2"] BGSS = sum(SEP) # between-group sum of squares - if ( - WGSS == 0 - ): # this can be 0 if all samples in different clusters, which is a divide by 0 error. + if WGSS == 0: # this can be 0 if all samples in different clusters, + # which is a divide by 0 error. newP["criterion_value"] = 0 else: newP["criterion_value"] = ( @@ -333,7 +334,8 @@ def remove_sample( newP["CD"] = CD CD["n"] = Data["n"] - 1 - # We need the delta v from when the sample was added, but the paper defines deltaV = Vold - Vnew, so I need to keep these signs + # We need the delta v from when the sample was added, + # but the paper defines deltaV = Vold - Vnew, so I need to keep these signs deltaVPrior = delta_remove_sample_from_average(Data["v"], x, Data["n"]) CD["v"] = Data["v"] + deltaVPrior # Vnew + deltaV = Vold diff_x_vPrior = x - Data["v"] @@ -365,9 +367,8 @@ def remove_sample( WGSS = self.WGSS + newP["CP_diff"] BGSS = sum(SEP) # between-group sum of squares - if ( - WGSS == 0 - ): # this can be 0 if all samples in different clusters, which is a divide by 0 error. + if WGSS == 0: # this can be 0 if all samples in different clusters, + # which is a divide by 0 error. newP["criterion_value"] = 0 else: newP["criterion_value"] = ( diff --git a/artlib/cvi/iCVIs/__init__.py b/artlib/cvi/iCVIs/__init__.py index e69de29..8d48a25 100644 --- a/artlib/cvi/iCVIs/__init__.py +++ b/artlib/cvi/iCVIs/__init__.py @@ -0,0 +1 @@ +"""Implementations of common iCVIs.""" diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index 1afdfad..217e18b 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -1,8 +1,8 @@ -"""Carpenter, G. +"""ART1. -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. +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. """ @@ -31,7 +31,8 @@ def __init__(self, rho: float, beta: float, L: float): 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. + 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. diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index e5a0278..1c44f7a 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -1,4 +1,5 @@ -""" +"""ART2. + Carpenter, G. A., & Grossberg, S. (1987b). ART 2: self-organization of stable category recognition codes for analog input patterns. Appl. Opt., 26, 4919–4930. doi:10.1364/AO.26.004919. @@ -6,17 +7,15 @@ 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. -""" -""" ================================================================== DISCLAIMER: DO NOT USE ART2!!! IT DOES NOT WORK It is provided for completeness only. Stephan Grossberg himself has said ART2 does not work. ================================================================== -""" +""" import numpy as np from typing import Optional, List from warnings import warn @@ -26,10 +25,14 @@ class ART2A(BaseART): """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. + 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. """ @@ -43,12 +46,11 @@ def __init__(self, rho: float, alpha: float, beta: float): 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. + 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" - ) + warn("Do Not Use ART2. It does not work. Module provided for completeness only") params = { "rho": rho, diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index 624dad1..1e3210d 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -1,11 +1,11 @@ -"""Vigdor, B., & Lerner, B. +"""Bayesian ART. -(2007). The Bayesian ARTMAP. IEEE Transactions on Neural Networks, 18, 1628–1644. -doi:10.1109/TNN.2007.900234. +Vigdor, B., & Lerner, B. (2007). The Bayesian ARTMAP. IEEE Transactions on Neural +Networks, 18, 1628–1644. doi:10.1109/TNN.2007.900234. """ import numpy as np -from typing import Optional, Iterable, List, Callable, Literal, Tuple +from typing import Optional, Iterable, List, Callable, Literal import operator from matplotlib.axes import Axes from artlib.common.BaseART import BaseART @@ -144,7 +144,8 @@ def match_criterion( """ # the original paper uses the det(cov_old) for match criterion - # however, it makes logical sense to use the new_cov and results are improved when doing so + # however, it makes logical sense to use the new_cov and results are + # improved when doing so new_w = self.update(i, w, params, cache) new_cov = new_w[self.dim_ : -1].reshape((self.dim_, self.dim_)) cache["new_w"] = new_w @@ -222,7 +223,8 @@ def _match_tracking( """ M = cache["match_criterion"] - # we have to reverse some signs becayse bayesianART has an inverted vigilence check + # we have to reverse some signs because bayesianART has an inverted + # vigilence check if method == "MT+": self.params["rho"] = M - epsilon return True diff --git a/artlib/elementary/EllipsoidART.py b/artlib/elementary/EllipsoidART.py index 4d9f257..58ab48d 100644 --- a/artlib/elementary/EllipsoidART.py +++ b/artlib/elementary/EllipsoidART.py @@ -1,4 +1,6 @@ -"""Anagnostopoulos, G. C., & Georgiopoulos, M. (2001a). Ellipsoid ART and ARTMAP for +"""Ellipsoid ART. + +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. @@ -37,7 +39,8 @@ def __init__(self, rho: float, alpha: float, beta: float, mu: float, r_hat: floa 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. + 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 diff --git a/artlib/elementary/FuzzyART.py b/artlib/elementary/FuzzyART.py index 454d36f..55a440d 100644 --- a/artlib/elementary/FuzzyART.py +++ b/artlib/elementary/FuzzyART.py @@ -1,7 +1,8 @@ -"""Carpenter, G. +"""Fuzzy ART. -A., Grossberg, S., & Rosen, D. B. (1991c). -Fuzzy ART: Fast stable learning and categorization of analog patterns by an adaptive resonance system. +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. """ @@ -57,9 +58,13 @@ def get_bounding_box( class FuzzyART(BaseART): """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. + 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. """ diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index a2a6450..e233fc3 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -1,13 +1,13 @@ -"""Williamson, J. +"""Gaussian ART. -R. (1996). -Gaussian ARTMAP: A Neural Network for Fast Incremental Learning of Noisy Multidimensional Maps. +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. """ import numpy as np -from decimal import Decimal from typing import Optional, Iterable, List from matplotlib.axes import Axes from artlib.common.BaseART import BaseART @@ -17,12 +17,15 @@ class GaussianART(BaseART): """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. + 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. + + 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. """ diff --git a/artlib/elementary/HypersphereART.py b/artlib/elementary/HypersphereART.py index a1eb354..79b7e1f 100644 --- a/artlib/elementary/HypersphereART.py +++ b/artlib/elementary/HypersphereART.py @@ -1,8 +1,9 @@ -"""Anagnostopoulos, G. +"""Hyperpshere ART. -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. +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. """ import numpy as np diff --git a/artlib/elementary/QuadraticNeuronART.py b/artlib/elementary/QuadraticNeuronART.py index 98124c5..1cffe9d 100644 --- a/artlib/elementary/QuadraticNeuronART.py +++ b/artlib/elementary/QuadraticNeuronART.py @@ -1,6 +1,7 @@ -"""Su, M.-C., & Liu, T.-K. (2001). Application of neural networks using quadratic -junctions in cluster analysis. Neurocomputing, 37, 165 – 175. -doi:10.1016/S0925-2312(00)00343-X. +"""Quadratic Neuron ART. + +Su, M.-C., & Liu, T.-K. (2001). Application of neural networks using quadratic junctions +in cluster analysis. Neurocomputing, 37, 165 – 175. doi:10.1016/S0925-2312(00)00343-X. 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. diff --git a/artlib/elementary/__init__.py b/artlib/elementary/__init__.py index 0a7bbdd..80afb39 100644 --- a/artlib/elementary/__init__.py +++ b/artlib/elementary/__init__.py @@ -1,2 +1,6 @@ -"""This module contains elementary ART modules which are those ART modules that do not -implement an abstraction layer on top of another module.""" +"""Elementary ART modules which are those ART modules that do not implement an +abstraction layer on top of another module. + +These are the most basic clustering methods with the simplest geometries. + +""" diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index f8b03b1..dc37fbc 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -1,6 +1,6 @@ -"""Tan, A.-H., Carpenter, G. +"""Fusion ART. -A., & Grossberg, S. (2007). +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). @@ -29,7 +29,8 @@ def get_channel_position_tuples( Returns ------- list of tuple of int - A list of tuples where each tuple represents the start and end index for a channel. + A list of tuples where each tuple represents the start and end index for a + channel. """ positions = [] @@ -51,11 +52,13 @@ class FusionART(BaseART): 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. + 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 + multi-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. """ @@ -92,7 +95,8 @@ def get_params(self, deep: bool = True) -> dict: Parameters ---------- deep : bool, optional - If True, will return parameters for this class and the contained sub-objects that are estimators (default is True). + If True, will return parameters for this class and the contained sub-objects + that are estimators (default is True). Returns ------- @@ -430,7 +434,8 @@ def _deep_copy_params(self) -> dict: Returns ------- dict - A dictionary with module indices as keys and their deep-copied parameters as values. + 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)} @@ -456,7 +461,6 @@ def partial_fit( Value to adjust the vigilance parameter (default is 0.0). """ - self.validate_data(X) self.check_dimensions(X) self.is_fitted_ = True @@ -523,7 +527,6 @@ def predict(self, X: np.ndarray, skip_channels: List[int] = []) -> np.ndarray: Predicted labels for the input data. """ - check_is_fitted(self) self.validate_data(X) self.check_dimensions(X) @@ -661,14 +664,16 @@ def predict_regression( 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). + 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. + 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] @@ -754,7 +759,8 @@ def split_channel_data( ) current_col += channel_width else: - # If this channel was skipped, we know it was filled with 0.5, so we skip those columns + # If this channel was skipped, we know it was filled with 0.5, + # so we skip those columns current_col += channel_width return channel_data diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index d99c8cf..ae70bad 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -1,7 +1,8 @@ -"""Carpenter, G. +"""Deep ARTMAP. -A., Grossberg, S., & Reynolds, J. H. (1991a). -ARTMAP: Supervised real-time learning and classification of nonstationary data by a self-organizing neural network. +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. """ @@ -16,7 +17,6 @@ class DeepARTMAP(BaseEstimator, ClassifierMixin, ClusterMixin): - """DeepARTMAP for Hierachical Supervised and Unsupervised Learning. This module implements DeepARTMAP, a generalization of the ARTMAP class that allows @@ -53,7 +53,8 @@ def get_params(self, deep: bool = True) -> dict: Parameters ---------- deep : bool, optional, default=True - If True, will return the parameters for this class and contained subobjects that are estimators. + If True, will return the parameters for this class and contained subobjects + that are estimators. Returns ------- @@ -83,7 +84,6 @@ def set_params(self, **params): The estimator instance. """ - if not params: # Simple optimization to gain speed (inspect is slow) return self @@ -205,9 +205,10 @@ def validate_data(self, X: list[np.ndarray], y: Optional[np.ndarray] = None): 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" + assert len(X) == self.n_modules, ( + f"Must provide {self.n_modules} input matrices for " + f"{self.n_modules} ART modules" + ) if y is not None: n = len(y) else: @@ -360,9 +361,10 @@ def partial_fit( self.layers = [ SimpleARTMAP(self.modules[i]) for i in range(self.n_modules) ] - assert ( - self.is_supervised - ), "Labels were previously provided. Must continue to provide labels for partial fit." + assert self.is_supervised, ( + "Labels were previously provided. " + "Must continue to provide labels for partial fit." + ) self.layers[0] = self.layers[0].partial_fit( X[0], y, match_reset_method=match_reset_method, epsilon=epsilon ) @@ -379,9 +381,10 @@ def partial_fit( list[BaseARTMAP], [SimpleARTMAP(self.modules[i]) for i in range(2, self.n_modules)], ) - assert ( - not self.is_supervised - ), "Labels were not previously provided. Do not provide labels to continue partial fit." + assert not self.is_supervised, ( + "Labels were not previously provided. " + "Do not provide labels to continue partial fit." + ) self.layers[0] = self.layers[0].partial_fit( X[1], diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index 0e6fed2..d237b51 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -1,7 +1,7 @@ -"""Bartfai, G. +"""SMART. -(1994). Hierarchical clustering with ART neural networks. In Proc. IEEE International -Conference on Neural Networks (ICNN) (pp. 940–944). volume 2. +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. """ @@ -40,7 +40,8 @@ def __init__( 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. + 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 : diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index 3bccf35..d65dd2f 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -1,4 +1,6 @@ -"""Tan, A.-H. (2004). FALCON: a fusion architecture for learning, cognition, and +"""FALCON. + +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. @@ -19,11 +21,14 @@ 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. + 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. """ @@ -203,7 +208,8 @@ def get_action( 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". + Whether to choose the action with the minimum or maximum reward, + by default "max". Returns ------- diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index 5d06d65..5e2a3da 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -1,7 +1,8 @@ -"""Carpenter, G. +"""ARTMAP. -A., Grossberg, S., & Reynolds, J. H. (1991a). -ARTMAP: Supervised real-time learning and classification of nonstationary data by a self-organizing neural network. +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. """ @@ -17,14 +18,17 @@ class ARTMAP(SimpleARTMAP): 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. + 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. + 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. """ @@ -48,7 +52,8 @@ def get_params(self, deep: bool = True) -> dict: Parameters ---------- deep : bool, optional - If True, will return the parameters for this class and contained subobjects that are estimators. + If True, will return the parameters for this class and contained subobjects + that are estimators. Returns ------- @@ -285,7 +290,8 @@ def predict_ab(self, X: np.ndarray) -> tuple[np.ndarray, np.ndarray]: def predict_regression(self, X: np.ndarray) -> np.ndarray: """ Predict values for the given data using cluster centers. - Note: ARTMAP is not recommended for regression. Use FusionART for regression tasks. + Note: ARTMAP is not recommended for regression. + Use FusionART for regression tasks. Parameters ---------- diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index 0ce0b97..62c59c2 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -1,6 +1,6 @@ -"""Serrano-Gotarredona, T., Linares-Barranco, B., & Andreou, A. +"""Simple ARTMAP. -G. (1998). +Serrano-Gotarredona, T., Linares-Barranco, B., & Andreou, A. G. (1998). Adaptive Resonance Theory Microchips: Circuit Design Techniques. Norwell, MA, USA: Kluwer Academic Publishers. @@ -22,10 +22,11 @@ class SimpleARTMAP(BaseARTMAP): 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. + 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. """ @@ -84,7 +85,8 @@ 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. + If True, will return the parameters for this class and contained subobjects + that are estimators. Returns ------- @@ -337,7 +339,8 @@ def labels_ab(self) -> Dict[str, np.ndarray]: Returns ------- dict - A dictionary with keys "A" and "B" containing labels from sides A and B, respectively. + A dictionary with keys "A" and "B" containing labels from sides A and B, + respectively. """ return {"A": self.labels_a, "B": self.labels_} diff --git a/artlib/supervised/__init__.py b/artlib/supervised/__init__.py index 40b7d4c..ea54798 100644 --- a/artlib/supervised/__init__.py +++ b/artlib/supervised/__init__.py @@ -1,11 +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. +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. +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. `Supervised learning `_ diff --git a/artlib/topological/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index 998fef1..722126f 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -1,7 +1,7 @@ -"""Brito da Silva, L. +"""Dual Vigilance ART. -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. +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. """ import numpy as np @@ -42,12 +42,15 @@ def __init__(self, base_module: BaseART, rho_lower_bound: float): assert isinstance(base_module, BaseART) if hasattr(base_module, "base_module"): warn( - f"{base_module.__class__.__name__} is an abstraction of the BaseART class. " - f"This module will only make use of the base_module {base_module.base_module.__class__.__name__}" + f"{base_module.__class__.__name__} " + f"is an abstraction of the BaseART class. " + f"This module will only make use of the base_module: " + f"{base_module.base_module.__class__.__name__}" ) - assert ( - "rho" in base_module.params - ), "Dual Vigilance ART is only compatible with ART modules relying on 'rho' for vigilance." + assert "rho" in base_module.params, ( + "Dual Vigilance ART is only compatible with ART modules " + "relying on 'rho' for vigilance." + ) params = {"rho_lower_bound": rho_lower_bound} assert base_module.params["rho"] > params["rho_lower_bound"] >= 0 @@ -94,7 +97,8 @@ def get_params(self, deep: bool = True) -> dict: Parameters ---------- deep : bool, optional - If True, return the parameters for this class and contained subobjects that are estimators, by default True. + If True, return the parameters for this class and contained subobjects that + are estimators, by default True. Returns ------- @@ -157,11 +161,7 @@ def labels_(self, new_labels: np.ndarray): self.base_module.labels_ = new_labels @property - def W(self): - return self.base_module.W - - @W.setter - def W(self, new_W: list[np.ndarray]): + def W(self) -> List: """Get the weights from the base module. Returns @@ -170,6 +170,10 @@ def W(self, new_W: list[np.ndarray]): Weights of the clusters. """ + return self.base_module.W + + @W.setter + def W(self, new_W: list[np.ndarray]): self.base_module.W = new_W def check_dimensions(self, X: np.ndarray): @@ -204,7 +208,6 @@ def validate_params(self, params: dict): Dictionary containing parameters for the algorithm. """ - assert ( "rho_lower_bound" in params ), "Dual Vigilance ART requires a lower bound 'rho' value" @@ -275,7 +278,8 @@ def step_fit( 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. + 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+". @@ -423,5 +427,6 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): ) except NotImplementedError: warn( - f"{self.base_module.__class__.__name__} does not support plotting cluster bounds." + f"{self.base_module.__class__.__name__} " + f"does not support plotting cluster bounds." ) diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index 6630b9f..e74eb38 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -1,6 +1,6 @@ -"""Tscherepanow, M. +"""Topo ART. -(2010). +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). @@ -22,15 +22,18 @@ class TopoART(BaseART): """Topo ART for Topological 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. + + 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. """ @@ -52,8 +55,10 @@ def __init__(self, base_module: BaseART, beta_lower: float, tau: int, phi: int): assert isinstance(base_module, BaseART) if hasattr(base_module, "base_module"): warn( - f"{base_module.__class__.__name__} is an abstraction of the BaseART class. " - f"This module will only make use of the base_module {base_module.base_module.__class__.__name__}" + f"{base_module.__class__.__name__} " + f"is an abstraction of the BaseART class. " + f"This module will only make use of the base_module: " + f"{base_module.base_module.__class__.__name__}" ) params = dict( base_module.params, @@ -286,7 +291,6 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: Newly generated cluster weight. """ - return self.base_module.new_weight(i, params) def add_weight(self, new_w: np.ndarray): @@ -565,5 +569,6 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): ) except NotImplementedError: warn( - f"{self.base_module.__class__.__name__} does not support plotting cluster bounds." + f"{self.base_module.__class__.__name__} " + f"does not support plotting cluster bounds." ) From c9077b980f5b22c83069ec1e8836cfda86a2edc6 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 17 Oct 2024 01:18:30 -0500 Subject: [PATCH 091/139] resolve typing issues --- .pre-commit-config.yaml | 23 ++++---- artlib/common/BaseART.py | 73 ++++++++++++++++--------- artlib/common/BaseARTMAP.py | 9 ++- artlib/common/utils.py | 4 +- artlib/cvi/iCVIs/CalinkskiHarabasz.py | 7 ++- artlib/elementary/ART1.py | 4 +- artlib/elementary/ART2.py | 2 +- artlib/elementary/BayesianART.py | 16 +++--- artlib/elementary/EllipsoidART.py | 12 ++-- artlib/elementary/FuzzyART.py | 4 +- artlib/elementary/GaussianART.py | 4 +- artlib/elementary/HypersphereART.py | 2 +- artlib/elementary/QuadraticNeuronART.py | 5 +- artlib/experimental/ConvexHullART.py | 4 +- artlib/experimental/SeqART.py | 4 +- artlib/fusion/FusionART.py | 49 +++++++++-------- artlib/hierarchical/DeepARTMAP.py | 8 +-- artlib/hierarchical/SMART.py | 17 ++++-- artlib/supervised/ARTMAP.py | 16 ++++-- artlib/supervised/SimpleARTMAP.py | 19 +++++-- artlib/topological/DualVigilanceART.py | 16 ++++-- artlib/topological/TopoART.py | 16 ++++-- 22 files changed, 189 insertions(+), 125 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9f24e67..d52e41e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,17 +34,14 @@ repos: - id: flake8 args: [--max-line-length=88, '--ignore=D205,D400,D105,E731,W503,D401,E203,D209'] additional_dependencies: [flake8-docstrings] -# -# - repo: https://github.com/PyCQA/pydocstyle -# rev: 6.3.0 -# hooks: -# - id: pydocstyle -# args: [--convention=numpy] -# -# - repo: https://github.com/pre-commit/mirrors-mypy -# rev: v1.5.1 -# hooks: -# - id: mypy -# args: [--strict] -exclude: ^(unit_tests/|scripts/|artlib/experimental/|examples/|templates/|docs/) + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.5.1 + hooks: + - id: mypy + args: + - --ignore-missing-imports + - --exclude=artlib/cvi/ + - --follow-imports=silent + +exclude: ^(unit_tests/|scripts/|artlib/experimental/|examples/|templates/|docs/|artlib/cvi/) diff --git a/artlib/common/BaseART.py b/artlib/common/BaseART.py index ec0c628..be01fd7 100644 --- a/artlib/common/BaseART.py +++ b/artlib/common/BaseART.py @@ -1,20 +1,20 @@ """Base class for all ART objects.""" import numpy as np -from typing import Optional, Callable, Iterable, Literal, List +from typing import Optional, Callable, Literal, List, Tuple, Union, Dict from copy import deepcopy from collections import defaultdict from matplotlib.axes import Axes from warnings import warn from sklearn.base import BaseEstimator, ClusterMixin from sklearn.utils.validation import check_is_fitted -from artlib.common.utils import normalize, de_normalize +from artlib.common.utils import normalize, de_normalize, IndexableOrKeyable import operator class BaseART(BaseEstimator, ClusterMixin): """Generic implementation of Adaptive Resonance Theory (ART)""" - def __init__(self, params: dict): + def __init__(self, params: Dict): """ Parameters ---------- @@ -25,7 +25,7 @@ def __init__(self, params: dict): self.validate_params(params) self.params = params self.sample_counter_ = 0 - self.weight_sample_counter_: list[int] = [] + self.weight_sample_counter_: List[int] = [] self.d_min_ = None self.d_max_ = None @@ -46,7 +46,7 @@ def __setattr__(self, key, value): # Otherwise, proceed with normal attribute setting super().__setattr__(key, value) - def get_params(self, deep: bool = True) -> dict: + def get_params(self, deep: bool = True) -> Dict: """ Parameters ---------- @@ -88,7 +88,7 @@ def set_params(self, **params): for key, value in params.items(): key, delim, sub_key = key.partition("__") if key not in valid_params: - local_valid_params = list(valid_params.keys()) + local_valid_params = List(valid_params.keys()) raise ValueError( f"Invalid parameter {key!r} for estimator {self}. " f"Valid parameters are: {local_valid_params!r}." @@ -155,7 +155,7 @@ def n_clusters(self) -> int: return 0 @staticmethod - def validate_params(params: dict): + def validate_params(params: Dict): """Validate clustering parameters. Parameters @@ -192,8 +192,8 @@ def validate_data(self, X: np.ndarray): self.check_dimensions(X) def category_choice( - self, i: np.ndarray, w: np.ndarray, params: dict - ) -> tuple[float, Optional[dict]]: + self, i: np.ndarray, w: np.ndarray, params: Dict + ) -> Tuple[float, Optional[Dict]]: """Get the activation of the cluster. Parameters @@ -217,9 +217,9 @@ def match_criterion( self, i: np.ndarray, w: np.ndarray, - params: dict, - cache: Optional[dict] = None, - ) -> tuple[float, dict]: + params: Dict, + cache: Optional[Dict] = None, + ) -> Tuple[Union[float, List[float]], Optional[Dict]]: """Get the match criterion of the cluster. Parameters @@ -245,10 +245,10 @@ def match_criterion_bin( self, i: np.ndarray, w: np.ndarray, - params: dict, - cache: Optional[dict] = None, + params: Dict, + cache: Optional[Dict] = None, op: Callable = operator.ge, - ) -> tuple[bool, dict]: + ) -> Tuple[bool, Dict]: """Get the binary match criterion of the cluster. Parameters @@ -280,8 +280,8 @@ def update( self, i: np.ndarray, w: np.ndarray, - params: dict, - cache: Optional[dict] = None, + params: Dict, + cache: Optional[Dict] = None, ) -> np.ndarray: """Get the updated cluster weight. @@ -304,7 +304,7 @@ def update( """ raise NotImplementedError - def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: + def new_weight(self, i: np.ndarray, params: Dict) -> np.ndarray: """Generate a new cluster weight. Parameters @@ -350,11 +350,32 @@ def set_weight(self, idx: int, new_w: np.ndarray): def _match_tracking( self, - cache: dict, + cache: Union[List[Dict], Dict], epsilon: float, - params: dict, + params: Union[List[Dict], Dict], method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"], ) -> bool: + """Perform match tracking using the specified method. + + Parameters + ---------- + cache : dict + Cached match criterion value. + epsilon : float + Small adjustment factor for match tracking. + params : dict + Parameters + method : Literal["MT+", "MT-", "MT0", "MT1", "MT~"] + Match tracking method to apply. + + Returns + ------- + bool + Whether to continue searching for a match. + + """ + assert isinstance(cache, dict) + assert isinstance(params, dict) M = cache["match_criterion"] if method == "MT+": self.params["rho"] = M + epsilon @@ -387,7 +408,7 @@ def _match_tracking_operator( def _set_params(self, new_params): self.params = new_params - def _deep_copy_params(self) -> dict: + def _deep_copy_params(self) -> Dict: return deepcopy(self.params) def step_fit( @@ -561,7 +582,7 @@ def fit( self.check_dimensions(X) self.is_fitted_ = True - self.W: list[np.ndarray] = [] + self.W: List[np.ndarray] = [] self.labels_ = np.zeros((X.shape[0],), dtype=int) for _ in range(max_iter): if verbose: @@ -609,7 +630,7 @@ def partial_fit( self.is_fitted_ = True if not hasattr(self, "W"): - self.W: list[np.ndarray] = [] + self.W = [] self.labels_ = np.zeros((X.shape[0],), dtype=int) j = 0 else: @@ -666,7 +687,9 @@ def shrink_clusters(self, shrink_ratio: float = 0.1): """ return self - def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): + def plot_cluster_bounds( + self, ax: Axes, colors: IndexableOrKeyable, linewidth: int = 1 + ): """Undefined function for visualizing the bounds of each cluster. Parameters @@ -699,7 +722,7 @@ def visualize( ax: Optional[Axes] = None, marker_size: int = 10, linewidth: int = 1, - colors: Optional[Iterable] = None, + colors: Optional[IndexableOrKeyable] = None, ): """Visualize the clustering of the data. diff --git a/artlib/common/BaseARTMAP.py b/artlib/common/BaseARTMAP.py index ee29540..7969545 100644 --- a/artlib/common/BaseARTMAP.py +++ b/artlib/common/BaseARTMAP.py @@ -1,9 +1,10 @@ """Base class for all ARTMAP objects.""" import numpy as np -from typing import Union, Optional, Iterable, Literal +from typing import Union, Optional, Literal from collections import defaultdict from matplotlib.axes import Axes from sklearn.base import BaseEstimator, ClassifierMixin, ClusterMixin +from artlib.common.utils import IndexableOrKeyable class BaseARTMAP(BaseEstimator, ClassifierMixin, ClusterMixin): @@ -169,7 +170,9 @@ def predict_ab(self, X: np.ndarray) -> tuple[np.ndarray, np.ndarray]: """ raise NotImplementedError - def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): + def plot_cluster_bounds( + self, ax: Axes, colors: IndexableOrKeyable, linewidth: int = 1 + ): """Visualize the bounds of each cluster. Parameters @@ -191,7 +194,7 @@ def visualize( ax: Optional[Axes] = None, marker_size: int = 10, linewidth: int = 1, - colors: Optional[Iterable] = None, + colors: Optional[IndexableOrKeyable] = None, ): """Visualize the clustering of the data. diff --git a/artlib/common/utils.py b/artlib/common/utils.py index 82ef009..98a6a3c 100644 --- a/artlib/common/utils.py +++ b/artlib/common/utils.py @@ -1,6 +1,8 @@ """General utilities used throughout ARTLib.""" import numpy as np -from typing import Tuple, Optional +from typing import Tuple, Optional, Mapping, Sequence, Union, Any + +IndexableOrKeyable = Union[Mapping[Any, Any], Sequence[Any]] def normalize( diff --git a/artlib/cvi/iCVIs/CalinkskiHarabasz.py b/artlib/cvi/iCVIs/CalinkskiHarabasz.py index 277bafb..fb696ee 100644 --- a/artlib/cvi/iCVIs/CalinkskiHarabasz.py +++ b/artlib/cvi/iCVIs/CalinkskiHarabasz.py @@ -11,6 +11,7 @@ """ import numpy as np +from typing import Dict def delta_add_sample_to_average( @@ -97,11 +98,11 @@ def __init__(self, x: np.ndarray) -> None: self.dim = x.shape[0] # Dimension of the input data self.n_samples: int = 0 # number of samples encountered self.mu = np.array([]) # geometric mean of the data - self.CD = {} # Dict for each cluster label containing n,v,CP, and G + self.CD: Dict = {} # Dict for each cluster label containing n,v,CP, and G self.WGSS = 0 # within group sum of squares self.criterion_value = 0 # calcualted CH index - def add_sample(self, x: np.ndarray, label: int) -> dict: + def add_sample(self, x: np.ndarray, label: int) -> Dict: """Calculate the result of adding a new sample with a given label. Parameters @@ -126,7 +127,7 @@ def add_sample(self, x: np.ndarray, label: int) -> dict: self.mu, x, newP["n_samples"] ) - CD = {} + CD: Dict = {} newP["CD"] = CD SEP = [] # separation between each cluster and the mean of the data if label not in self.CD: diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index 217e18b..f14c42f 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -7,7 +7,7 @@ """ import numpy as np -from typing import Optional, List +from typing import Optional, List, Tuple, Union, Dict from artlib.common.BaseART import BaseART from artlib.common.utils import l1norm @@ -103,7 +103,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> tuple[float, dict]: + ) -> Tuple[Union[float, List[float]], Optional[Dict]]: """Get the match criterion of the cluster. Parameters diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index 1c44f7a..6c37b3b 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -126,7 +126,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> tuple[float, dict]: + ) -> tuple[float, Optional[dict]]: """Get the match criterion of the cluster. Parameters diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index 1e3210d..bc2ecd9 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -5,7 +5,7 @@ """ import numpy as np -from typing import Optional, Iterable, List, Callable, Literal +from typing import Optional, Iterable, List, Callable, Literal, Tuple, Union, Dict import operator from matplotlib.axes import Axes from artlib.common.BaseART import BaseART @@ -121,7 +121,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> tuple[float, dict]: + ) -> Tuple[Union[float, List[float]], Optional[Dict]]: """Get the match criterion of the cluster. Parameters @@ -146,11 +146,10 @@ def match_criterion( # the original paper uses the det(cov_old) for match criterion # however, it makes logical sense to use the new_cov and results are # improved when doing so + assert cache is not None new_w = self.update(i, w, params, cache) new_cov = new_w[self.dim_ : -1].reshape((self.dim_, self.dim_)) cache["new_w"] = new_w - # if cache is None: - # raise ValueError("No cache provided") # return cache["det_cov"] return np.linalg.det(new_cov), cache @@ -198,9 +197,9 @@ def match_criterion_bin( def _match_tracking( self, - cache: dict, + cache: Union[List[Dict], Dict], epsilon: float, - params: dict, + params: Union[List[Dict], Dict], method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"], ) -> bool: """Adjust match tracking based on the method and epsilon value. @@ -222,6 +221,8 @@ def _match_tracking( True if match tracking continues, False otherwise. """ + assert isinstance(cache, dict) + assert isinstance(params, dict) M = cache["match_criterion"] # we have to reverse some signs because bayesianART has an inverted # vigilence check @@ -268,8 +269,7 @@ def update( Updated cluster weight. """ - if cache is None: - raise ValueError("No cache provided") + assert cache is not None if "new_w" in cache: return cache["new_w"] diff --git a/artlib/elementary/EllipsoidART.py b/artlib/elementary/EllipsoidART.py index 58ab48d..1425c5a 100644 --- a/artlib/elementary/EllipsoidART.py +++ b/artlib/elementary/EllipsoidART.py @@ -11,10 +11,10 @@ """ import numpy as np -from typing import Optional, Iterable, List +from typing import Optional, List, Tuple, Union, Dict from matplotlib.axes import Axes from artlib.common.BaseART import BaseART -from artlib.common.utils import l2norm2 +from artlib.common.utils import l2norm2, IndexableOrKeyable class EllipsoidART(BaseART): @@ -157,7 +157,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> tuple[float, dict]: + ) -> Tuple[Union[float, List[float]], Optional[Dict]]: """Get the match criterion of the cluster. Parameters @@ -250,7 +250,7 @@ def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: """ return np.concatenate([i, np.zeros_like(i), [0.0]]) - def get_2d_ellipsoids(self) -> list[tuple]: + def get_2d_ellipsoids(self) -> List[Tuple[np.ndarray, float, float, float]]: """Get the 2D ellipsoids for visualization. Returns @@ -284,7 +284,9 @@ def get_cluster_centers(self) -> List[np.ndarray]: """ return [w[: self.dim_] for w in self.W] - def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): + def plot_cluster_bounds( + self, ax: Axes, colors: IndexableOrKeyable, linewidth: int = 1 + ): """Visualize the bounds of each cluster. Parameters diff --git a/artlib/elementary/FuzzyART.py b/artlib/elementary/FuzzyART.py index 55a440d..fd849d0 100644 --- a/artlib/elementary/FuzzyART.py +++ b/artlib/elementary/FuzzyART.py @@ -7,7 +7,7 @@ """ import numpy as np -from typing import Optional, Iterable, List +from typing import Optional, Iterable, List, Tuple, Union, Dict from matplotlib.axes import Axes from artlib.common.BaseART import BaseART from artlib.common.utils import ( @@ -205,7 +205,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> tuple[float, dict]: + ) -> Tuple[Union[float, List[float]], Optional[Dict]]: """Get the match criterion of the cluster. Parameters diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index e233fc3..11f76c2 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -8,7 +8,7 @@ """ import numpy as np -from typing import Optional, Iterable, List +from typing import Optional, Iterable, List, Tuple, Union, Dict from matplotlib.axes import Axes from artlib.common.BaseART import BaseART from artlib.common.visualization import plot_gaussian_contours_fading @@ -109,7 +109,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> tuple[float, dict]: + ) -> Tuple[Union[float, List[float]], Optional[Dict]]: """Get the match criterion of the cluster. Parameters diff --git a/artlib/elementary/HypersphereART.py b/artlib/elementary/HypersphereART.py index 79b7e1f..38b6e76 100644 --- a/artlib/elementary/HypersphereART.py +++ b/artlib/elementary/HypersphereART.py @@ -136,7 +136,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> tuple[float, dict]: + ) -> tuple[float, Optional[dict]]: """Get the match criterion of the cluster. Parameters diff --git a/artlib/elementary/QuadraticNeuronART.py b/artlib/elementary/QuadraticNeuronART.py index 1cffe9d..a492c42 100644 --- a/artlib/elementary/QuadraticNeuronART.py +++ b/artlib/elementary/QuadraticNeuronART.py @@ -9,7 +9,7 @@ """ import numpy as np -from typing import Optional, Iterable, List +from typing import Optional, Iterable, List, Tuple, Union, Dict from matplotlib.axes import Axes from artlib.common.BaseART import BaseART from artlib.common.utils import l2norm2 @@ -126,7 +126,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> tuple[float, dict]: + ) -> Tuple[Union[float, List[float]], Optional[Dict]]: """Get the match criterion of the cluster. Parameters @@ -178,6 +178,7 @@ def update( Updated cluster weight, cache used for later processing. """ + assert cache is not None s = cache["s"] w_ = cache["w"] b = cache["b"] diff --git a/artlib/experimental/ConvexHullART.py b/artlib/experimental/ConvexHullART.py index f418487..0929134 100644 --- a/artlib/experimental/ConvexHullART.py +++ b/artlib/experimental/ConvexHullART.py @@ -1,7 +1,7 @@ import numpy as np from matplotlib.axes import Axes from copy import deepcopy -from typing import Optional, Iterable, List, Union +from typing import Optional, Iterable, List, Tuple, Union, Dict from scipy.spatial import ConvexHull from artlib.common.BaseART import BaseART @@ -271,7 +271,7 @@ def match_criterion( w: HullTypes, params: dict, cache: Optional[dict] = None, - ) -> tuple[float, dict]: + ) -> Tuple[Union[float, List[float]], Optional[Dict]]: """ Get the match criterion of the cluster. diff --git a/artlib/experimental/SeqART.py b/artlib/experimental/SeqART.py index 8d1a66a..7621f60 100644 --- a/artlib/experimental/SeqART.py +++ b/artlib/experimental/SeqART.py @@ -1,5 +1,5 @@ import numpy as np -from typing import Optional, Callable, Tuple +from typing import Optional, Callable, Tuple, Union, Dict from artlib import BaseART import operator import re @@ -234,7 +234,7 @@ def category_choice( def match_criterion( self, i: str, w: str, params: dict, cache: Optional[dict] = None - ) -> tuple[float, dict]: + ) -> Tuple[Union[float, List[float]], Optional[Dict]]: """ Get the match criterion of the cluster. diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index dc37fbc..f5f3bc2 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -9,7 +9,7 @@ """ import numpy as np -from typing import Optional, Union, Callable, List, Literal +from typing import Optional, Union, Callable, List, Literal, Tuple, Dict from copy import deepcopy from artlib.common.BaseART import BaseART from sklearn.utils.validation import check_is_fitted @@ -17,8 +17,8 @@ def get_channel_position_tuples( - channel_dims: list[int], -) -> list[tuple[int, int]]: + channel_dims: List[int], +) -> List[Tuple[int, int]]: """Generate the start and end positions for each channel in the input data. Parameters @@ -89,7 +89,7 @@ def __init__( self._channel_indices = get_channel_position_tuples(self.channel_dims) self.dim_ = sum(channel_dims) - def get_params(self, deep: bool = True) -> dict: + def get_params(self, deep: bool = True) -> Dict: """Get the parameters of the FusionART model. Parameters @@ -158,7 +158,7 @@ def W(self, new_W): self.modules[k].W = [] @staticmethod - def validate_params(params: dict): + def validate_params(params: Dict): """Validate clustering parameters. Parameters @@ -241,9 +241,9 @@ def category_choice( self, i: np.ndarray, w: np.ndarray, - params: dict, + params: Dict, skip_channels: List[int] = [], - ) -> tuple[float, Optional[dict]]: + ) -> Tuple[float, Optional[Dict]]: """Get the activation of the cluster. Parameters @@ -285,10 +285,10 @@ def match_criterion( self, i: np.ndarray, w: np.ndarray, - params: dict, - cache: Optional[dict] = None, + params: Dict, + cache: Optional[Dict] = None, skip_channels: List[int] = [], - ) -> tuple[list[float], dict]: + ) -> Tuple[Union[float, List[float]], Optional[Dict]]: """Get the match criterion for the cluster. Parameters @@ -332,11 +332,11 @@ def match_criterion_bin( self, i: np.ndarray, w: np.ndarray, - params: dict, - cache: Optional[dict] = None, - skip_channels: List[int] = [], + params: Dict, + cache: Optional[Dict] = None, op: Callable = operator.ge, - ) -> tuple[bool, dict]: + skip_channels: List[int] = [], + ) -> Tuple[bool, Dict]: """Get the binary match criterion for the cluster. Parameters @@ -349,10 +349,10 @@ def match_criterion_bin( 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). + skip_channels : list of int, optional + Channels to be skipped (default is []). Returns ------- @@ -381,9 +381,9 @@ def match_criterion_bin( def _match_tracking( self, - cache: List[dict], + cache: Union[List[Dict], Dict], epsilon: float, - params: List[dict], + params: Union[List[Dict], Dict], method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"], ) -> bool: """Perform match tracking for all channels using the specified method. @@ -416,7 +416,7 @@ def _match_tracking( keep_searching.append(True) return all(keep_searching) - def _set_params(self, new_params: List[dict]): + def _set_params(self, new_params: List[Dict]): """Set the parameters for each module in FusionART. Parameters @@ -428,7 +428,7 @@ def _set_params(self, new_params: List[dict]): for i in range(self.n): self.modules[i].params = new_params[i] - def _deep_copy_params(self) -> dict: + def _deep_copy_params(self) -> Dict: """Create a deep copy of the parameters for each module. Returns @@ -466,7 +466,7 @@ def partial_fit( self.is_fitted_ = True if not hasattr(self.modules[0], "W"): - self.W: list[np.ndarray] = [] + self.W: List[np.ndarray] = [] self.labels_ = np.zeros((X.shape[0],), dtype=int) j = 0 else: @@ -541,8 +541,8 @@ def update( self, i: np.ndarray, w: np.ndarray, - params: dict, - cache: Optional[dict] = None, + params: Dict, + cache: Optional[Dict] = None, ) -> np.ndarray: """Update the cluster weight. @@ -563,6 +563,7 @@ def update( Updated cluster weight. """ + assert cache is not None W = [ self.modules[k].update( i[self._channel_indices[k][0] : self._channel_indices[k][1]], @@ -574,7 +575,7 @@ def update( ] return np.concatenate(W) - def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: + def new_weight(self, i: np.ndarray, params: Dict) -> np.ndarray: """Generate a new cluster weight. Parameters diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index ae70bad..94c6d25 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -218,8 +218,8 @@ def validate_data(self, X: list[np.ndarray], y: Optional[np.ndarray] = None): ), "Inconsistent sample number in input matrices" def prepare_data( - self, X: list[np.ndarray], y: Optional[np.ndarray] = None - ) -> Tuple[list[np.ndarray], Optional[np.ndarray]]: + self, X: Union[np.ndarray, list[np.ndarray]], y: Optional[np.ndarray] = None + ) -> Union[np.ndarray, Tuple[list[np.ndarray], Optional[np.ndarray]]]: """Prepare the data for clustering. Parameters @@ -238,8 +238,8 @@ def prepare_data( 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]]: + self, X: Union[np.ndarray, list[np.ndarray]], y: Optional[np.ndarray] = None + ) -> Union[np.ndarray, Tuple[list[np.ndarray], Optional[np.ndarray]]]: """Restore the data to its original state before preparation. Parameters diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index d237b51..718d155 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -7,9 +7,10 @@ """ import numpy as np -from typing import Union, Type, Optional, Iterable, Literal +from typing import Union, Type, Optional, Literal, Tuple from matplotlib.axes import Axes from artlib.common.BaseART import BaseART +from artlib.common.utils import IndexableOrKeyable from artlib.hierarchical.DeepARTMAP import DeepARTMAP @@ -66,7 +67,9 @@ def __init__( ), "Only elementary ART-like objects are supported" super().__init__(modules) - def prepare_data(self, X: np.ndarray) -> np.ndarray: + def prepare_data( + self, X: Union[np.ndarray, list[np.ndarray]], y: Optional[np.ndarray] = None + ) -> Union[np.ndarray, Tuple[list[np.ndarray], Optional[np.ndarray]]]: """Prepare data for clustering. Parameters @@ -83,7 +86,9 @@ def prepare_data(self, X: np.ndarray) -> np.ndarray: X_, _ = super(SMART, self).prepare_data([X] * self.n_modules) return X_[0] - def restore_data(self, X: np.ndarray) -> np.ndarray: + def restore_data( + self, X: Union[np.ndarray, list[np.ndarray]], y: Optional[np.ndarray] = None + ) -> Union[np.ndarray, Tuple[list[np.ndarray], Optional[np.ndarray]]]: """Restore data to its original form before preparation. Parameters @@ -168,7 +173,9 @@ def partial_fit( X_list, match_reset_method=match_reset_method, epsilon=epsilon ) - def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): + def plot_cluster_bounds( + self, ax: Axes, colors: IndexableOrKeyable, linewidth: int = 1 + ): """Visualize the cluster boundaries. Parameters @@ -201,7 +208,7 @@ def visualize( ax: Optional[Axes] = None, marker_size: int = 10, linewidth: int = 1, - colors: Optional[Iterable] = None, + colors: Optional[IndexableOrKeyable] = None, ): """Visualize the clustering of the data with cluster boundaries. diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index 5e2a3da..3f98737 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -7,7 +7,7 @@ """ import numpy as np -from typing import Literal, Tuple, Dict +from typing import Literal, Tuple, Dict, Union, Optional from artlib.common.BaseART import BaseART from artlib.supervised.SimpleARTMAP import SimpleARTMAP from sklearn.utils.validation import check_is_fitted @@ -125,8 +125,8 @@ def validate_data(self, X: np.ndarray, y: np.ndarray): self.module_b.validate_data(y) def prepare_data( - self, X: np.ndarray, y: np.ndarray - ) -> Tuple[np.ndarray, np.ndarray]: + self, X: np.ndarray, y: Optional[np.ndarray] = None + ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]: """Prepare data for clustering by normalizing and transforming. Parameters @@ -142,11 +142,12 @@ def prepare_data( Normalized data for both channels. """ + assert y is not None 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]: + self, X: np.ndarray, y: Optional[np.ndarray] = None + ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]: """Restore data to its original state before preparation. Parameters @@ -162,6 +163,7 @@ def restore_data( Restored data for both channels. """ + assert y is not None return self.module_a.restore_data(X), self.module_b.restore_data(y) def fit( @@ -171,6 +173,7 @@ def fit( max_iter=1, match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10, + verbose: bool = False, ): """Fit the ARTMAP model to the data. @@ -186,6 +189,8 @@ def fit( Method for resetting the vigilance parameter when match criterion fails. epsilon : float, optional Small increment to modify the vigilance parameter. + verbose : bool, default=False + If True, displays a progress bar during training. Returns ------- @@ -211,6 +216,7 @@ def fit( max_iter=max_iter, match_reset_method=match_reset_method, epsilon=epsilon, + verbose=verbose, ) return self diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index 62c59c2..3e5cd2a 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -6,10 +6,11 @@ """ import numpy as np -from typing import Optional, Iterable, Literal, Dict +from typing import Optional, Literal, Dict, Union, Tuple from matplotlib.axes import Axes from artlib.common.BaseART import BaseART from artlib.common.BaseARTMAP import BaseARTMAP +from artlib.common.utils import IndexableOrKeyable from sklearn.utils.validation import check_is_fitted, check_X_y from sklearn.utils.multiclass import unique_labels @@ -122,13 +123,17 @@ def validate_data( self.module_a.validate_data(X) return X, y - def prepare_data(self, X: np.ndarray) -> np.ndarray: + def prepare_data( + self, X: np.ndarray, y: Optional[np.ndarray] = None + ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]: """Prepare data for clustering. Parameters ---------- X : np.ndarray Data set. + y : Optional[np.ndarray] + Data set B. Not used in SimpleARTMAP Returns ------- @@ -138,7 +143,9 @@ def prepare_data(self, X: np.ndarray) -> np.ndarray: """ return self.module_a.prepare_data(X) - def restore_data(self, X: np.ndarray) -> np.ndarray: + def restore_data( + self, X: np.ndarray, y: Optional[np.ndarray] = None + ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]: """Restore data to state prior to preparation. Parameters @@ -443,7 +450,9 @@ def predict_ab(self, X: np.ndarray) -> tuple[np.ndarray, np.ndarray]: y_b[i] = c_b return y_a, y_b - def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): + def plot_cluster_bounds( + self, ax: Axes, colors: IndexableOrKeyable, linewidth: int = 1 + ): """Visualize the cluster boundaries. Parameters @@ -468,7 +477,7 @@ def visualize( ax: Optional[Axes] = None, marker_size: int = 10, linewidth: int = 1, - colors: Optional[Iterable] = None, + colors: Optional[IndexableOrKeyable] = None, ): """Visualize the clustering of the data. diff --git a/artlib/topological/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index 722126f..86a473c 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -5,11 +5,12 @@ """ import numpy as np -from typing import Optional, Callable, Iterable, List, Literal +from typing import Optional, Callable, List, Literal, Union, Dict from warnings import warn from copy import deepcopy from matplotlib.axes import Axes from artlib.common.BaseART import BaseART +from artlib.common.utils import IndexableOrKeyable class DualVigilanceART(BaseART): @@ -199,7 +200,8 @@ def validate_data(self, X: np.ndarray): self.base_module.validate_data(X) self.check_dimensions(X) - def validate_params(self, params: dict): + @staticmethod + def validate_params(params: dict): """Validate clustering parameters. Parameters @@ -216,9 +218,9 @@ def validate_params(self, params: dict): def _match_tracking( self, - cache: dict, + cache: Union[List[Dict], Dict], epsilon: float, - params: dict, + params: Union[List[Dict], Dict], method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"], ) -> bool: """Adjust match tracking based on the method and epsilon value. @@ -240,6 +242,8 @@ def _match_tracking( True if match tracking continues, False otherwise. """ + assert isinstance(cache, dict) + assert isinstance(params, dict) M = cache["match_criterion"] if method == "MT+": self.base_module.params["rho"] = M + epsilon @@ -404,7 +408,9 @@ def get_cluster_centers(self) -> List[np.ndarray]: """ return self.base_module.get_cluster_centers() - def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): + def plot_cluster_bounds( + self, ax: Axes, colors: IndexableOrKeyable, linewidth: int = 1 + ): """Visualize the bounds of each cluster. Parameters diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index e74eb38..02ce25a 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -10,11 +10,12 @@ """ import numpy as np -from typing import Optional, Callable, Iterable, List, Literal +from typing import Optional, Callable, List, Literal, Tuple, Union, Dict from matplotlib.axes import Axes from warnings import warn from copy import deepcopy from artlib.common.BaseART import BaseART +from artlib.common.utils import IndexableOrKeyable import operator @@ -192,7 +193,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> tuple[float, dict]: + ) -> Tuple[Union[float, List[float]], Optional[Dict]]: """Get the match criterion of the cluster. Parameters @@ -271,6 +272,7 @@ def update( Updated cluster weight. """ + assert cache is not None 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) @@ -373,9 +375,9 @@ def post_step_fit(self, X: np.ndarray): def _match_tracking( self, - cache: dict, + cache: Union[List[Dict], Dict], epsilon: float, - params: dict, + params: Union[List[Dict], Dict], method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"], ) -> bool: """Adjust the vigilance parameter based on match tracking methods. @@ -397,6 +399,8 @@ def _match_tracking( True if the match tracking continues, False otherwise. """ + assert isinstance(cache, dict) + assert isinstance(params, dict) M = cache["match_criterion"] if method == "MT+": self.base_module.params["rho"] = M + epsilon @@ -550,7 +554,9 @@ def get_cluster_centers(self) -> List[np.ndarray]: """ return self.base_module.get_cluster_centers() - def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): + def plot_cluster_bounds( + self, ax: Axes, colors: IndexableOrKeyable, linewidth: int = 1 + ): """Visualize the boundaries of each cluster. Parameters From 934468a75e762d473f8239d0383f021bb89e90a9 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 17 Oct 2024 01:22:25 -0500 Subject: [PATCH 092/139] restore docs --- docs/source/artlib.biclustering.rst | 2 + docs/source/artlib.common.rst | 1 + docs/source/artlib.cvi.iCVIs.rst | 2 + docs/source/artlib.cvi.rst | 2 + docs/source/artlib.elementary.rst | 2 + docs/source/artlib.experimental.rst | 2 + docs/source/artlib.fusion.rst | 2 + docs/source/artlib.hierarchical.rst | 2 + docs/source/artlib.reinforcement.rst | 2 + docs/source/artlib.rst | 2 + docs/source/artlib.supervised.rst | 2 + docs/source/artlib.topological.rst | 2 + docs/source/available_models.rst | 1 + docs/source/comparison.rst | 1 + docs/source/conf.py | 58 ++++++++++++++-------------- docs/source/contact.rst | 1 + docs/source/contributing.rst | 1 + docs/source/examples.rst | 1 + docs/source/index.rst | 2 +- docs/source/installation.rst | 1 + docs/source/license.rst | 1 + docs/source/quick_start.rst | 1 + 22 files changed, 61 insertions(+), 30 deletions(-) diff --git a/docs/source/artlib.biclustering.rst b/docs/source/artlib.biclustering.rst index a65b925..38d7512 100644 --- a/docs/source/artlib.biclustering.rst +++ b/docs/source/artlib.biclustering.rst @@ -19,3 +19,5 @@ artlib.biclustering.BARTMAP module :members: :undoc-members: :show-inheritance: + + diff --git a/docs/source/artlib.common.rst b/docs/source/artlib.common.rst index 13cce7c..77a39ca 100644 --- a/docs/source/artlib.common.rst +++ b/docs/source/artlib.common.rst @@ -51,3 +51,4 @@ artlib.common.visualization module :members: :undoc-members: :show-inheritance: + diff --git a/docs/source/artlib.cvi.iCVIs.rst b/docs/source/artlib.cvi.iCVIs.rst index 4283789..023924e 100644 --- a/docs/source/artlib.cvi.iCVIs.rst +++ b/docs/source/artlib.cvi.iCVIs.rst @@ -19,3 +19,5 @@ artlib.cvi.iCVIs.CalinkskiHarabasz module :members: :undoc-members: :show-inheritance: + + diff --git a/docs/source/artlib.cvi.rst b/docs/source/artlib.cvi.rst index a79c0d7..8b22857 100644 --- a/docs/source/artlib.cvi.rst +++ b/docs/source/artlib.cvi.rst @@ -35,3 +35,5 @@ artlib.cvi.iCVIFuzzyArt module :members: :undoc-members: :show-inheritance: + + diff --git a/docs/source/artlib.elementary.rst b/docs/source/artlib.elementary.rst index 03f6a18..87ec705 100644 --- a/docs/source/artlib.elementary.rst +++ b/docs/source/artlib.elementary.rst @@ -75,3 +75,5 @@ artlib.elementary.QuadraticNeuronART module :members: :undoc-members: :show-inheritance: + + diff --git a/docs/source/artlib.experimental.rst b/docs/source/artlib.experimental.rst index 760180f..3e991ae 100644 --- a/docs/source/artlib.experimental.rst +++ b/docs/source/artlib.experimental.rst @@ -35,3 +35,5 @@ artlib.experimental.merging module :members: :undoc-members: :show-inheritance: + + diff --git a/docs/source/artlib.fusion.rst b/docs/source/artlib.fusion.rst index 08eb2af..354cb6f 100644 --- a/docs/source/artlib.fusion.rst +++ b/docs/source/artlib.fusion.rst @@ -19,3 +19,5 @@ artlib.fusion.FusionART module :members: :undoc-members: :show-inheritance: + + diff --git a/docs/source/artlib.hierarchical.rst b/docs/source/artlib.hierarchical.rst index cd953fe..1eb9acd 100644 --- a/docs/source/artlib.hierarchical.rst +++ b/docs/source/artlib.hierarchical.rst @@ -27,3 +27,5 @@ artlib.hierarchical.SMART module :members: :undoc-members: :show-inheritance: + + diff --git a/docs/source/artlib.reinforcement.rst b/docs/source/artlib.reinforcement.rst index e68ca15..c9aaa86 100644 --- a/docs/source/artlib.reinforcement.rst +++ b/docs/source/artlib.reinforcement.rst @@ -19,3 +19,5 @@ artlib.reinforcement.FALCON module :members: :undoc-members: :show-inheritance: + + diff --git a/docs/source/artlib.rst b/docs/source/artlib.rst index 83c0402..8a27f50 100644 --- a/docs/source/artlib.rst +++ b/docs/source/artlib.rst @@ -25,3 +25,5 @@ Subpackages artlib.reinforcement artlib.supervised artlib.topological + + diff --git a/docs/source/artlib.supervised.rst b/docs/source/artlib.supervised.rst index d14e899..882670e 100644 --- a/docs/source/artlib.supervised.rst +++ b/docs/source/artlib.supervised.rst @@ -27,3 +27,5 @@ artlib.supervised.SimpleARTMAP module :members: :undoc-members: :show-inheritance: + + diff --git a/docs/source/artlib.topological.rst b/docs/source/artlib.topological.rst index e471d45..f10b26a 100644 --- a/docs/source/artlib.topological.rst +++ b/docs/source/artlib.topological.rst @@ -27,3 +27,5 @@ artlib.topological.TopoART module :members: :undoc-members: :show-inheritance: + + diff --git a/docs/source/available_models.rst b/docs/source/available_models.rst index 0803940..fcc20cf 100644 --- a/docs/source/available_models.rst +++ b/docs/source/available_models.rst @@ -2,3 +2,4 @@ :parser: myst_parser.sphinx_ :start-after: :end-before: + diff --git a/docs/source/comparison.rst b/docs/source/comparison.rst index c128375..3765d81 100644 --- a/docs/source/comparison.rst +++ b/docs/source/comparison.rst @@ -2,3 +2,4 @@ :parser: myst_parser.sphinx_ :start-after: :end-before: + diff --git a/docs/source/conf.py b/docs/source/conf.py index 89f8d81..1335c8b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,52 +6,52 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = "AdaptiveResonanceLib" -copyright = "2024, Niklas Melton" -author = "Niklas Melton" -release = "0.1.2" +project = 'AdaptiveResonanceLib' +copyright = '2024, Niklas Melton' +author = 'Niklas Melton' +release = '0.1.2' # -- 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", - "sphinxcontrib.bibtex", + 'sphinx.ext.autodoc', + 'autoapi.extension', + 'sphinx.ext.napoleon', + 'myst_parser', + 'sphinx.ext.intersphinx', + 'sphinxcontrib.bibtex', ] source_suffix = { - ".rst": "restructuredtext", - ".md": "markdown", + '.rst': 'restructuredtext', + '.md': 'markdown', } -templates_path = ["_templates"] -exclude_patterns = ["artlib/experimental/*", "../../artlib/experimental/*"] +templates_path = ['_templates'] +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/*"] -autoapi_python_class_content = "both" +autoapi_type = 'python' +autoapi_dirs = ['../../artlib'] # Adjust this to point to your source code directory +autoapi_ignore = ['*/experimental', '*/experimental/*'] +autoapi_python_class_content = 'both' -bibtex_bibfiles = ["references.bib"] +bibtex_bibfiles = ['references.bib'] intersphinx_mapping = { - "python": ("https://docs.python.org/3", None), - "sklearn": ("https://scikit-learn.org/stable/", None), + 'python': ('https://docs.python.org/3', None), + 'sklearn': ('https://scikit-learn.org/stable/', None) } -suppress_warnings = [ - "ref.duplicate", - "duplicate.object", - "myst.duplicate_def", - "ref.python", -] +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 -html_theme = "sphinx_rtd_theme" -html_static_path = ["../_static"] +html_theme = 'sphinx_rtd_theme' +html_static_path = ['../_static'] + + + + + diff --git a/docs/source/contact.rst b/docs/source/contact.rst index 348e1f5..0fc9e70 100644 --- a/docs/source/contact.rst +++ b/docs/source/contact.rst @@ -2,3 +2,4 @@ :parser: myst_parser.sphinx_ :start-after: :end-before: + diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 4c0d940..38baf90 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -2,3 +2,4 @@ :parser: myst_parser.sphinx_ :start-after: :end-before: + diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 270cc6a..5983c5b 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -2,3 +2,4 @@ :parser: myst_parser.sphinx_ :start-after: :end-before: + diff --git a/docs/source/index.rst b/docs/source/index.rst index e740fae..0fabef5 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -37,4 +37,4 @@ AdaptiveResonanceLib Home artlib.hierarchical artlib.reinforcement artlib.supervised - artlib.topological + artlib.topological \ No newline at end of file diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 339f40d..5a58eb8 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -2,3 +2,4 @@ :parser: myst_parser.sphinx_ :start-after: :end-before: + diff --git a/docs/source/license.rst b/docs/source/license.rst index 51661c7..d8aa21e 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -3,3 +3,4 @@ License .. include:: ../../LICENSE :parser: myst_parser.sphinx_ + diff --git a/docs/source/quick_start.rst b/docs/source/quick_start.rst index 5b6db9c..d9f1602 100644 --- a/docs/source/quick_start.rst +++ b/docs/source/quick_start.rst @@ -2,3 +2,4 @@ :parser: myst_parser.sphinx_ :start-after: :end-before: + From e8d93006a99e09cf3be872515749fa13b56d4d77 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 17 Oct 2024 02:13:03 -0500 Subject: [PATCH 093/139] autoclass_content = 'both' --- docs/source/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 1335c8b..9ea18b3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -35,6 +35,7 @@ autoapi_dirs = ['../../artlib'] # Adjust this to point to your source code directory autoapi_ignore = ['*/experimental', '*/experimental/*'] autoapi_python_class_content = 'both' +autoclass_content = 'both' bibtex_bibfiles = ['references.bib'] From deb48a2b0723ba5d38de4a341e4ea7de0eb43a46 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 17 Oct 2024 02:16:31 -0500 Subject: [PATCH 094/139] autoclass_content = 'both' --- docs/source/conf.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 9ea18b3..a029d8b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,6 +6,14 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +def skip(app, what, name, obj, would_skip, options): + if name == "__init__": + return False + return would_skip + +def setup(app): + app.connect("autodoc-skip-member", skip) + project = 'AdaptiveResonanceLib' copyright = '2024, Niklas Melton' author = 'Niklas Melton' From 19139fb0e2b93183087a8047ce05ca85ca76978a Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 17 Oct 2024 02:39:32 -0500 Subject: [PATCH 095/139] autoclass_content = 'both' --- docs/source/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index a029d8b..d69d5b3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -42,8 +42,8 @@ def setup(app): autoapi_type = 'python' autoapi_dirs = ['../../artlib'] # Adjust this to point to your source code directory autoapi_ignore = ['*/experimental', '*/experimental/*'] -autoapi_python_class_content = 'both' -autoclass_content = 'both' +# autoapi_python_class_content = 'both' +# autoclass_content = 'both' bibtex_bibfiles = ['references.bib'] From cde241a9f748a4700315d308b1cbf0e5b1a0df5e Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 12:14:57 -0500 Subject: [PATCH 096/139] match_reset_method -> match_tracking --- artlib/common/BaseART.py | 24 ++++++++++++------------ artlib/common/BaseARTMAP.py | 8 ++++---- artlib/cvi/CVIART.py | 10 +++++----- artlib/cvi/iCVIFuzzyArt.py | 8 ++++---- artlib/fusion/FusionART.py | 6 +++--- artlib/hierarchical/DeepARTMAP.py | 20 ++++++++++---------- artlib/hierarchical/SMART.py | 12 ++++++------ artlib/supervised/ARTMAP.py | 16 ++++++++-------- artlib/supervised/SimpleARTMAP.py | 18 +++++++++--------- artlib/topological/DualVigilanceART.py | 8 ++++---- artlib/topological/TopoART.py | 8 ++++---- 11 files changed, 69 insertions(+), 69 deletions(-) diff --git a/artlib/common/BaseART.py b/artlib/common/BaseART.py index be01fd7..6ab8255 100644 --- a/artlib/common/BaseART.py +++ b/artlib/common/BaseART.py @@ -415,7 +415,7 @@ def step_fit( self, x: np.ndarray, match_reset_func: Optional[Callable] = None, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ) -> int: """Fit the model to a single sample. @@ -426,7 +426,7 @@ def step_fit( Data sample. match_reset_func : callable, optional A callable that influences cluster creation. - match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, default="MT+" + match_tracking : {"MT+", "MT-", "MT0", "MT1", "MT~"}, default="MT+" Method for resetting match criterion. epsilon : float, default=0.0 Epsilon value used for adjusting match criterion. @@ -439,13 +439,13 @@ def step_fit( """ self.sample_counter_ += 1 base_params = self._deep_copy_params() - mt_operator = self._match_tracking_operator(match_reset_method) + mt_operator = self._match_tracking_operator(match_tracking) if len(self.W) == 0: w_new = self.new_weight(x, self.params) self.add_weight(w_new) return 0 else: - if match_reset_method in ["MT~"] and match_reset_func is not None: + if match_tracking in ["MT~"] and match_reset_func is not None: T_values, T_cache = zip( *[ self.category_choice(x, w, params=self.params) @@ -466,7 +466,7 @@ def step_fit( m, cache = self.match_criterion_bin( x, w, params=self.params, cache=cache, op=mt_operator ) - if match_reset_method in ["MT~"] and match_reset_func is not None: + if match_tracking in ["MT~"] and match_reset_func is not None: no_match_reset = True else: no_match_reset = match_reset_func is None or match_reset_func( @@ -480,7 +480,7 @@ def step_fit( T[c_] = np.nan if m and not no_match_reset: keep_searching = self._match_tracking( - cache, epsilon, self.params, match_reset_method + cache, epsilon, self.params, match_tracking ) if not keep_searching: T[:] = np.nan @@ -554,7 +554,7 @@ def fit( y: Optional[np.ndarray] = None, match_reset_func: Optional[Callable] = None, max_iter=1, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, verbose: bool = False, ): @@ -570,7 +570,7 @@ def fit( 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+" + match_tracking : {"MT+", "MT-", "MT0", "MT1", "MT~"}, default="MT+" Method for resetting match criterion. epsilon : float, default=0.0 Epsilon value used for adjusting match criterion. @@ -596,7 +596,7 @@ def fit( c = self.step_fit( x, match_reset_func=match_reset_func, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) self.labels_[i] = c @@ -608,7 +608,7 @@ def partial_fit( self, X: np.ndarray, match_reset_func: Optional[Callable] = None, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ): """Iteratively fit the model to the data. @@ -619,7 +619,7 @@ def partial_fit( The dataset. match_reset_func : callable, optional A callable that influences cluster creation. - match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, default="MT+" + match_tracking : {"MT+", "MT-", "MT0", "MT1", "MT~"}, default="MT+" Method for resetting match criterion. epsilon : float, default=0.0 Epsilon value used for adjusting match criterion. @@ -640,7 +640,7 @@ def partial_fit( c = self.step_fit( x, match_reset_func=match_reset_func, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) self.labels_[i + j] = c diff --git a/artlib/common/BaseARTMAP.py b/artlib/common/BaseARTMAP.py index 7969545..312dfac 100644 --- a/artlib/common/BaseARTMAP.py +++ b/artlib/common/BaseARTMAP.py @@ -94,7 +94,7 @@ def fit( X: np.ndarray, y: np.ndarray, max_iter=1, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10, ): """Fit the model to the data. @@ -107,7 +107,7 @@ def fit( 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 + match_tracking : {"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. @@ -119,7 +119,7 @@ def partial_fit( self, X: np.ndarray, y: np.ndarray, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10, ): """Partial fit the model to the data. @@ -130,7 +130,7 @@ def partial_fit( Dataset A. y : np.ndarray Dataset B. - match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, optional + match_tracking : {"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. diff --git a/artlib/cvi/CVIART.py b/artlib/cvi/CVIART.py index 104c51d..3eda039 100644 --- a/artlib/cvi/CVIART.py +++ b/artlib/cvi/CVIART.py @@ -224,7 +224,7 @@ def fit( y: Optional[np.ndarray] = None, match_reset_func: Optional[Callable] = None, max_iter=1, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ): """Fit the model to the data. @@ -241,7 +241,7 @@ def fit( 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 + match_tracking : {"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. @@ -289,7 +289,7 @@ def fit( c = self.base_module.step_fit( x, match_reset_func=cvi_match_reset_func, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) self.labels_[index] = c @@ -321,7 +321,7 @@ def step_fit( self, x: np.ndarray, match_reset_func: Optional[Callable] = None, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ) -> int: """Fit the model to a single sample. @@ -334,7 +334,7 @@ def step_fit( 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 + match_tracking : {"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. diff --git a/artlib/cvi/iCVIFuzzyArt.py b/artlib/cvi/iCVIFuzzyArt.py index d69dd0d..8a18d22 100644 --- a/artlib/cvi/iCVIFuzzyArt.py +++ b/artlib/cvi/iCVIFuzzyArt.py @@ -92,7 +92,7 @@ def fit( y: Optional[np.ndarray] = None, match_reset_func: Optional[Callable] = None, max_iter=1, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ): """Fit the model to the data. @@ -109,7 +109,7 @@ def fit( 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 + match_tracking : {"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. @@ -136,7 +136,7 @@ def fit( c = self.step_fit( x, match_reset_func=self.iCVI_match, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) else: @@ -147,7 +147,7 @@ def fit( c = self.step_fit( x, match_reset_func=match_reset_func_, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index f5f3bc2..343f3ea 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -444,7 +444,7 @@ def partial_fit( self, X: np.ndarray, match_reset_func: Optional[Callable] = None, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ): """Iteratively fit the model to the data. @@ -455,7 +455,7 @@ def partial_fit( 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 + match_tracking : 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). @@ -476,7 +476,7 @@ def partial_fit( c = self.step_fit( x, match_reset_func=match_reset_func, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) self.labels_[i + j] = c diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index 94c6d25..9e771b4 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -262,7 +262,7 @@ def fit( X: list[np.ndarray], y: Optional[np.ndarray] = None, max_iter=1, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ): """Fit the DeepARTMAP model to the data. @@ -275,7 +275,7 @@ def fit( 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 + match_tracking : {"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. @@ -294,7 +294,7 @@ def fit( X[0], y, max_iter=max_iter, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) else: @@ -312,7 +312,7 @@ def fit( X[1], X[0], max_iter=max_iter, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) @@ -322,7 +322,7 @@ def fit( X[art_i], y_i, max_iter=max_iter, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) @@ -332,7 +332,7 @@ def partial_fit( self, X: list[np.ndarray], y: Optional[np.ndarray] = None, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ): """Partially fit the DeepARTMAP model to the data. @@ -343,7 +343,7 @@ def partial_fit( 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 + match_tracking : {"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. @@ -366,7 +366,7 @@ def partial_fit( "Must continue to provide labels for partial fit." ) self.layers[0] = self.layers[0].partial_fit( - X[0], y, match_reset_method=match_reset_method, epsilon=epsilon + X[0], y, match_tracking=match_tracking, epsilon=epsilon ) x_i = 1 else: @@ -389,7 +389,7 @@ def partial_fit( self.layers[0] = self.layers[0].partial_fit( X[1], X[0], - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) x_i = 2 @@ -400,7 +400,7 @@ def partial_fit( self.layers[art_i] = self.layers[art_i].partial_fit( X[x_i], y_i, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) x_i += 1 diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index 718d155..42ac2b3 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -110,7 +110,7 @@ def fit( X: np.ndarray, y: Optional[np.ndarray] = None, max_iter=1, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ): """Fit the SMART model to the data. @@ -123,7 +123,7 @@ def fit( 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 + match_tracking : {"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. @@ -138,7 +138,7 @@ def fit( return super().fit( X_list, max_iter=max_iter, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) @@ -146,7 +146,7 @@ def partial_fit( self, X: np.ndarray, y: Optional[np.ndarray] = None, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ): """Partial fit the SMART model to the data. @@ -157,7 +157,7 @@ def partial_fit( 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 + match_tracking : {"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. @@ -170,7 +170,7 @@ def partial_fit( """ X_list = [X] * self.n_modules return super(SMART, self).partial_fit( - X_list, match_reset_method=match_reset_method, epsilon=epsilon + X_list, match_tracking=match_tracking, epsilon=epsilon ) def plot_cluster_bounds( diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index 3f98737..4529146 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -171,7 +171,7 @@ def fit( X: np.ndarray, y: np.ndarray, max_iter=1, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10, verbose: bool = False, ): @@ -185,7 +185,7 @@ def fit( 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 + match_tracking : {"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. @@ -204,7 +204,7 @@ def fit( self.module_b.fit( y, max_iter=max_iter, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) @@ -214,7 +214,7 @@ def fit( X, y_c, max_iter=max_iter, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, verbose=verbose, ) @@ -225,7 +225,7 @@ def partial_fit( self, X: np.ndarray, y: np.ndarray, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10, ): """Partially fit the ARTMAP model to the data. @@ -236,7 +236,7 @@ def partial_fit( Data set A (independent channel). y : np.ndarray Data set B (dependent channel). - match_reset_method : {"MT+", "MT-", "MT0", "MT1", "MT~"}, optional + match_tracking : {"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. @@ -249,12 +249,12 @@ def partial_fit( """ self.validate_data(X, y) self.module_b.partial_fit( - y, match_reset_method=match_reset_method, epsilon=epsilon + y, match_tracking=match_tracking, epsilon=epsilon ) super(ARTMAP, self).partial_fit( X, self.labels_b, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) return self diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index 3e5cd2a..1674bd5 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -165,7 +165,7 @@ def step_fit( self, x: np.ndarray, c_b: int, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10, ) -> int: """Fit the model to a single sample. @@ -176,7 +176,7 @@ def step_fit( Data sample for side A. c_b : int Side B label. - match_reset_method : Literal, default="MT+" + match_tracking : Literal, default="MT+" Method to reset the match. epsilon : float, default=1e-10 Small value to adjust the vigilance. @@ -198,7 +198,7 @@ def step_fit( c_a = self.module_a.step_fit( x, match_reset_func=match_reset_func, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) if c_a not in self.map: @@ -212,7 +212,7 @@ def fit( X: np.ndarray, y: np.ndarray, max_iter=1, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10, verbose: bool = False, ): @@ -226,7 +226,7 @@ def fit( 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+" + match_tracking : Literal, default="MT+" Method to reset the match. epsilon : float, default=1e-10 Small value to adjust the vigilance. @@ -260,7 +260,7 @@ def fit( c_a = self.step_fit( x, c_b, - match_reset_method=match_reset_method, + match_tracking=match_tracking, epsilon=epsilon, ) self.module_a.labels_[i] = c_a @@ -271,7 +271,7 @@ def partial_fit( self, X: np.ndarray, y: np.ndarray, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 1e-10, ): """Partial fit the model to the data. @@ -282,7 +282,7 @@ def partial_fit( Data set A. y : np.ndarray Data set B. - match_reset_method : Literal, default="MT+" + match_tracking : Literal, default="MT+" Method to reset the match. epsilon : float, default=1e-10 Small value to adjust the vigilance. @@ -309,7 +309,7 @@ def partial_fit( for i, (x, c_b) in enumerate(zip(X, y)): self.module_a.pre_step_fit(X) c_a = self.step_fit( - x, c_b, match_reset_method=match_reset_method, epsilon=epsilon + x, c_b, match_tracking=match_tracking, epsilon=epsilon ) self.module_a.labels_[i + j] = c_a self.module_a.post_step_fit(X) diff --git a/artlib/topological/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index 86a473c..45b8d9c 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -272,7 +272,7 @@ def step_fit( self, x: np.ndarray, match_reset_func: Optional[Callable] = None, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ) -> int: """Fit the model to a single sample. @@ -285,7 +285,7 @@ def step_fit( 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 + match_tracking : {"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. @@ -297,7 +297,7 @@ def step_fit( """ base_params = self._deep_copy_params() - mt_operator = self._match_tracking_operator(match_reset_method) + mt_operator = self._match_tracking_operator(match_tracking) self.sample_counter_ += 1 if len(self.base_module.W) == 0: new_w = self.base_module.new_weight(x, self.base_module.params) @@ -360,7 +360,7 @@ def step_fit( return self.map[c_new] else: keep_searching = self._match_tracking( - cache, epsilon, self.params, match_reset_method + cache, epsilon, self.params, match_tracking ) if not keep_searching: T[:] = np.nan diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index 02ce25a..8f5fae6 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -445,7 +445,7 @@ def step_fit( self, x: np.ndarray, match_reset_func: Optional[Callable] = None, - match_reset_method: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", epsilon: float = 0.0, ) -> int: """Fit the model to a single sample. @@ -456,7 +456,7 @@ def step_fit( 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+" + match_tracking : Literal["MT+", "MT-", "MT0", "MT1", "MT~"], default="MT+" Method to reset the match. epsilon : float, default=0.0 Adjustment factor for vigilance. @@ -468,7 +468,7 @@ def step_fit( """ base_params = self._deep_copy_params() - mt_operator = self._match_tracking_operator(match_reset_method) + mt_operator = self._match_tracking_operator(match_tracking) self.sample_counter_ += 1 resonant_c: int = -1 @@ -529,7 +529,7 @@ def step_fit( T[c_] = np.nan if not no_match_reset: keep_searching = self._match_tracking( - cache, epsilon, self.params, match_reset_method + cache, epsilon, self.params, match_tracking ) if not keep_searching: T[:] = np.nan From 4762eb8b1cc644eb3613677abd806d05b706467a Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 12:49:41 -0500 Subject: [PATCH 097/139] add references.bib --- artlib/cvi/iCVIFuzzyArt.py | 8 +- artlib/elementary/ART1.py | 6 +- artlib/elementary/BayesianART.py | 4 +- artlib/elementary/EllipsoidART.py | 17 ++- artlib/elementary/HypersphereART.py | 8 +- artlib/elementary/QuadraticNeuronART.py | 8 +- artlib/hierarchical/SMART.py | 6 +- artlib/reinforcement/FALCON.py | 14 +- artlib/topological/DualVigilanceART.py | 5 +- docs/source/citation.rst | 4 +- docs/source/conf.py | 2 +- references.bib | 190 ++++++++++++++++++++++++ scripts/generate_references.py | 2 +- 13 files changed, 241 insertions(+), 33 deletions(-) create mode 100644 references.bib diff --git a/artlib/cvi/iCVIFuzzyArt.py b/artlib/cvi/iCVIFuzzyArt.py index 8a18d22..f8f931f 100644 --- a/artlib/cvi/iCVIFuzzyArt.py +++ b/artlib/cvi/iCVIFuzzyArt.py @@ -1,4 +1,10 @@ -"""TODO: Add Reference in correct format. +"""iCVI Fuzzy ART + +da Silva, Leonardo Enzo Brito, Nagasharath Rayapati, and Donald C. Wunsch. +"iCVI-ARTMAP: using incremental cluster validity indices and adaptive resonance +theory reset mechanism to accelerate validation and achieve multiprototype unsupervised +representations." +IEEE Transactions on Neural Networks and Learning Systems 34.12 (2022): 9757-9770. The original matlab code can be found at https://github.com/ACIL-Group/iCVI-toolbox/tree/master diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index f14c42f..33fd039 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -1,7 +1,9 @@ """ART1. -Carpenter, G. A., & Grossberg, S. (1987a). A massively parallel architecture for a self- -organizing neural pattern recognition machine. Computer Vision, Graphics, and Image +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. """ diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index bc2ecd9..36fb2de 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -1,6 +1,8 @@ """Bayesian ART. -Vigdor, B., & Lerner, B. (2007). The Bayesian ARTMAP. IEEE Transactions on Neural +Vigdor, B., & Lerner, B. (2007). +The Bayesian ARTMAP. +IEEE Transactions on Neural Networks, 18, 1628–1644. doi:10.1109/TNN.2007.900234. """ diff --git a/artlib/elementary/EllipsoidART.py b/artlib/elementary/EllipsoidART.py index 1425c5a..7fbd9aa 100644 --- a/artlib/elementary/EllipsoidART.py +++ b/artlib/elementary/EllipsoidART.py @@ -1,13 +1,14 @@ """Ellipsoid ART. -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. - -Anagnostopoulos, G. C., & Georgiopoulos, M. (2001b). Ellipsoid ART and ARTMAP for -incremental unsupervised and supervised learning. In Aerospace/Defense Sensing, -Simulation, and Controls (pp. 293– 304). International Society for Optics and Photonics. -doi:10.1117/12.421180. +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. + +Anagnostopoulos, G. C., & Georgiopoulos, M. (2001b). +Ellipsoid ART and ARTMAP for incremental unsupervised and supervised learning. +In Aerospace/Defense Sensing, Simulation, and Controls (pp. 293– 304). +International Society for Optics and Photonics. doi:10.1117/12.421180. """ import numpy as np diff --git a/artlib/elementary/HypersphereART.py b/artlib/elementary/HypersphereART.py index 38b6e76..2bcf3df 100644 --- a/artlib/elementary/HypersphereART.py +++ b/artlib/elementary/HypersphereART.py @@ -1,9 +1,9 @@ """Hyperpshere ART. -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. +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. """ import numpy as np diff --git a/artlib/elementary/QuadraticNeuronART.py b/artlib/elementary/QuadraticNeuronART.py index a492c42..c79bc54 100644 --- a/artlib/elementary/QuadraticNeuronART.py +++ b/artlib/elementary/QuadraticNeuronART.py @@ -1,9 +1,11 @@ """Quadratic Neuron ART. -Su, M.-C., & Liu, T.-K. (2001). Application of neural networks using quadratic junctions -in cluster analysis. Neurocomputing, 37, 165 – 175. doi:10.1016/S0925-2312(00)00343-X. +Su, M.-C., & Liu, T.-K. (2001). +Application of neural networks using quadratic junctions in cluster analysis. +Neurocomputing, 37, 165 – 175. doi:10.1016/S0925-2312(00)00343-X. -Su, M.-C., & Liu, Y.-C. (2005). A new approach to clustering data with arbitrary shapes. +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. """ diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index 42ac2b3..aeaaf85 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -1,7 +1,9 @@ """SMART. -Bartfai, G. (1994). Hierarchical clustering with ART neural networks. In Proc. IEEE -International Conference on Neural Networks (ICNN) (pp. 940–944). volume 2. +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. """ diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index d65dd2f..c03d469 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -1,11 +1,13 @@ """FALCON. -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. +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 """ diff --git a/artlib/topological/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index 45b8d9c..b855856 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -1,7 +1,8 @@ """Dual Vigilance ART. -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. +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. """ import numpy as np diff --git a/docs/source/citation.rst b/docs/source/citation.rst index 1b5e4dd..035e8bf 100644 --- a/docs/source/citation.rst +++ b/docs/source/citation.rst @@ -2,11 +2,11 @@ Citation ================================== If you use this project in your research, please cite it as: -.. bibliography:: references.bib +.. bibliography:: artlib_citation.bib :style: unsrt :all: Alternatively, you can cite the project using the following BibTeX entry: -.. literalinclude:: references.bib +.. literalinclude:: artlib_citation.bib :language: bibtex diff --git a/docs/source/conf.py b/docs/source/conf.py index d69d5b3..926d7a8 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -45,7 +45,7 @@ def setup(app): # autoapi_python_class_content = 'both' # autoclass_content = 'both' -bibtex_bibfiles = ['references.bib'] +bibtex_bibfiles = ['artlib_citation.bib'] intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), diff --git a/references.bib b/references.bib new file mode 100644 index 0000000..6d693ed --- /dev/null +++ b/references.bib @@ -0,0 +1,190 @@ +@inproceedings{bezdek2002vat, + title={VAT: A tool for visual assessment of (cluster) tendency}, + author={Bezdek, James C and Hathaway, Richard J}, + booktitle={Proceedings of the 2002 International Joint Conference on Neural Networks. IJCNN'02 (Cat. No. 02CH37290)}, + volume={3}, + pages={2225--2230}, + year={2002}, + organization={IEEE} +} +@article{da2022icvi, + title={iCVI-ARTMAP: using incremental cluster validity indices and adaptive resonance theory reset mechanism to accelerate validation and achieve multiprototype unsupervised representations}, + author={da Silva, Leonardo Enzo Brito and Rayapati, Nagasharath and Wunsch, Donald C}, + journal={IEEE Transactions on Neural Networks and Learning Systems}, + volume={34}, + number={12}, + pages={9757--9770}, + year={2022}, + publisher={IEEE} +} +@article{carpenter1987massively, + title={A massively parallel architecture for a self-organizing neural pattern recognition machine}, + author={Carpenter, Gail A and Grossberg, Stephen}, + journal={Computer vision, graphics, and image processing}, + volume={37}, + number={1}, + pages={54--115}, + year={1987}, + publisher={Elsevier} +} +@article{carpenter1987art, + title={ART 2: Self-organization of stable category recognition codes for analog input patterns}, + author={Carpenter, Gail A and Grossberg, Stephen}, + journal={Applied optics}, + volume={26}, + number={23}, + pages={4919--4930}, + year={1987}, + publisher={Optica Publishing Group} +} +@article{carpenter1991art, + title={ART 2-A: An adaptive resonance algorithm for rapid category learning and recognition}, + author={Carpenter, Gail A and Grossberg, Stephen and Rosen, David B}, + journal={Neural networks}, + volume={4}, + number={4}, + pages={493--504}, + year={1991}, + publisher={Elsevier} +} +@article{vigdor2007bayesian, + title={The bayesian artmap}, + author={Vigdor, Boaz and Lerner, Boaz}, + journal={IEEE Transactions on Neural Networks}, + volume={18}, + number={6}, + pages={1628--1644}, + year={2007}, + publisher={IEEE} +} +@inproceedings{anagnostopoulos2001ellipsoid, + title={Ellipsoid ART and ARTMAP for incremental clustering and classification}, + author={Anagnostopoulos, Georgios C and Georgiopoulos, Michael}, + booktitle={IJCNN'01. International Joint Conference on Neural Networks. Proceedings (Cat. No. 01CH37222)}, + volume={2}, + pages={1221--1226}, + year={2001}, + organization={IEEE} +} +@inproceedings{anagnostopoulos2001ellipsoid, + title={Ellipsoid ART and ARTMAP for incremental unsupervised and supervised learning}, + author={Anagnostopoulos, Georgios C and Georgiopoulos, Michael}, + booktitle={Applications and Science of Computational Intelligence IV}, + volume={4390}, + pages={293--304}, + year={2001}, + organization={SPIE} +} +@article{carpenter1991fuzzy, + title={Fuzzy ART: Fast stable learning and categorization of analog patterns by an adaptive resonance system}, + author={Carpenter, Gail A and Grossberg, Stephen and Rosen, David B}, + journal={Neural networks}, + volume={4}, + number={6}, + pages={759--771}, + year={1991}, + publisher={Elsevier} +} +@article{williamson1996gaussian, + title={Gaussian ARTMAP: A neural network for fast incremental learning of noisy multidimensional maps}, + author={Williamson, James R}, + journal={Neural networks}, + volume={9}, + number={5}, + pages={881--897}, + year={1996}, + publisher={Elsevier} +} +@inproceedings{anagnostopoulos2000hypersphere, + title={Hypersphere ART and ARTMAP for unsupervised and supervised, incremental learning}, + author={Anagnostopoulos, Georgios C and Georgiopulos, M}, + booktitle={Proceedings of the IEEE-INNS-ENNS International Joint Conference on Neural Networks. IJCNN 2000. Neural Computing: New Challenges and Perspectives for the New Millennium}, + volume={6}, + pages={59--64}, + year={2000}, + organization={IEEE} +} +@article{su2001application, + title={Application of neural networks using quadratic junctions in cluster analysis}, + author={Su, Mu-Chun and Liu, Ta-Kang}, + journal={Neurocomputing}, + volume={37}, + number={1-4}, + pages={165--175}, + year={2001}, + publisher={Elsevier} +} +@article{su2005new, + title={A new approach to clustering data with arbitrary shapes}, + author={Su, Mu-chun and Liu, Yi-chun}, + journal={pattern recognition}, + volume={38}, + number={11}, + pages={1887--1901}, + year={2005}, + publisher={Elsevier} +} +@inproceedings{tan2007intelligence, + title={Intelligence through interaction: Towards a unified theory for learning}, + author={Tan, Ah-Hwee and Carpenter, Gail A and Grossberg, Stephen}, + booktitle={International Symposium on Neural Networks}, + pages={1094--1103}, + year={2007}, + organization={Springer} +} +@inproceedings{bartfai1994hierarchical, + title={Hierarchical clustering with ART neural networks}, + author={Bartfai, Guszti}, + booktitle={Proceedings of 1994 IEEE International Conference on Neural Networks (ICNN'94)}, + volume={2}, + pages={940--944}, + year={1994}, + organization={IEEE} +} +@inproceedings{tan2004falcon, + title={FALCON: A fusion architecture for learning, cognition, and navigation}, + author={Tan, Ah-Hwee}, + booktitle={2004 IEEE International Joint Conference on Neural Networks (IEEE Cat. No. 04CH37541)}, + volume={4}, + pages={3297--3302}, + year={2004}, + organization={IEEE} +} +@article{tan2008integrating, + title={Integrating temporal difference methods and self-organizing neural networks for reinforcement learning with delayed evaluative feedback}, + author={Tan, Ah-Hwee and Lu, Ning and Xiao, Dan}, + journal={IEEE transactions on neural networks}, + volume={19}, + number={2}, + pages={230--244}, + year={2008}, + publisher={IEEE} +} +@article{carpenter1991artmap, + title={ARTMAP: Supervised real-time learning and classification of nonstationary data by a self-organizing neural network}, + author={Carpenter, Gail A and Grossberg, Stephen and Reynolds, John H}, + year={1991} +} +@misc{gotarredona1998adaptive, + title={Adaptive Resonance Theory Microchips Circuit Design Techniques}, + author={Gotarredona, Teresa Serrano and Barranco, B Linares and Andreou, AG}, + year={1998}, + publisher={Norwell, MA: Kluwer} +} +@article{da2019dual, + title={Dual vigilance fuzzy adaptive resonance theory}, + author={da Silva, Leonardo Enzo Brito and Elnabarawy, Islam and Wunsch II, Donald C}, + journal={Neural Networks}, + volume={109}, + pages={1--5}, + year={2019}, + publisher={Elsevier} +} +@inproceedings{tscherepanow2010topoart, + title={TopoART: A topology learning hierarchical ART network}, + author={Tscherepanow, Marko}, + booktitle={International Conference on Artificial Neural Networks}, + pages={157--167}, + year={2010}, + organization={Springer} +} \ No newline at end of file diff --git a/scripts/generate_references.py b/scripts/generate_references.py index 6a7a24f..51f0404 100644 --- a/scripts/generate_references.py +++ b/scripts/generate_references.py @@ -23,7 +23,7 @@ def generate_references(): modified_bibtex = "\n".join(bibtex_lines) # Write the modified content to references.bib - output_path = os.path.join("docs", "source", "references.bib") + output_path = os.path.join("docs", "source", "artlib_citation.bib") with open(output_path, "w", encoding="utf-8") as f: f.write(modified_bibtex) else: From dfd061286a2916acd19fcc30ede34ec1be92c959 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 13:13:57 -0500 Subject: [PATCH 098/139] add variable references to code --- artlib/common/VAT.py | 11 +++++++---- artlib/cvi/iCVIFuzzyArt.py | 13 ++++++++----- artlib/elementary/ART1.py | 14 ++++++++------ artlib/elementary/ART2.py | 20 ++++++++++++++------ artlib/elementary/BayesianART.py | 11 +++++++---- artlib/elementary/EllipsoidART.py | 23 ++++++++++++++--------- artlib/elementary/FuzzyART.py | 11 +++++++---- artlib/elementary/GaussianART.py | 11 +++++++---- artlib/elementary/HypersphereART.py | 11 +++++++---- artlib/elementary/QuadraticNeuronART.py | 19 ++++++++++++------- artlib/fusion/FusionART.py | 16 ++++++++++------ artlib/hierarchical/DeepARTMAP.py | 11 +++++++---- artlib/hierarchical/SMART.py | 13 ++++++++----- artlib/reinforcement/FALCON.py | 23 ++++++++++++++--------- artlib/supervised/ARTMAP.py | 11 +++++++---- artlib/supervised/SimpleARTMAP.py | 9 ++++++--- artlib/topological/DualVigilanceART.py | 9 ++++++--- artlib/topological/TopoART.py | 15 +++++++++------ docs/source/conf.py | 2 +- references.bib | 4 ++-- 20 files changed, 161 insertions(+), 96 deletions(-) diff --git a/artlib/common/VAT.py b/artlib/common/VAT.py index 3ae48b8..6bce6a4 100644 --- a/artlib/common/VAT.py +++ b/artlib/common/VAT.py @@ -1,9 +1,12 @@ """VAT. -Bezdek, J. C., & Hathaway, R. J. (2002). -VAT: A tool for visual assessment of cluster tendency. -Proceedings of the 2002 International Joint Conference on Neural Networks. -doi:10.1109/IJCNN.2002.1007487 +.. # Bezdek, J. C., & Hathaway, R. J. (2002). +.. # VAT: A tool for visual assessment of cluster tendency. +.. # Proceedings of the 2002 International Joint Conference on Neural Networks. +.. # doi:10.1109/IJCNN.2002.1007487 + +.. bibliography:: + :filter: docname in docnames and citation_key == "bezdek2002vat" """ import numpy as np diff --git a/artlib/cvi/iCVIFuzzyArt.py b/artlib/cvi/iCVIFuzzyArt.py index f8f931f..c6ff956 100644 --- a/artlib/cvi/iCVIFuzzyArt.py +++ b/artlib/cvi/iCVIFuzzyArt.py @@ -1,10 +1,10 @@ """iCVI Fuzzy ART -da Silva, Leonardo Enzo Brito, Nagasharath Rayapati, and Donald C. Wunsch. -"iCVI-ARTMAP: using incremental cluster validity indices and adaptive resonance -theory reset mechanism to accelerate validation and achieve multiprototype unsupervised -representations." -IEEE Transactions on Neural Networks and Learning Systems 34.12 (2022): 9757-9770. +.. # da Silva, Leonardo Enzo Brito, Nagasharath Rayapati, and Donald C. Wunsch. +.. # "iCVI-ARTMAP: using incremental cluster validity indices and adaptive resonance +.. # theory reset mechanism to accelerate validation and achieve multiprototype +.. # unsupervised representations." +.. # IEEE Transactions on Neural Networks and Learning Systems 34.12 (2022): 9757-9770. The original matlab code can be found at https://github.com/ACIL-Group/iCVI-toolbox/tree/master @@ -13,6 +13,9 @@ Pages 314-316 and 319-320 Extended icvi offline mode can be found at https://ieeexplore.ieee.org/document/9745260 +.. bibliography:: + :filter: docname in docnames and citation_key == "da2022icvi" + """ import numpy as np from typing import Optional, Literal, Callable diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index 33fd039..fbb6260 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -1,11 +1,13 @@ """ART1. -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. - +.. # 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. + +.. bibliography:: + :filter: docname in docnames and citation_key == "carpenter1987massively" """ import numpy as np diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index 6c37b3b..3178c6a 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -1,12 +1,20 @@ """ART2. -Carpenter, G. A., & Grossberg, S. (1987b). -ART 2: self-organization of stable category recognition codes for analog input patterns. -Appl. Opt., 26, 4919–4930. doi:10.1364/AO.26.004919. +.. # Carpenter, G. A., & Grossberg, S. (1987b). +.. # ART 2: self-organization of stable category recognition codes for analog input +.. # patterns. +.. # Appl. Opt., 26, 4919–4930. doi:10.1364/AO.26.004919. + +.. # 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. + +.. bibliography:: + :filter: docname in docnames and \ + (citation_key == "carpenter1987art" or \ + citation_key == "carpenter1991art") -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. ================================================================== DISCLAIMER: DO NOT USE ART2!!! diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index 36fb2de..6262425 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -1,9 +1,12 @@ """Bayesian ART. -Vigdor, B., & Lerner, B. (2007). -The Bayesian ARTMAP. -IEEE Transactions on Neural -Networks, 18, 1628–1644. doi:10.1109/TNN.2007.900234. +.. # Vigdor, B., & Lerner, B. (2007). +.. # The Bayesian ARTMAP. +.. # IEEE Transactions on Neural +.. # Networks, 18, 1628–1644. doi:10.1109/TNN.2007.900234. + +.. bibliography:: + :filter: docname in docnames and citation_key == "vigdor2007bayesian" """ import numpy as np diff --git a/artlib/elementary/EllipsoidART.py b/artlib/elementary/EllipsoidART.py index 7fbd9aa..4459b9f 100644 --- a/artlib/elementary/EllipsoidART.py +++ b/artlib/elementary/EllipsoidART.py @@ -1,14 +1,19 @@ """Ellipsoid ART. -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. - -Anagnostopoulos, G. C., & Georgiopoulos, M. (2001b). -Ellipsoid ART and ARTMAP for incremental unsupervised and supervised learning. -In Aerospace/Defense Sensing, Simulation, and Controls (pp. 293– 304). -International Society for Optics and Photonics. doi:10.1117/12.421180. +.. # 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. + +.. # Anagnostopoulos, G. C., & Georgiopoulos, M. (2001b). +.. # Ellipsoid ART and ARTMAP for incremental unsupervised and supervised learning. +.. # In Aerospace/Defense Sensing, Simulation, and Controls (pp. 293– 304). +.. # International Society for Optics and Photonics. doi:10.1117/12.421180. + +.. bibliography:: + :filter: docname in docnames and \ + (citation_key == "anagnostopoulos2001a" or \ + citation_key == "anagnostopoulos2001b") """ import numpy as np diff --git a/artlib/elementary/FuzzyART.py b/artlib/elementary/FuzzyART.py index fd849d0..d864915 100644 --- a/artlib/elementary/FuzzyART.py +++ b/artlib/elementary/FuzzyART.py @@ -1,9 +1,12 @@ """Fuzzy ART. -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. +.. # 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. + +.. bibliography:: + :filter: docname in docnames and citation_key == "carpenter1991fuzzy" """ import numpy as np diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index 11f76c2..cdf7638 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -1,9 +1,12 @@ """Gaussian ART. -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. +.. # 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. + +.. bibliography:: + :filter: docname in docnames and citation_key == "williamson1996gaussian" """ diff --git a/artlib/elementary/HypersphereART.py b/artlib/elementary/HypersphereART.py index 2bcf3df..0fc3a20 100644 --- a/artlib/elementary/HypersphereART.py +++ b/artlib/elementary/HypersphereART.py @@ -1,9 +1,12 @@ """Hyperpshere ART. -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. +.. # 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. + +.. bibliography:: + :filter: docname in docnames and citation_key == "anagnostopoulos2000hypersphere" """ import numpy as np diff --git a/artlib/elementary/QuadraticNeuronART.py b/artlib/elementary/QuadraticNeuronART.py index c79bc54..2a9a0f2 100644 --- a/artlib/elementary/QuadraticNeuronART.py +++ b/artlib/elementary/QuadraticNeuronART.py @@ -1,12 +1,17 @@ """Quadratic Neuron ART. -Su, M.-C., & Liu, T.-K. (2001). -Application of neural networks using quadratic junctions in cluster analysis. -Neurocomputing, 37, 165 – 175. doi:10.1016/S0925-2312(00)00343-X. - -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. +.. # Su, M.-C., & Liu, T.-K. (2001). +.. # Application of neural networks using quadratic junctions in cluster analysis. +.. # Neurocomputing, 37, 165 – 175. doi:10.1016/S0925-2312(00)00343-X. + +.. # 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. + +.. bibliography:: + :filter: docname in docnames and \ + (citation_key == "su2001application" or \ + citation_key == "su2005new") """ diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index 343f3ea..15eebcc 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -1,11 +1,15 @@ """Fusion ART. -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. +.. # 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. + +.. bibliography:: + :filter: docname in docnames and citation_key == "tan2007intelligence" + """ import numpy as np diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index 9e771b4..bda8e4d 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -1,9 +1,12 @@ """Deep ARTMAP. -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. +.. # 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. + +.. bibliography:: + :filter: docname in docnames and citation_key == "carpenter1991artmap" """ import numpy as np diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index aeaaf85..636bec7 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -1,10 +1,13 @@ """SMART. -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. +.. # 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. + +.. bibliography:: + :filter: docname in docnames and citation_key == "bartfai1994hierarchical" """ diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index c03d469..2d30f8d 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -1,14 +1,19 @@ """FALCON. -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 +.. # 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 + +.. bibliography:: + :filter: docname in docnames and \ + (citation_key == "tan2004falcon" or \ + citation_key == "tan2008integrating") """ diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index 4529146..d368bd9 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -1,9 +1,12 @@ """ARTMAP. -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. +.. # 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. + +.. bibliography:: + :filter: docname in docnames and citation_key == "carpenter1991artmap" """ import numpy as np diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index 1674bd5..b59dde1 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -1,8 +1,11 @@ """Simple ARTMAP. -Serrano-Gotarredona, T., Linares-Barranco, B., & Andreou, A. G. (1998). -Adaptive Resonance Theory Microchips: Circuit Design Techniques. -Norwell, MA, USA: Kluwer Academic Publishers. +.. # Serrano-Gotarredona, T., Linares-Barranco, B., & Andreou, A. G. (1998). +.. # Adaptive Resonance Theory Microchips: Circuit Design Techniques. +.. # Norwell, MA, USA: Kluwer Academic Publishers. + +.. bibliography:: + :filter: docname in docnames and citation_key == "gotarredona1998adaptive" """ import numpy as np diff --git a/artlib/topological/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index b855856..9c23c6d 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -1,8 +1,11 @@ """Dual Vigilance ART. -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. +.. # 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. + +.. bibliography:: + :filter: docname in docnames and citation_key == "da2019dual" """ import numpy as np diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index 8f5fae6..a2d2fa6 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -1,11 +1,14 @@ """Topo ART. -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. +.. # 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. + +.. bibliography:: + :filter: docname in docnames and citation_key == "tscherepanow2010topoart" """ diff --git a/docs/source/conf.py b/docs/source/conf.py index 926d7a8..68f3418 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -45,7 +45,7 @@ def setup(app): # autoapi_python_class_content = 'both' # autoclass_content = 'both' -bibtex_bibfiles = ['artlib_citation.bib'] +bibtex_bibfiles = ['artlib_citation.bib', '../../references.bib'] intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), diff --git a/references.bib b/references.bib index 6d693ed..ca3ddba 100644 --- a/references.bib +++ b/references.bib @@ -57,7 +57,7 @@ @article{vigdor2007bayesian year={2007}, publisher={IEEE} } -@inproceedings{anagnostopoulos2001ellipsoid, +@inproceedings{anagnostopoulos2001a, title={Ellipsoid ART and ARTMAP for incremental clustering and classification}, author={Anagnostopoulos, Georgios C and Georgiopoulos, Michael}, booktitle={IJCNN'01. International Joint Conference on Neural Networks. Proceedings (Cat. No. 01CH37222)}, @@ -66,7 +66,7 @@ @inproceedings{anagnostopoulos2001ellipsoid year={2001}, organization={IEEE} } -@inproceedings{anagnostopoulos2001ellipsoid, +@inproceedings{anagnostopoulos2001b, title={Ellipsoid ART and ARTMAP for incremental unsupervised and supervised learning}, author={Anagnostopoulos, Georgios C and Georgiopoulos, Michael}, booktitle={Applications and Science of Computational Intelligence IV}, From 7f2bbf30aa38b893240709df8d92a49237b5da2b Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 13:21:06 -0500 Subject: [PATCH 099/139] add variable references to code --- artlib/common/VAT.py | 2 +- artlib/cvi/iCVIFuzzyArt.py | 2 +- artlib/elementary/ART1.py | 2 +- artlib/elementary/ART2.py | 2 +- artlib/elementary/BayesianART.py | 2 +- artlib/elementary/EllipsoidART.py | 2 +- artlib/elementary/FuzzyART.py | 2 +- artlib/elementary/GaussianART.py | 2 +- artlib/elementary/HypersphereART.py | 2 +- artlib/elementary/QuadraticNeuronART.py | 2 +- artlib/fusion/FusionART.py | 2 +- artlib/hierarchical/DeepARTMAP.py | 2 +- artlib/hierarchical/SMART.py | 2 +- artlib/reinforcement/FALCON.py | 2 +- artlib/supervised/ARTMAP.py | 2 +- artlib/supervised/SimpleARTMAP.py | 2 +- artlib/topological/DualVigilanceART.py | 2 +- artlib/topological/TopoART.py | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/artlib/common/VAT.py b/artlib/common/VAT.py index 6bce6a4..051502c 100644 --- a/artlib/common/VAT.py +++ b/artlib/common/VAT.py @@ -6,7 +6,7 @@ .. # doi:10.1109/IJCNN.2002.1007487 .. bibliography:: - :filter: docname in docnames and citation_key == "bezdek2002vat" + :filter: citation_key == "bezdek2002vat" """ import numpy as np diff --git a/artlib/cvi/iCVIFuzzyArt.py b/artlib/cvi/iCVIFuzzyArt.py index c6ff956..434e121 100644 --- a/artlib/cvi/iCVIFuzzyArt.py +++ b/artlib/cvi/iCVIFuzzyArt.py @@ -14,7 +14,7 @@ https://ieeexplore.ieee.org/document/9745260 .. bibliography:: - :filter: docname in docnames and citation_key == "da2022icvi" + :filter: citation_key == "da2022icvi" """ import numpy as np diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index fbb6260..dc4d7cd 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -7,7 +7,7 @@ .. # Processing, 37, 54 – 115. doi:10. 1016/S0734-189X(87)80014-2. .. bibliography:: - :filter: docname in docnames and citation_key == "carpenter1987massively" + :filter: citation_key == "carpenter1987massively" """ import numpy as np diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index 3178c6a..b649dfc 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -11,7 +11,7 @@ .. # Neural Networks, 4, 493 – 504. doi:10.1016/0893-6080(91) 90045-7. .. bibliography:: - :filter: docname in docnames and \ + :filter: \ (citation_key == "carpenter1987art" or \ citation_key == "carpenter1991art") diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index 6262425..609dab6 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -6,7 +6,7 @@ .. # Networks, 18, 1628–1644. doi:10.1109/TNN.2007.900234. .. bibliography:: - :filter: docname in docnames and citation_key == "vigdor2007bayesian" + :filter: citation_key == "vigdor2007bayesian" """ import numpy as np diff --git a/artlib/elementary/EllipsoidART.py b/artlib/elementary/EllipsoidART.py index 4459b9f..58bd49b 100644 --- a/artlib/elementary/EllipsoidART.py +++ b/artlib/elementary/EllipsoidART.py @@ -11,7 +11,7 @@ .. # International Society for Optics and Photonics. doi:10.1117/12.421180. .. bibliography:: - :filter: docname in docnames and \ + :filter: \ (citation_key == "anagnostopoulos2001a" or \ citation_key == "anagnostopoulos2001b") diff --git a/artlib/elementary/FuzzyART.py b/artlib/elementary/FuzzyART.py index d864915..5459ddd 100644 --- a/artlib/elementary/FuzzyART.py +++ b/artlib/elementary/FuzzyART.py @@ -6,7 +6,7 @@ .. # Neural Networks, 4, 759 – 771. doi:10.1016/0893-6080(91)90056-B. .. bibliography:: - :filter: docname in docnames and citation_key == "carpenter1991fuzzy" + :filter: citation_key == "carpenter1991fuzzy" """ import numpy as np diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index cdf7638..aac7986 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -6,7 +6,7 @@ .. # Neural Networks, 9, 881 – 897. doi:10.1016/0893-6080(95)00115-8. .. bibliography:: - :filter: docname in docnames and citation_key == "williamson1996gaussian" + :filter: citation_key == "williamson1996gaussian" """ diff --git a/artlib/elementary/HypersphereART.py b/artlib/elementary/HypersphereART.py index 0fc3a20..9849231 100644 --- a/artlib/elementary/HypersphereART.py +++ b/artlib/elementary/HypersphereART.py @@ -6,7 +6,7 @@ .. # (pp. 59–64). volume 6. doi:10.1109/IJCNN.2000.859373. .. bibliography:: - :filter: docname in docnames and citation_key == "anagnostopoulos2000hypersphere" + :filter: citation_key == "anagnostopoulos2000hypersphere" """ import numpy as np diff --git a/artlib/elementary/QuadraticNeuronART.py b/artlib/elementary/QuadraticNeuronART.py index 2a9a0f2..12a011b 100644 --- a/artlib/elementary/QuadraticNeuronART.py +++ b/artlib/elementary/QuadraticNeuronART.py @@ -9,7 +9,7 @@ .. # Pattern Recognition, 38, 1887 – 1901. doi:10.1016/j.patcog.2005.04.010. .. bibliography:: - :filter: docname in docnames and \ + :filter: \ (citation_key == "su2001application" or \ citation_key == "su2005new") diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index 15eebcc..5cd165e 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -8,7 +8,7 @@ .. # doi:10.1007/ 978-3-540-72383-7_128. .. bibliography:: - :filter: docname in docnames and citation_key == "tan2007intelligence" + :filter: citation_key == "tan2007intelligence" """ diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index bda8e4d..896848d 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -6,7 +6,7 @@ .. # Neural Networks, 4, 565 – 588. doi:10.1016/0893-6080(91)90012-T. .. bibliography:: - :filter: docname in docnames and citation_key == "carpenter1991artmap" + :filter: citation_key == "carpenter1991artmap" """ import numpy as np diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index 636bec7..6cb766a 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -7,7 +7,7 @@ .. # doi:10.1109/ICNN.1994.374307. .. bibliography:: - :filter: docname in docnames and citation_key == "bartfai1994hierarchical" + :filter: citation_key == "bartfai1994hierarchical" """ diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index 2d30f8d..6aa2d79 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -11,7 +11,7 @@ .. # IEEE Transactions on Neural Networks, 19 , 230–244. doi:10.1109/TNN.2007.905839 .. bibliography:: - :filter: docname in docnames and \ + :filter: \ (citation_key == "tan2004falcon" or \ citation_key == "tan2008integrating") diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index d368bd9..fd769d8 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -6,7 +6,7 @@ .. # Neural Networks, 4, 565 – 588. doi:10.1016/0893-6080(91)90012-T. .. bibliography:: - :filter: docname in docnames and citation_key == "carpenter1991artmap" + :filter: citation_key == "carpenter1991artmap" """ import numpy as np diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index b59dde1..8af8362 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -5,7 +5,7 @@ .. # Norwell, MA, USA: Kluwer Academic Publishers. .. bibliography:: - :filter: docname in docnames and citation_key == "gotarredona1998adaptive" + :filter: citation_key == "gotarredona1998adaptive" """ import numpy as np diff --git a/artlib/topological/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index 9c23c6d..844e653 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -5,7 +5,7 @@ .. # Neural Networks, 109, 1–5. doi:10.1016/j.neunet.2018.09.015. .. bibliography:: - :filter: docname in docnames and citation_key == "da2019dual" + :filter: citation_key == "da2019dual" """ import numpy as np diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index a2d2fa6..6b2a62d 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -8,7 +8,7 @@ .. # doi:10.1007/978-3-642-15825-4_21. .. bibliography:: - :filter: docname in docnames and citation_key == "tscherepanow2010topoart" + :filter: citation_key == "tscherepanow2010topoart" """ From 050d186a44f91aa8e066ab1fb674e299d66844ea Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 13:27:09 -0500 Subject: [PATCH 100/139] add references page --- docs/source/index.rst | 1 + docs/source/references.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 docs/source/references.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 0fabef5..1573794 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -22,6 +22,7 @@ AdaptiveResonanceLib Home contributing contact license + references citation .. toctree:: diff --git a/docs/source/references.rst b/docs/source/references.rst new file mode 100644 index 0000000..45f1346 --- /dev/null +++ b/docs/source/references.rst @@ -0,0 +1,6 @@ +References +================================== + +.. bibliography:: ../../references.bib + :style: unsrt + :all: \ No newline at end of file From 4eeee1a110175b29e4b2fac2a50d25874b1bc8d6 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 13:33:08 -0500 Subject: [PATCH 101/139] update citations --- artlib/common/VAT.py | 2 +- artlib/cvi/iCVIFuzzyArt.py | 2 +- artlib/elementary/ART1.py | 2 +- artlib/elementary/ART2.py | 2 +- artlib/elementary/BayesianART.py | 2 +- artlib/elementary/EllipsoidART.py | 2 +- artlib/elementary/FuzzyART.py | 2 +- artlib/elementary/GaussianART.py | 2 +- artlib/elementary/HypersphereART.py | 2 +- artlib/elementary/QuadraticNeuronART.py | 2 +- artlib/fusion/FusionART.py | 2 +- artlib/hierarchical/DeepARTMAP.py | 2 +- artlib/hierarchical/SMART.py | 2 +- artlib/reinforcement/FALCON.py | 2 +- artlib/supervised/ARTMAP.py | 2 +- artlib/supervised/SimpleARTMAP.py | 2 +- artlib/topological/DualVigilanceART.py | 2 +- artlib/topological/TopoART.py | 2 +- references.bib | 14 ++++++++++---- 19 files changed, 28 insertions(+), 22 deletions(-) diff --git a/artlib/common/VAT.py b/artlib/common/VAT.py index 051502c..c601c27 100644 --- a/artlib/common/VAT.py +++ b/artlib/common/VAT.py @@ -5,7 +5,7 @@ .. # Proceedings of the 2002 International Joint Conference on Neural Networks. .. # doi:10.1109/IJCNN.2002.1007487 -.. bibliography:: +.. bibliography:: ../../references.bib :filter: citation_key == "bezdek2002vat" """ diff --git a/artlib/cvi/iCVIFuzzyArt.py b/artlib/cvi/iCVIFuzzyArt.py index 434e121..75b6fee 100644 --- a/artlib/cvi/iCVIFuzzyArt.py +++ b/artlib/cvi/iCVIFuzzyArt.py @@ -13,7 +13,7 @@ Pages 314-316 and 319-320 Extended icvi offline mode can be found at https://ieeexplore.ieee.org/document/9745260 -.. bibliography:: +.. bibliography:: ../../references.bib :filter: citation_key == "da2022icvi" """ diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index dc4d7cd..9d36abd 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -6,7 +6,7 @@ .. # Computer Vision, Graphics, and Image .. # Processing, 37, 54 – 115. doi:10. 1016/S0734-189X(87)80014-2. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: citation_key == "carpenter1987massively" """ diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index b649dfc..f40cc03 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -10,7 +10,7 @@ .. # recognition. .. # Neural Networks, 4, 493 – 504. doi:10.1016/0893-6080(91) 90045-7. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: \ (citation_key == "carpenter1987art" or \ citation_key == "carpenter1991art") diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index 609dab6..d61e2e6 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -5,7 +5,7 @@ .. # IEEE Transactions on Neural .. # Networks, 18, 1628–1644. doi:10.1109/TNN.2007.900234. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: citation_key == "vigdor2007bayesian" """ diff --git a/artlib/elementary/EllipsoidART.py b/artlib/elementary/EllipsoidART.py index 58bd49b..31cf8f9 100644 --- a/artlib/elementary/EllipsoidART.py +++ b/artlib/elementary/EllipsoidART.py @@ -10,7 +10,7 @@ .. # In Aerospace/Defense Sensing, Simulation, and Controls (pp. 293– 304). .. # International Society for Optics and Photonics. doi:10.1117/12.421180. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: \ (citation_key == "anagnostopoulos2001a" or \ citation_key == "anagnostopoulos2001b") diff --git a/artlib/elementary/FuzzyART.py b/artlib/elementary/FuzzyART.py index 5459ddd..83a8394 100644 --- a/artlib/elementary/FuzzyART.py +++ b/artlib/elementary/FuzzyART.py @@ -5,7 +5,7 @@ .. # adaptive resonance system. .. # Neural Networks, 4, 759 – 771. doi:10.1016/0893-6080(91)90056-B. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: citation_key == "carpenter1991fuzzy" """ diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index aac7986..c5a4a78 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -5,7 +5,7 @@ .. # Multidimensional Maps. .. # Neural Networks, 9, 881 – 897. doi:10.1016/0893-6080(95)00115-8. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: citation_key == "williamson1996gaussian" """ diff --git a/artlib/elementary/HypersphereART.py b/artlib/elementary/HypersphereART.py index 9849231..320b6a1 100644 --- a/artlib/elementary/HypersphereART.py +++ b/artlib/elementary/HypersphereART.py @@ -5,7 +5,7 @@ .. # In Proc. IEEE International Joint Conference on Neural Networks (IJCNN) .. # (pp. 59–64). volume 6. doi:10.1109/IJCNN.2000.859373. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: citation_key == "anagnostopoulos2000hypersphere" """ diff --git a/artlib/elementary/QuadraticNeuronART.py b/artlib/elementary/QuadraticNeuronART.py index 12a011b..acb52ee 100644 --- a/artlib/elementary/QuadraticNeuronART.py +++ b/artlib/elementary/QuadraticNeuronART.py @@ -8,7 +8,7 @@ .. # A new approach to clustering data with arbitrary shapes. .. # Pattern Recognition, 38, 1887 – 1901. doi:10.1016/j.patcog.2005.04.010. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: \ (citation_key == "su2001application" or \ citation_key == "su2005new") diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index 5cd165e..ba4f398 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -7,7 +7,7 @@ .. # Berlin, Heidelberg: Springer Berlin Heidelberg. .. # doi:10.1007/ 978-3-540-72383-7_128. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: citation_key == "tan2007intelligence" diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index 896848d..bcefe4c 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -5,7 +5,7 @@ .. # self-organizing neural network. .. # Neural Networks, 4, 565 – 588. doi:10.1016/0893-6080(91)90012-T. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: citation_key == "carpenter1991artmap" """ diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index 6cb766a..9d814a7 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -6,7 +6,7 @@ .. # (pp. 940–944). volume 2. .. # doi:10.1109/ICNN.1994.374307. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: citation_key == "bartfai1994hierarchical" """ diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index 6aa2d79..7f54a56 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -10,7 +10,7 @@ .. # Reinforcement Learning With Delayed Evaluative Feedback. .. # IEEE Transactions on Neural Networks, 19 , 230–244. doi:10.1109/TNN.2007.905839 -.. bibliography:: +.. bibliography:: ../../references.bib :filter: \ (citation_key == "tan2004falcon" or \ citation_key == "tan2008integrating") diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index fd769d8..9f14063 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -5,7 +5,7 @@ .. # self-organizing neural network. .. # Neural Networks, 4, 565 – 588. doi:10.1016/0893-6080(91)90012-T. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: citation_key == "carpenter1991artmap" """ diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index 8af8362..48457c8 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -4,7 +4,7 @@ .. # Adaptive Resonance Theory Microchips: Circuit Design Techniques. .. # Norwell, MA, USA: Kluwer Academic Publishers. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: citation_key == "gotarredona1998adaptive" """ diff --git a/artlib/topological/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index 844e653..b6f3c27 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -4,7 +4,7 @@ .. # Dual vigilance fuzzy adaptive resonance theory. .. # Neural Networks, 109, 1–5. doi:10.1016/j.neunet.2018.09.015. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: citation_key == "da2019dual" """ diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index 6b2a62d..3b58d19 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -7,7 +7,7 @@ .. # Berlin, Heidelberg: Springer Berlin Heidelberg. .. # doi:10.1007/978-3-642-15825-4_21. -.. bibliography:: +.. bibliography:: ../../references.bib :filter: citation_key == "tscherepanow2010topoart" """ diff --git a/references.bib b/references.bib index ca3ddba..d797762 100644 --- a/references.bib +++ b/references.bib @@ -160,10 +160,16 @@ @article{tan2008integrating year={2008}, publisher={IEEE} } -@article{carpenter1991artmap, - title={ARTMAP: Supervised real-time learning and classification of nonstationary data by a self-organizing neural network}, - author={Carpenter, Gail A and Grossberg, Stephen and Reynolds, John H}, - year={1991} +@inproceedings{carpenter1991artmap, + author={Carpenter, G.A. and Grossberg, S. and Reynolds, J.H.}, + booktitle={[1991 Proceedings] IEEE Conference on Neural Networks for Ocean Engineering}, + title={ARTMAP: supervised real-time learning and classification of nonstationary data by a self-organizing neural network}, + year={1991}, + volume={}, + number={}, + pages={341-342}, + keywords={Subspace constraints;Databases;Neural networks;Supervised learning;Resonance;Pattern recognition;System testing;Benchmark testing;Machine learning;Machine learning algorithms}, + doi={10.1109/ICNN.1991.163370} } @misc{gotarredona1998adaptive, title={Adaptive Resonance Theory Microchips Circuit Design Techniques}, From c1c4492713fc890b7763b1bc77aa7ca4476373f6 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 13:54:29 -0500 Subject: [PATCH 102/139] add citations to class descriptions --- artlib/common/VAT.py | 15 ++++++++++++++ artlib/cvi/iCVIFuzzyArt.py | 14 ++++++++++++- artlib/elementary/ART1.py | 20 +++++++++++++------ artlib/elementary/ART2.py | 21 +++++++++++++++----- artlib/elementary/BayesianART.py | 18 ++++++++++++----- artlib/elementary/EllipsoidART.py | 26 +++++++++++++++++++------ artlib/elementary/FuzzyART.py | 17 ++++++++++------ artlib/elementary/GaussianART.py | 14 ++++++++----- artlib/elementary/HypersphereART.py | 17 +++++++++++----- artlib/elementary/QuadraticNeuronART.py | 22 ++++++++++++++++----- artlib/fusion/FusionART.py | 19 +++++++++++------- artlib/hierarchical/DeepARTMAP.py | 8 ++++++++ artlib/hierarchical/SMART.py | 22 ++++++++++++++------- artlib/reinforcement/FALCON.py | 21 +++++++++++++++----- artlib/supervised/ARTMAP.py | 14 ++++++++----- artlib/supervised/SimpleARTMAP.py | 12 ++++++++---- artlib/topological/DualVigilanceART.py | 14 +++++++++---- artlib/topological/TopoART.py | 19 ++++++++++-------- 18 files changed, 229 insertions(+), 84 deletions(-) diff --git a/artlib/common/VAT.py b/artlib/common/VAT.py index c601c27..9e9d23f 100644 --- a/artlib/common/VAT.py +++ b/artlib/common/VAT.py @@ -20,6 +20,21 @@ def VAT( ) -> Tuple[np.ndarray, np.ndarray]: """Visual Assessment of Cluster Tendency (VAT) algorithm. + VAT was originally designed as a visualization tool for clustering behavior of data. + When the VAT-reordered distance matrix is plotted as an image, clusters will appear + in visually distinct groups along the diagonal. However, it has since been + discovered that the reordering significantly improves the results of order-dependent + clustering methods like ART. It is therefore recommended to pre-process data with + VAT prior to presentation when possible. + + .. # Bezdek, J. C., & Hathaway, R. J. (2002). + .. # VAT: A tool for visual assessment of cluster tendency. + .. # Proceedings of the 2002 International Joint Conference on Neural Networks. + .. # doi:10.1109/IJCNN.2002.1007487 + + .. bibliography:: ../../references.bib + :filter: citation_key == "bezdek2002vat" + Parameters ---------- data : np.ndarray diff --git a/artlib/cvi/iCVIFuzzyArt.py b/artlib/cvi/iCVIFuzzyArt.py index 75b6fee..4e3c37a 100644 --- a/artlib/cvi/iCVIFuzzyArt.py +++ b/artlib/cvi/iCVIFuzzyArt.py @@ -24,7 +24,19 @@ class iCVIFuzzyART(FuzzyART): - """ICVI Fuzzy Art For Clustering.""" + """ICVI Fuzzy Art For Clustering. + + .. # da Silva, Leonardo Enzo Brito, Nagasharath Rayapati, and Donald C. Wunsch. + .. # "iCVI-ARTMAP: using incremental cluster validity indices and adaptive resonance + .. # theory reset mechanism to accelerate validation and achieve multiprototype + .. # unsupervised representations." + .. # IEEE Transactions on Neural Networks and Learning Systems + .. # 34.12 (2022): 9757-9770. + + .. bibliography:: ../../references.bib + :filter: citation_key == "da2022icvi" + + """ CALINSKIHARABASZ = 1 diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index 9d36abd..0a7938d 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -8,6 +8,7 @@ .. bibliography:: ../../references.bib :filter: citation_key == "carpenter1987massively" + """ import numpy as np @@ -17,13 +18,20 @@ class ART1(BaseART): - """ART1 for Clustering. + """ART1 for Binary 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. + + .. bibliography:: ../../references.bib + :filter: citation_key == "carpenter1987massively" - 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. + ART1 is exclusively for clustering binary data. """ diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index f40cc03..f9ef370 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -33,11 +33,22 @@ class ART2A(BaseART): """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. + This module implements ART2-A as first published in: + + .. # Carpenter, G. A., & Grossberg, S. (1987b). + .. # ART 2: self-organization of stable category recognition codes for analog input + .. # patterns. + .. # Appl. Opt., 26, 4919–4930. doi:10.1364/AO.26.004919. + + .. # 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. + + .. bibliography:: ../../references.bib + :filter: \ + (citation_key == "carpenter1987art" or \ + citation_key == "carpenter1991art") ART2-A is similar to ART1 but designed for analog data. This method is implemented for historical purposes and is not recommended for use. diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index d61e2e6..879b1a6 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -20,11 +20,19 @@ class BayesianART(BaseART): """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. + 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. + + .. bibliography:: ../../references.bib + :filter: citation_key == "vigdor2007bayesian" + + 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. """ diff --git a/artlib/elementary/EllipsoidART.py b/artlib/elementary/EllipsoidART.py index 31cf8f9..9baccc9 100644 --- a/artlib/elementary/EllipsoidART.py +++ b/artlib/elementary/EllipsoidART.py @@ -26,12 +26,26 @@ class EllipsoidART(BaseART): """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. + 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. + + .. # Anagnostopoulos, G. C., & Georgiopoulos, M. (2001b). + .. # Ellipsoid ART and ARTMAP for incremental unsupervised and supervised learning. + .. # In Aerospace/Defense Sensing, Simulation, and Controls (pp. 293– 304). + .. # International Society for Optics and Photonics. doi:10.1117/12.421180. + + .. bibliography:: ../../references.bib + :filter: \ + (citation_key == "anagnostopoulos2001a" or \ + citation_key == "anagnostopoulos2001b") + + 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. """ diff --git a/artlib/elementary/FuzzyART.py b/artlib/elementary/FuzzyART.py index 83a8394..41ee2bb 100644 --- a/artlib/elementary/FuzzyART.py +++ b/artlib/elementary/FuzzyART.py @@ -61,13 +61,18 @@ def get_bounding_box( class FuzzyART(BaseART): """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. + This module implements Fuzzy ART as first published in: - Fuzzy ART is a hyper-box based clustering method. + .. # 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. + + .. bibliography:: ../../references.bib + :filter: citation_key == "carpenter1991fuzzy" + + Fuzzy ART is a hyper-box based clustering method that is exceptionally fast and + explainable. """ diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index c5a4a78..a2f6e7b 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -20,11 +20,15 @@ class GaussianART(BaseART): """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. + 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. + + .. bibliography:: ../../references.bib + :filter: citation_key == "williamson1996gaussian" 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 diff --git a/artlib/elementary/HypersphereART.py b/artlib/elementary/HypersphereART.py index 320b6a1..e46b74d 100644 --- a/artlib/elementary/HypersphereART.py +++ b/artlib/elementary/HypersphereART.py @@ -19,11 +19,18 @@ class HypersphereART(BaseART): """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. + 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. + + .. bibliography:: ../../references.bib + :filter: citation_key == "anagnostopoulos2000hypersphere" + + Hyperpshere ART clusters data in Hyper-spheres similar to k-means with a dynamic k. """ diff --git a/artlib/elementary/QuadraticNeuronART.py b/artlib/elementary/QuadraticNeuronART.py index acb52ee..cf5273c 100644 --- a/artlib/elementary/QuadraticNeuronART.py +++ b/artlib/elementary/QuadraticNeuronART.py @@ -26,11 +26,23 @@ class QuadraticNeuronART(BaseART): """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. + This module implements Quadratic Neuron ART as first published in: + + .. # Su, M.-C., & Liu, T.-K. (2001). + .. # Application of neural networks using quadratic junctions in cluster analysis. + .. # Neurocomputing, 37, 165 – 175. doi:10.1016/S0925-2312(00)00343-X. + + .. # 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. + + .. bibliography:: ../../references.bib + :filter: \ + (citation_key == "su2001application" or \ + citation_key == "su2005new") + + Quadratic Neuron ART clusters data in Hyper-ellipsoid by utilizing a quadratic + neural network for activation and resonance. """ diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index ba4f398..2d5ea71 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -49,13 +49,18 @@ def get_channel_position_tuples( class FusionART(BaseART): """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. + 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. + + .. bibliography:: ../../references.bib + :filter: citation_key == "tan2007intelligence" + 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 diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index bcefe4c..98683fc 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -29,6 +29,14 @@ class DeepARTMAP(BaseEstimator, ClassifierMixin, ClusterMixin): second module is the B module. DeepARTMAP does not currently have a direct citation and is an original creation of this library. + .. # 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. + + .. bibliography:: ../../references.bib + :filter: citation_key == "carpenter1991artmap" + """ def __init__(self, modules: list[BaseART]): diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index 9d814a7..1f0a2ca 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -22,13 +22,21 @@ 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. + 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. + + .. bibliography:: ../../references.bib + :filter: citation_key == "bartfai1994hierarchical" + + SMART accepts an uninstantiated 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. """ diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index 7f54a56..e80c3a8 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -27,11 +27,22 @@ 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. + 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. + + .. # 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 + + .. bibliography:: ../../references.bib + :filter: \ + (citation_key == "tan2004falcon" or \ + citation_key == "tan2008integrating") 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 diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index 9f14063..f899a74 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -19,11 +19,15 @@ 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. + 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. + + .. bibliography:: ../../references.bib + :filter: citation_key == "carpenter1991artmap" 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 diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index 48457c8..fe74739 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -21,10 +21,14 @@ 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. + 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. + + .. bibliography:: ../../references.bib + :filter: citation_key == "gotarredona1998adaptive" 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 diff --git a/artlib/topological/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index b6f3c27..85cda9c 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -20,10 +20,16 @@ class DualVigilanceART(BaseART): """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 + 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. + + .. bibliography:: ../../references.bib + :filter: citation_key == "da2019dual" + + 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 diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index 3b58d19..ea81bf2 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -25,14 +25,17 @@ class TopoART(BaseART): """Topo ART for Topological 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. + 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. + + .. bibliography:: ../../references.bib + :filter: citation_key == "tscherepanow2010topoart" 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 From 254c3cc2b7cf30429ee0379ca91bbc4027e238a9 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 13:56:15 -0500 Subject: [PATCH 103/139] run pre-commit --- artlib/fusion/FusionART.py | 1 - artlib/hierarchical/DeepARTMAP.py | 4 ++-- artlib/supervised/ARTMAP.py | 4 +--- artlib/supervised/SimpleARTMAP.py | 4 +--- references.bib | 2 +- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index 2d5ea71..ae43ffb 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -10,7 +10,6 @@ .. bibliography:: ../../references.bib :filter: citation_key == "tan2007intelligence" - """ import numpy as np from typing import Optional, Union, Callable, List, Literal, Tuple, Dict diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index 98683fc..ae6c940 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -30,8 +30,8 @@ class DeepARTMAP(BaseEstimator, ClassifierMixin, ClusterMixin): and is an original creation of this library. .. # 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. + .. # 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. .. bibliography:: ../../references.bib diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index f899a74..5e975a6 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -255,9 +255,7 @@ def partial_fit( """ self.validate_data(X, y) - self.module_b.partial_fit( - y, match_tracking=match_tracking, epsilon=epsilon - ) + self.module_b.partial_fit(y, match_tracking=match_tracking, epsilon=epsilon) super(ARTMAP, self).partial_fit( X, self.labels_b, diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index fe74739..18ef884 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -315,9 +315,7 @@ def partial_fit( ) for i, (x, c_b) in enumerate(zip(X, y)): self.module_a.pre_step_fit(X) - c_a = self.step_fit( - x, c_b, match_tracking=match_tracking, epsilon=epsilon - ) + c_a = self.step_fit(x, c_b, match_tracking=match_tracking, epsilon=epsilon) self.module_a.labels_[i + j] = c_a self.module_a.post_step_fit(X) return self diff --git a/references.bib b/references.bib index d797762..3188944 100644 --- a/references.bib +++ b/references.bib @@ -193,4 +193,4 @@ @inproceedings{tscherepanow2010topoart pages={157--167}, year={2010}, organization={Springer} -} \ No newline at end of file +} From 30cd08cea3b8f9b5199d9e7b910db41dba408faa Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 14:07:47 -0500 Subject: [PATCH 104/139] add spacing --- artlib/elementary/ART1.py | 2 ++ artlib/elementary/ART2.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index 0a7938d..4eee54b 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -22,6 +22,7 @@ class ART1(BaseART): 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. @@ -31,6 +32,7 @@ class ART1(BaseART): .. bibliography:: ../../references.bib :filter: citation_key == "carpenter1987massively" + ART1 is exclusively for clustering binary data. """ diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index f9ef370..8331bfd 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -35,6 +35,7 @@ class ART2A(BaseART): This module implements ART2-A as first published in: + .. # Carpenter, G. A., & Grossberg, S. (1987b). .. # ART 2: self-organization of stable category recognition codes for analog input .. # patterns. @@ -50,6 +51,7 @@ class ART2A(BaseART): (citation_key == "carpenter1987art" or \ citation_key == "carpenter1991art") + ART2-A is similar to ART1 but designed for analog data. This method is implemented for historical purposes and is not recommended for use. From 8d5be137d77df0cfcc095879558853fa415727ca Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 14:20:12 -0500 Subject: [PATCH 105/139] use citation instead --- artlib/elementary/ART1.py | 19 +++++-------------- artlib/elementary/ART2.py | 6 +----- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index 4eee54b..50e6ea0 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -1,13 +1,8 @@ -"""ART1. +"""ART1 :cite:`carpenter1987massively`. -.. # 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. - -.. bibliography:: ../../references.bib - :filter: citation_key == "carpenter1987massively" +.. # 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. """ @@ -20,7 +15,7 @@ class ART1(BaseART): """ART1 for Binary Clustering. - This module implements ART1 as first published in: + This module implements ART1 as first published in :cite:`carpenter1987massively`. .. # Carpenter, G. A., & Grossberg, S. (1987a). @@ -29,10 +24,6 @@ class ART1(BaseART): .. # Computer Vision, Graphics, and Image .. # Processing, 37, 54 – 115. doi:10. 1016/S0734-189X(87)80014-2. - .. bibliography:: ../../references.bib - :filter: citation_key == "carpenter1987massively" - - ART1 is exclusively for clustering binary data. """ diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index 8331bfd..3cb7ad6 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -34,6 +34,7 @@ class ART2A(BaseART): """ART2-A for Clustering. This module implements ART2-A as first published in: + :cite:`carpenter1987art`, :cite:`carpenter1991art` .. # Carpenter, G. A., & Grossberg, S. (1987b). @@ -46,11 +47,6 @@ class ART2A(BaseART): .. # recognition. .. # Neural Networks, 4, 493 – 504. doi:10.1016/0893-6080(91) 90045-7. - .. bibliography:: ../../references.bib - :filter: \ - (citation_key == "carpenter1987art" or \ - citation_key == "carpenter1991art") - ART2-A is similar to ART1 but designed for analog data. This method is implemented for historical purposes and is not recommended for use. From 0fb70025b8d6cf8ffefedf7ba0d57f5ad2a631d7 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 14:57:06 -0500 Subject: [PATCH 106/139] use comment block for citations --- artlib/common/VAT.py | 10 ++----- artlib/elementary/ART1.py | 14 ++++----- artlib/elementary/ART2.py | 38 +++++++++++-------------- artlib/elementary/BayesianART.py | 19 ++++--------- artlib/elementary/EllipsoidART.py | 33 +++++++-------------- artlib/elementary/FuzzyART.py | 19 ++++--------- artlib/elementary/GaussianART.py | 20 ++++--------- artlib/elementary/HypersphereART.py | 19 ++++--------- artlib/elementary/QuadraticNeuronART.py | 30 ++++++------------- artlib/fusion/FusionART.py | 23 ++++++--------- artlib/hierarchical/DeepARTMAP.py | 32 ++++++++------------- artlib/hierarchical/SMART.py | 23 +++++---------- artlib/reinforcement/FALCON.py | 32 +++++++-------------- artlib/supervised/ARTMAP.py | 21 ++++---------- artlib/supervised/SimpleARTMAP.py | 18 ++++-------- artlib/topological/DualVigilanceART.py | 19 ++++--------- artlib/topological/TopoART.py | 24 ++++++---------- 17 files changed, 132 insertions(+), 262 deletions(-) diff --git a/artlib/common/VAT.py b/artlib/common/VAT.py index 9e9d23f..17d8b79 100644 --- a/artlib/common/VAT.py +++ b/artlib/common/VAT.py @@ -1,12 +1,8 @@ """VAT. -.. # Bezdek, J. C., & Hathaway, R. J. (2002). -.. # VAT: A tool for visual assessment of cluster tendency. -.. # Proceedings of the 2002 International Joint Conference on Neural Networks. -.. # doi:10.1109/IJCNN.2002.1007487 - -.. bibliography:: ../../references.bib - :filter: citation_key == "bezdek2002vat" +.. # Bezdek, J. C., & Hathaway, R. J. (2002). .. # VAT: A tool for visual assessment of +cluster tendency. .. # Proceedings of the 2002 International Joint Conference on Neural +Networks. .. # doi:10.1109/IJCNN.2002.1007487 """ import numpy as np diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index 50e6ea0..bc6b1c2 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -1,10 +1,10 @@ -"""ART1 :cite:`carpenter1987massively`. +"""ART1 :cite:`carpenter1987massively`.""" -.. # 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. - -""" +# 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. import numpy as np from typing import Optional, List, Tuple, Union, Dict @@ -15,7 +15,7 @@ class ART1(BaseART): """ART1 for Binary Clustering. - This module implements ART1 as first published in :cite:`carpenter1987massively`. + This module implements ART1 as first published in: :cite:`carpenter1987massively`. .. # Carpenter, G. A., & Grossberg, S. (1987a). diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index 3cb7ad6..7f3b4b1 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -1,29 +1,25 @@ -"""ART2. +"""ART2 :cite:`carpenter1987art`, :cite:`carpenter1991art`. -.. # Carpenter, G. A., & Grossberg, S. (1987b). -.. # ART 2: self-organization of stable category recognition codes for analog input -.. # patterns. -.. # Appl. Opt., 26, 4919–4930. doi:10.1364/AO.26.004919. +:: + ================================================================== + DISCLAIMER: DO NOT USE ART2!!! + IT DOES NOT WORK + It is provided for completeness only. + Stephan Grossberg himself has said ART2 does not work. + ================================================================== -.. # 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. - -.. bibliography:: ../../references.bib - :filter: \ - (citation_key == "carpenter1987art" or \ - citation_key == "carpenter1991art") +""" +# Carpenter, G. A., & Grossberg, S. (1987b). +# ART 2: self-organization of stable category recognition codes for analog input +# patterns. +# Appl. Opt., 26, 4919–4930. doi:10.1364/AO.26.004919. -================================================================== -DISCLAIMER: DO NOT USE ART2!!! -IT DOES NOT WORK -It is provided for completeness only. -Stephan Grossberg himself has said ART2 does not work. -================================================================== +# 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. -""" import numpy as np from typing import Optional, List from warnings import warn diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index 879b1a6..3d8e3ad 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -1,14 +1,9 @@ -"""Bayesian ART. +"""Bayesian ART :cite:`vigdor2007bayesian`.""" +# Vigdor, B., & Lerner, B. (2007). +# The Bayesian ARTMAP. +# IEEE Transactions on Neural +# Networks, 18, 1628–1644. doi:10.1109/TNN.2007.900234. -.. # Vigdor, B., & Lerner, B. (2007). -.. # The Bayesian ARTMAP. -.. # IEEE Transactions on Neural -.. # Networks, 18, 1628–1644. doi:10.1109/TNN.2007.900234. - -.. bibliography:: ../../references.bib - :filter: citation_key == "vigdor2007bayesian" - -""" import numpy as np from typing import Optional, Iterable, List, Callable, Literal, Tuple, Union, Dict import operator @@ -21,15 +16,13 @@ class BayesianART(BaseART): """Bayesian ART for Clustering. This module implements Bayesian ART as first published in: + :cite:`vigdor2007bayesian`. .. # Vigdor, B., & Lerner, B. (2007). .. # The Bayesian ARTMAP. .. # IEEE Transactions on Neural .. # Networks, 18, 1628–1644. doi:10.1109/TNN.2007.900234. - .. bibliography:: ../../references.bib - :filter: citation_key == "vigdor2007bayesian" - 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. diff --git a/artlib/elementary/EllipsoidART.py b/artlib/elementary/EllipsoidART.py index 9baccc9..0c5b9d2 100644 --- a/artlib/elementary/EllipsoidART.py +++ b/artlib/elementary/EllipsoidART.py @@ -1,21 +1,14 @@ -"""Ellipsoid ART. +"""Ellipsoid ART :cite:`anagnostopoulos2001a`, :cite:`anagnostopoulos2001b`.""" +# 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. -.. # 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. +# Anagnostopoulos, G. C., & Georgiopoulos, M. (2001b). +# Ellipsoid ART and ARTMAP for incremental unsupervised and supervised learning. +# In Aerospace/Defense Sensing, Simulation, and Controls (pp. 293– 304). +# International Society for Optics and Photonics. doi:10.1117/12.421180. -.. # Anagnostopoulos, G. C., & Georgiopoulos, M. (2001b). -.. # Ellipsoid ART and ARTMAP for incremental unsupervised and supervised learning. -.. # In Aerospace/Defense Sensing, Simulation, and Controls (pp. 293– 304). -.. # International Society for Optics and Photonics. doi:10.1117/12.421180. - -.. bibliography:: ../../references.bib - :filter: \ - (citation_key == "anagnostopoulos2001a" or \ - citation_key == "anagnostopoulos2001b") - -""" import numpy as np from typing import Optional, List, Tuple, Union, Dict from matplotlib.axes import Axes @@ -27,8 +20,9 @@ class EllipsoidART(BaseART): """Ellipsoid ART for Clustering. This module implements Ellipsoid ART as first published in: + :cite:`anagnostopoulos2001a`, :cite:`anagnostopoulos2001b`. - .. # Anagnostopoulos, G. C., & Georgiopoulos, M. (2001a). + .. # 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. @@ -38,11 +32,6 @@ class EllipsoidART(BaseART): .. # In Aerospace/Defense Sensing, Simulation, and Controls (pp. 293– 304). .. # International Society for Optics and Photonics. doi:10.1117/12.421180. - .. bibliography:: ../../references.bib - :filter: \ - (citation_key == "anagnostopoulos2001a" or \ - citation_key == "anagnostopoulos2001b") - 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. diff --git a/artlib/elementary/FuzzyART.py b/artlib/elementary/FuzzyART.py index 41ee2bb..5410b7e 100644 --- a/artlib/elementary/FuzzyART.py +++ b/artlib/elementary/FuzzyART.py @@ -1,14 +1,9 @@ -"""Fuzzy ART. +"""Fuzzy ART :cite:`carpenter1991fuzzy`.""" +# 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. -.. # 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. - -.. bibliography:: ../../references.bib - :filter: citation_key == "carpenter1991fuzzy" - -""" import numpy as np from typing import Optional, Iterable, List, Tuple, Union, Dict from matplotlib.axes import Axes @@ -62,15 +57,13 @@ class FuzzyART(BaseART): """Fuzzy ART for Clustering. This module implements Fuzzy ART as first published in: + :cite:`carpenter1991fuzzy`. .. # 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. - .. bibliography:: ../../references.bib - :filter: citation_key == "carpenter1991fuzzy" - Fuzzy ART is a hyper-box based clustering method that is exceptionally fast and explainable. diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index a2f6e7b..7380d0e 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -1,14 +1,8 @@ -"""Gaussian ART. - -.. # 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. - -.. bibliography:: ../../references.bib - :filter: citation_key == "williamson1996gaussian" - -""" +"""Gaussian ART :cite:`williamson1996gaussian`.""" +# 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. import numpy as np from typing import Optional, Iterable, List, Tuple, Union, Dict @@ -21,15 +15,13 @@ class GaussianART(BaseART): """Gaussian ART for Clustering. This module implements Gaussian ART as first published in: + :cite:`williamson1996gaussian`. .. # 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. - .. bibliography:: ../../references.bib - :filter: citation_key == "williamson1996gaussian" - 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. diff --git a/artlib/elementary/HypersphereART.py b/artlib/elementary/HypersphereART.py index e46b74d..73ff933 100644 --- a/artlib/elementary/HypersphereART.py +++ b/artlib/elementary/HypersphereART.py @@ -1,14 +1,9 @@ -"""Hyperpshere ART. +"""Hyperpshere ART :cite:`anagnostopoulos2000hypersphere`.""" +# 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. -.. # 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. - -.. bibliography:: ../../references.bib - :filter: citation_key == "anagnostopoulos2000hypersphere" - -""" import numpy as np from typing import Optional, Iterable, List from matplotlib.axes import Axes @@ -20,6 +15,7 @@ class HypersphereART(BaseART): """Hypersphere ART for Clustering. This module implements Ellipsoid ART as first published in: + :cite:`anagnostopoulos2000hypersphere`. .. # Anagnostopoulos, G. C., & Georgiopulos, M. (2000). .. # Hypersphere ART and ARTMAP for unsupervised and supervised, incremental @@ -27,9 +23,6 @@ class HypersphereART(BaseART): .. # In Proc. IEEE International Joint Conference on Neural Networks (IJCNN) .. # (pp. 59–64). volume 6. doi:10.1109/IJCNN.2000.859373. - .. bibliography:: ../../references.bib - :filter: citation_key == "anagnostopoulos2000hypersphere" - Hyperpshere ART clusters data in Hyper-spheres similar to k-means with a dynamic k. """ diff --git a/artlib/elementary/QuadraticNeuronART.py b/artlib/elementary/QuadraticNeuronART.py index cf5273c..1d0c6f7 100644 --- a/artlib/elementary/QuadraticNeuronART.py +++ b/artlib/elementary/QuadraticNeuronART.py @@ -1,19 +1,11 @@ -"""Quadratic Neuron ART. +"""Quadratic Neuron ART :cite:`su2001application`, :cite:`su2005new`.""" +# Su, M.-C., & Liu, T.-K. (2001). +# Application of neural networks using quadratic junctions in cluster analysis. +# Neurocomputing, 37, 165 – 175. doi:10.1016/S0925-2312(00)00343-X. -.. # Su, M.-C., & Liu, T.-K. (2001). -.. # Application of neural networks using quadratic junctions in cluster analysis. -.. # Neurocomputing, 37, 165 – 175. doi:10.1016/S0925-2312(00)00343-X. - -.. # 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. - -.. bibliography:: ../../references.bib - :filter: \ - (citation_key == "su2001application" or \ - citation_key == "su2005new") - -""" +# 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. import numpy as np from typing import Optional, Iterable, List, Tuple, Union, Dict @@ -27,8 +19,9 @@ class QuadraticNeuronART(BaseART): """Quadratic Neuron ART for Clustering. This module implements Quadratic Neuron ART as first published in: + :cite:`su2001application`, :cite:`su2005new`. - .. # Su, M.-C., & Liu, T.-K. (2001). + .. # Su, M.-C., & Liu, T.-K. (2001). .. # Application of neural networks using quadratic junctions in cluster analysis. .. # Neurocomputing, 37, 165 – 175. doi:10.1016/S0925-2312(00)00343-X. @@ -36,11 +29,6 @@ class QuadraticNeuronART(BaseART): .. # A new approach to clustering data with arbitrary shapes. .. # Pattern Recognition, 38, 1887 – 1901. doi:10.1016/j.patcog.2005.04.010. - .. bibliography:: ../../references.bib - :filter: \ - (citation_key == "su2001application" or \ - citation_key == "su2005new") - Quadratic Neuron ART clusters data in Hyper-ellipsoid by utilizing a quadratic neural network for activation and resonance. diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index ae43ffb..58449ef 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -1,16 +1,11 @@ -"""Fusion ART. +"""Fusion ART :cite:`tan2007intelligence`.""" +# 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. -.. # 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. - -.. bibliography:: ../../references.bib - :filter: citation_key == "tan2007intelligence" - -""" import numpy as np from typing import Optional, Union, Callable, List, Literal, Tuple, Dict from copy import deepcopy @@ -49,6 +44,7 @@ class FusionART(BaseART): """Fusion ART for Data Fusion and Regression. This module implements Fusion ART as first described in: + :cite:`tan2007intelligence`. .. # Tan, A.-H., Carpenter, G. A., & Grossberg, S. (2007). .. # Intelligence Through Interaction: Towards a Unified Theory for Learning. @@ -57,9 +53,6 @@ class FusionART(BaseART): .. # Berlin, Heidelberg: Springer Berlin Heidelberg. .. # doi:10.1007/ 978-3-540-72383-7_128. - .. bibliography:: ../../references.bib - :filter: citation_key == "tan2007intelligence" - 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 diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index ae6c940..bcddacb 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -1,14 +1,8 @@ -"""Deep ARTMAP. - -.. # 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. - -.. bibliography:: ../../references.bib - :filter: citation_key == "carpenter1991artmap" - -""" +"""Deep ARTMAP :cite:`carpenter1991artmap`.""" +# 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. import numpy as np from sklearn.base import BaseEstimator, ClassifierMixin, ClusterMixin from typing import Optional, cast, Union, Literal, Tuple @@ -22,21 +16,19 @@ 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. + This module implements DeepARTMAP, a generalization of the ARTMAP class + :cite:`carpenter1991artmap` 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. .. # 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. - .. bibliography:: ../../references.bib - :filter: citation_key == "carpenter1991artmap" - """ def __init__(self, modules: list[BaseART]): diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index 1f0a2ca..a00c979 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -1,15 +1,9 @@ -"""SMART. - -.. # 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. - -.. bibliography:: ../../references.bib - :filter: citation_key == "bartfai1994hierarchical" - -""" +"""SMART :cite:`bartfai1994hierarchical`.""" +# 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. import numpy as np from typing import Union, Type, Optional, Literal, Tuple @@ -22,7 +16,7 @@ class SMART(DeepARTMAP): """SMART for Hierachical Clustering. - This module implements SMART as first published in: + This module implements SMART as first published in: :cite:`bartfai1994hierarchical` .. # Bartfai, G. (1994). .. # Hierarchical clustering with ART neural networks. @@ -30,9 +24,6 @@ class SMART(DeepARTMAP): .. # (pp. 940–944). volume 2. .. # doi:10.1109/ICNN.1994.374307. - .. bibliography:: ../../references.bib - :filter: citation_key == "bartfai1994hierarchical" - SMART accepts an uninstantiated 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 diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index e80c3a8..7895234 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -1,21 +1,13 @@ -"""FALCON. +"""FALCON :cite:`tan2004falcon`, :cite:`tan2008integrating`.""" +# 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. (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 - -.. bibliography:: ../../references.bib - :filter: \ - (citation_key == "tan2004falcon" or \ - citation_key == "tan2008integrating") - -""" +# 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 @@ -28,6 +20,7 @@ class FALCON: """FALCON for Reinforcement Learning. This module implements the reactive FALCON as first described in: + :cite:`tan2004falcon`, :cite:`tan2008integrating`. .. # Tan, A.-H. (2004). .. # FALCON: a fusion architecture for learning, cognition, and navigation. @@ -39,11 +32,6 @@ class FALCON: .. # Reinforcement Learning With Delayed Evaluative Feedback. .. # IEEE Transactions on Neural Networks, 19 , 230–244. doi:10.1109/TNN.2007.905839 - .. bibliography:: ../../references.bib - :filter: \ - (citation_key == "tan2004falcon" or \ - citation_key == "tan2008integrating") - 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. diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index 5e975a6..e719e7e 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -1,14 +1,8 @@ -"""ARTMAP. - -.. # 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. - -.. bibliography:: ../../references.bib - :filter: citation_key == "carpenter1991artmap" - -""" +"""ARTMAP :cite:`carpenter1991artmap`.""" +# 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. import numpy as np from typing import Literal, Tuple, Dict, Union, Optional from artlib.common.BaseART import BaseART @@ -19,16 +13,13 @@ class ARTMAP(SimpleARTMAP): """ARTMAP for Classification and Regression. - This module implements ARTMAP as first published in: + This module implements ARTMAP as first published in: :cite:`carpenter1991artmap`. .. # 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. - .. bibliography:: ../../references.bib - :filter: citation_key == "carpenter1991artmap" - 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 diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index 18ef884..79e073f 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -1,13 +1,7 @@ -"""Simple ARTMAP. - -.. # Serrano-Gotarredona, T., Linares-Barranco, B., & Andreou, A. G. (1998). -.. # Adaptive Resonance Theory Microchips: Circuit Design Techniques. -.. # Norwell, MA, USA: Kluwer Academic Publishers. - -.. bibliography:: ../../references.bib - :filter: citation_key == "gotarredona1998adaptive" - -""" +"""Simple ARTMAP :cite:`gotarredona1998adaptive`.""" +# Serrano-Gotarredona, T., Linares-Barranco, B., & Andreou, A. G. (1998). +# Adaptive Resonance Theory Microchips: Circuit Design Techniques. +# Norwell, MA, USA: Kluwer Academic Publishers. import numpy as np from typing import Optional, Literal, Dict, Union, Tuple from matplotlib.axes import Axes @@ -22,14 +16,12 @@ class SimpleARTMAP(BaseARTMAP): """SimpleARTMAP for Classification. This module implements SimpleARTMAP as first published in: + :cite:`gotarredona1998adaptive`. .. # Serrano-Gotarredona, T., Linares-Barranco, B., & Andreou, A. G. (1998). .. # Adaptive Resonance Theory Microchips: Circuit Design Techniques. .. # Norwell, MA, USA: Kluwer Academic Publishers. - .. bibliography:: ../../references.bib - :filter: citation_key == "gotarredona1998adaptive" - 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 diff --git a/artlib/topological/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index 85cda9c..7a828cd 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -1,13 +1,7 @@ -"""Dual Vigilance ART. - -.. # 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. - -.. bibliography:: ../../references.bib - :filter: citation_key == "da2019dual" - -""" +"""Dual Vigilance ART :cite:`da2019dual`.""" +# 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. import numpy as np from typing import Optional, Callable, List, Literal, Union, Dict from warnings import warn @@ -20,15 +14,12 @@ class DualVigilanceART(BaseART): """Dual Vigilance ART for Clustering. - This module implements Dual Vigilance ART as first published in: + This module implements Dual Vigilance ART as first published in: :cite:`da2019dual`. .. # 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. - .. bibliography:: ../../references.bib - :filter: citation_key == "da2019dual" - 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 diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index ea81bf2..4dd8ed3 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -1,16 +1,10 @@ -"""Topo ART. - -.. # 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. - -.. bibliography:: ../../references.bib - :filter: citation_key == "tscherepanow2010topoart" - -""" +"""Topo ART :cite:`tscherepanow2010topoart`.""" +# 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. import numpy as np from typing import Optional, Callable, List, Literal, Tuple, Union, Dict @@ -26,6 +20,7 @@ class TopoART(BaseART): """Topo ART for Topological Clustering. This module implements Topo ART as first published in: + :cite:`tscherepanow2010topoart`. .. # Tscherepanow, M. (2010). .. # TopoART: A Topology Learning Hierarchical ART Network. @@ -34,9 +29,6 @@ class TopoART(BaseART): .. # Berlin, Heidelberg: Springer Berlin Heidelberg. .. # doi:10.1007/978-3-642-15825-4_21. - .. bibliography:: ../../references.bib - :filter: citation_key == "tscherepanow2010topoart" - 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 From 791d52b179fd3eb23f118442df4d9eb46a4ee8db Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 15:01:47 -0500 Subject: [PATCH 107/139] disclaimer --- artlib/elementary/ART2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index 7f3b4b1..2d20695 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -1,6 +1,7 @@ """ART2 :cite:`carpenter1987art`, :cite:`carpenter1991art`. :: + ================================================================== DISCLAIMER: DO NOT USE ART2!!! IT DOES NOT WORK From 26acdc590ef380d4c178b5b509bfb2513ff998d1 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 15:18:35 -0500 Subject: [PATCH 108/139] class cross references --- artlib/elementary/ART2.py | 4 ++-- artlib/elementary/BayesianART.py | 4 ++-- artlib/elementary/GaussianART.py | 5 +++-- artlib/hierarchical/DeepARTMAP.py | 6 ++--- artlib/hierarchical/SMART.py | 4 ++-- artlib/reinforcement/FALCON.py | 31 +++++++++++++------------- artlib/reinforcement/__init__.py | 1 + artlib/supervised/ARTMAP.py | 14 ++++++------ artlib/supervised/SimpleARTMAP.py | 11 ++++----- artlib/topological/DualVigilanceART.py | 15 +++++++------ artlib/topological/TopoART.py | 9 ++++---- artlib/topological/__init__.py | 10 ++++----- 12 files changed, 59 insertions(+), 55 deletions(-) diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index 2d20695..2b841ad 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -45,8 +45,8 @@ 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. + ART2-A is similar to :class:`ART1` but designed for analog data. This method is + implemented for historical purposes and is not recommended for use. """ diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index 3d8e3ad..a47a19a 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -24,8 +24,8 @@ class BayesianART(BaseART): .. # 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. + similar to :class:`GaussianART` but differs in that it allows arbitrary rotation of + the hyper-ellipsoid. """ diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index 7380d0e..89cfb9b 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -23,8 +23,9 @@ class GaussianART(BaseART): .. # 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. + similar to :class:`BayesianART` but differs in that the hyper-ellipsoid always have + their principal axes square to the coordinate frame. It is also faster than + Bayesian ART. """ diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index bcddacb..116365b 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -16,12 +16,12 @@ class DeepARTMAP(BaseEstimator, ClassifierMixin, ClusterMixin): """DeepARTMAP for Hierachical Supervised and Unsupervised Learning. - This module implements DeepARTMAP, a generalization of the ARTMAP class + This module implements DeepARTMAP, a generalization of the :class:`ARTMAP` class :cite:`carpenter1991artmap` 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 + :class:`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. .. # Carpenter, G. A., Grossberg, S., & Reynolds, J. H. (1991a). diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index a00c979..81188b9 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -26,8 +26,8 @@ class SMART(DeepARTMAP): SMART accepts an uninstantiated 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. + in their restrictiveness. SMART is a special case of :class:`DeepARTMAP`, which + forms the backbone of this class, where all channels receive the same data. """ diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index 7895234..e33a891 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -20,21 +20,16 @@ class FALCON: """FALCON for Reinforcement Learning. This module implements the reactive FALCON as first described in: - :cite:`tan2004falcon`, :cite:`tan2008integrating`. + :cite:`tan2004falcon`. .. # 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 - - 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. + FALCON is based on a :class:`FusionART` backbone but only accepts 3 channels: + State, Action, and Reward. Specific functions are implemented for getting optimal + reward and action predictions. """ @@ -297,13 +292,17 @@ def get_rewards(self, states: np.ndarray, actions: np.ndarray) -> np.ndarray: 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. + This module implements TD-FALCON as first described in: + :cite:`tan2008integrating`. + + .. # 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 :class:`FALCON` backbone but includes specific function for + temporal-difference learning. Currently, only SARSA is implemented and only + :class:`FuzzyART` base modules are supported. """ diff --git a/artlib/reinforcement/__init__.py b/artlib/reinforcement/__init__.py index 93dc41b..fb10e97 100644 --- a/artlib/reinforcement/__init__.py +++ b/artlib/reinforcement/__init__.py @@ -13,6 +13,7 @@ The modules herein only provide for reactive and SARSA style learning. `SARSA `_ + `Reactive agents `_ """ diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index e719e7e..d89b6fe 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -20,13 +20,13 @@ class ARTMAP(SimpleARTMAP): .. # 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. + ARTMAP accepts two :class:`BaseART` 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 :class:`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, :class:`FusionART` provides + substantially better fit for regression problems which are not monotonic. """ diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index 79e073f..4714035 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -22,11 +22,12 @@ class SimpleARTMAP(BaseARTMAP): .. # 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. + SimpleARTMAP is a special case of :class:`ARTMAP` specifically for + classification. It allows the clustering of data samples while enforcing a + many-to-one mapping from sample clusters to labels. It accepts an instantiated + :class:`BaseART` 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. """ diff --git a/artlib/topological/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index 7a828cd..a349a99 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -20,13 +20,14 @@ class DualVigilanceART(BaseART): .. # 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. + Dual Vigilance ART allows a :class:`BaseART` 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 :class:`FuzzyART`, a Dual Vigilance Fuzzy ART + clustering result would look like a series of hyper-boxes forming an arbitrary + geometry. """ diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index 4dd8ed3..1e2932b 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -29,10 +29,11 @@ class TopoART(BaseART): .. # 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. + Topo ART clusters accepts an instatiated :class:`BaseART` 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. """ diff --git a/artlib/topological/__init__.py b/artlib/topological/__init__.py index 72f9273..4e2d9d0 100644 --- a/artlib/topological/__init__.py +++ b/artlib/topological/__init__.py @@ -5,11 +5,11 @@ 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 herein 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. +The two modules herein provide contrasting advantages. :class:`TopoART` allows for the +creation of an adjacency matrix which can be useful when clusters overlap or are in +close proximity. :class:`DualVigilanceART` 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 `_ From 3523314d47224b341c2747f57d1023fbfa863ce6 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 15:36:14 -0500 Subject: [PATCH 109/139] class cross references --- artlib/elementary/ART2.py | 5 +++-- artlib/elementary/BayesianART.py | 4 ++-- artlib/elementary/GaussianART.py | 6 +++--- artlib/hierarchical/DeepARTMAP.py | 15 ++++++++------- artlib/hierarchical/SMART.py | 9 +++++---- artlib/reinforcement/FALCON.py | 6 +++--- artlib/supervised/ARTMAP.py | 9 +++++---- artlib/supervised/SimpleARTMAP.py | 13 +++++++------ artlib/topological/DualVigilanceART.py | 14 ++++++++------ artlib/topological/TopoART.py | 10 +++++----- artlib/topological/__init__.py | 7 ++++--- 11 files changed, 53 insertions(+), 45 deletions(-) diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index 2b841ad..f23f5d5 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -45,8 +45,9 @@ class ART2A(BaseART): .. # Neural Networks, 4, 493 – 504. doi:10.1016/0893-6080(91) 90045-7. - ART2-A is similar to :class:`ART1` but designed for analog data. This method is - implemented for historical purposes and is not recommended for use. + ART2-A is similar to :class:`artlib.elementary.ART1.ART1` but designed for analog + data. This method is implemented for historical purposes and is not recommended + for use. """ diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index a47a19a..14ce695 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -24,8 +24,8 @@ class BayesianART(BaseART): .. # Networks, 18, 1628–1644. doi:10.1109/TNN.2007.900234. Bayesian ART clusters data in Bayesian Distributions (Hyper-ellipsoids) and is - similar to :class:`GaussianART` but differs in that it allows arbitrary rotation of - the hyper-ellipsoid. + similar to :class:`artlib.elementary.GaussianART.GaussianART` but differs in that it + allows arbitrary rotation of the hyper-ellipsoid. """ diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index 89cfb9b..e3b5960 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -23,9 +23,9 @@ class GaussianART(BaseART): .. # 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 :class:`BayesianART` but differs in that the hyper-ellipsoid always have - their principal axes square to the coordinate frame. It is also faster than - Bayesian ART. + similar to :class:`artlib.elementary.BayesianART.BayesianART` but differs in that + the hyper-ellipsoid always have their principal axes square to the coordinate + frame. It is also faster than :class:`artlib.elementary.BayesianART.BayesianART`. """ diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index 116365b..4d90983 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -16,13 +16,14 @@ class DeepARTMAP(BaseEstimator, ClassifierMixin, ClusterMixin): """DeepARTMAP for Hierachical Supervised and Unsupervised Learning. - This module implements DeepARTMAP, a generalization of the :class:`ARTMAP` class - :cite:`carpenter1991artmap` 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 - :class:`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. + This module implements DeepARTMAP, a generalization of the + :class:`artlib.supervised.ARTMAP.ARTMAP` class :cite:`carpenter1991artmap` 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 :class:`artlib.supervised.ARTMAP.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. .. # Carpenter, G. A., Grossberg, S., & Reynolds, J. H. (1991a). .. # ARTMAP: Supervised real-time learning and classification of nonstationary data diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index 81188b9..28db2e1 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -24,10 +24,11 @@ class SMART(DeepARTMAP): .. # (pp. 940–944). volume 2. .. # doi:10.1109/ICNN.1994.374307. - SMART accepts an uninstantiated 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 :class:`DeepARTMAP`, which - forms the backbone of this class, where all channels receive the same data. + SMART accepts an uninstantiated :class:`artlib.common.BaseART.BaseART` 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 :class:`artlib.hierarchical.DeepARTMAP.DeepARTMAP`, which forms the backbone + of this class, where all channels receive the same data. """ diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index e33a891..b8fbd48 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -27,9 +27,9 @@ class FALCON: .. # 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 :class:`FusionART` backbone but only accepts 3 channels: - State, Action, and Reward. Specific functions are implemented for getting optimal - reward and action predictions. + FALCON is based on a :class:`artlib.fusion.FusionART.FusionART` backbone but only + accepts 3 channels: State, Action, and Reward. Specific functions are implemented + for getting optimal reward and action predictions. """ diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index d89b6fe..999a9b1 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -23,10 +23,11 @@ class ARTMAP(SimpleARTMAP): ARTMAP accepts two :class:`BaseART` 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 :class:`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, :class:`FusionART` provides - substantially better fit for regression problems which are not monotonic. + use :class:`artlib.supervised.SimpleARTMAP.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, + :class:`artlib.fusion.FusionART.FusionART` provides substantially better fit for + regression problems which are not monotonic. """ diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index 4714035..66a1017 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -22,12 +22,13 @@ class SimpleARTMAP(BaseARTMAP): .. # Adaptive Resonance Theory Microchips: Circuit Design Techniques. .. # Norwell, MA, USA: Kluwer Academic Publishers. - SimpleARTMAP is a special case of :class:`ARTMAP` specifically for - classification. It allows the clustering of data samples while enforcing a - many-to-one mapping from sample clusters to labels. It accepts an instantiated - :class:`BaseART` 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. + SimpleARTMAP is a special case of :class:`artlib.supervised.ARTMAP.ARTMAP` + specifically for classification. It allows the clustering of data samples while + enforcing a many-to-one mapping from sample clusters to labels. It accepts an + instantiated :class:`artlib.common.BaseART.BaseART` module and dynamically adapts + the vigilance function to preventresonance when the many-to-one mapping is + violated. This enables SimpleARTMAP to identify discrete clusters belonging to + each category label. """ diff --git a/artlib/topological/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index a349a99..df9fb6f 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -20,12 +20,14 @@ class DualVigilanceART(BaseART): .. # Dual vigilance fuzzy adaptive resonance theory. .. # Neural Networks, 109, 1–5. doi:10.1016/j.neunet.2018.09.015. - Dual Vigilance ART allows a :class:`BaseART` 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 :class:`FuzzyART`, a Dual Vigilance Fuzzy ART + Dual Vigilance ART allows a :class:`artlib.common.BaseART.BaseART` module to + cluster with both an upper and lower vigilance value. The upper-vigilance value + allows the :class:`artlib.common.BaseART.BaseART` 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 + :class:`artlib.common.BaseART.BaseART` module is + :class:`artlib.elementary.FuzzyART.FuzzyART`, a Dual Vigilance Fuzzy ART clustering result would look like a series of hyper-boxes forming an arbitrary geometry. diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index 1e2932b..0be2368 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -29,11 +29,11 @@ class TopoART(BaseART): .. # Berlin, Heidelberg: Springer Berlin Heidelberg. .. # doi:10.1007/978-3-642-15825-4_21. - Topo ART clusters accepts an instatiated :class:`BaseART` 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. + Topo ART clusters accepts an instantiated :class:`artlib.common.BaseART.BaseART` + 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. """ diff --git a/artlib/topological/__init__.py b/artlib/topological/__init__.py index 4e2d9d0..0866df7 100644 --- a/artlib/topological/__init__.py +++ b/artlib/topological/__init__.py @@ -5,9 +5,10 @@ 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 herein provide contrasting advantages. :class:`TopoART` allows for the -creation of an adjacency matrix which can be useful when clusters overlap or are in -close proximity. :class:`DualVigilanceART` allows for the abstract merging of many +The two modules herein provide contrasting advantages. +:class:`artlib.topological.TopoART` allows for the creation of an adjacency matrix +which can be useful when clusters overlap or are in close proximity. +:class:`artlib.topological.DualVigilanceART` 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. From 78992702a55d29ca6af7d97890d43ef663684f42 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Thu, 17 Oct 2024 15:41:25 -0500 Subject: [PATCH 110/139] class cross references --- artlib/elementary/ART2.py | 2 +- artlib/elementary/BayesianART.py | 4 ++-- artlib/elementary/GaussianART.py | 4 ++-- artlib/hierarchical/DeepARTMAP.py | 4 ++-- artlib/hierarchical/SMART.py | 6 +++--- artlib/reinforcement/FALCON.py | 4 ++-- artlib/supervised/ARTMAP.py | 7 ++++--- artlib/supervised/SimpleARTMAP.py | 6 +++--- artlib/topological/DualVigilanceART.py | 8 ++++---- artlib/topological/TopoART.py | 2 +- artlib/topological/__init__.py | 10 +++++----- 11 files changed, 29 insertions(+), 28 deletions(-) diff --git a/artlib/elementary/ART2.py b/artlib/elementary/ART2.py index f23f5d5..e734410 100644 --- a/artlib/elementary/ART2.py +++ b/artlib/elementary/ART2.py @@ -45,7 +45,7 @@ class ART2A(BaseART): .. # Neural Networks, 4, 493 – 504. doi:10.1016/0893-6080(91) 90045-7. - ART2-A is similar to :class:`artlib.elementary.ART1.ART1` but designed for analog + ART2-A is similar to :class:`~artlib.elementary.ART1.ART1` but designed for analog data. This method is implemented for historical purposes and is not recommended for use. diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index 14ce695..6b19f9b 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -24,8 +24,8 @@ class BayesianART(BaseART): .. # Networks, 18, 1628–1644. doi:10.1109/TNN.2007.900234. Bayesian ART clusters data in Bayesian Distributions (Hyper-ellipsoids) and is - similar to :class:`artlib.elementary.GaussianART.GaussianART` but differs in that it - allows arbitrary rotation of the hyper-ellipsoid. + similar to :class:`~artlib.elementary.GaussianART.GaussianART` but differs in that + it allows arbitrary rotation of the hyper-ellipsoid. """ diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index e3b5960..34a917b 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -23,9 +23,9 @@ class GaussianART(BaseART): .. # 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 :class:`artlib.elementary.BayesianART.BayesianART` but differs in that + similar to :class:`~artlib.elementary.BayesianART.BayesianART` but differs in that the hyper-ellipsoid always have their principal axes square to the coordinate - frame. It is also faster than :class:`artlib.elementary.BayesianART.BayesianART`. + frame. It is also faster than :class:`~artlib.elementary.BayesianART.BayesianART`. """ diff --git a/artlib/hierarchical/DeepARTMAP.py b/artlib/hierarchical/DeepARTMAP.py index 4d90983..b11475a 100644 --- a/artlib/hierarchical/DeepARTMAP.py +++ b/artlib/hierarchical/DeepARTMAP.py @@ -17,10 +17,10 @@ class DeepARTMAP(BaseEstimator, ClassifierMixin, ClusterMixin): """DeepARTMAP for Hierachical Supervised and Unsupervised Learning. This module implements DeepARTMAP, a generalization of the - :class:`artlib.supervised.ARTMAP.ARTMAP` class :cite:`carpenter1991artmap` that + :class:`~artlib.supervised.ARTMAP.ARTMAP` class :cite:`carpenter1991artmap` 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 :class:`artlib.supervised.ARTMAP.ARTMAP` + provided, DeepARTMAP reverts to standard :class:`~artlib.supervised.ARTMAP.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. diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index 28db2e1..8952607 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -24,11 +24,11 @@ class SMART(DeepARTMAP): .. # (pp. 940–944). volume 2. .. # doi:10.1109/ICNN.1994.374307. - SMART accepts an uninstantiated :class:`artlib.common.BaseART.BaseART` class and + SMART accepts an uninstantiated :class:`~artlib.common.BaseART.BaseART` 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 :class:`artlib.hierarchical.DeepARTMAP.DeepARTMAP`, which forms the backbone - of this class, where all channels receive the same data. + case of :class:`~artlib.hierarchical.DeepARTMAP.DeepARTMAP`, which forms the + backbone of this class, where all channels receive the same data. """ diff --git a/artlib/reinforcement/FALCON.py b/artlib/reinforcement/FALCON.py index b8fbd48..8946ffb 100644 --- a/artlib/reinforcement/FALCON.py +++ b/artlib/reinforcement/FALCON.py @@ -27,7 +27,7 @@ class FALCON: .. # 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 :class:`artlib.fusion.FusionART.FusionART` backbone but only + FALCON is based on a :class:`~artlib.fusion.FusionART.FusionART` backbone but only accepts 3 channels: State, Action, and Reward. Specific functions are implemented for getting optimal reward and action predictions. @@ -302,7 +302,7 @@ class TD_FALCON(FALCON): TD-FALCON is based on a :class:`FALCON` backbone but includes specific function for temporal-difference learning. Currently, only SARSA is implemented and only - :class:`FuzzyART` base modules are supported. + :class:`~artlib.elementary.FuzzyART.FuzzyART` base modules are supported. """ diff --git a/artlib/supervised/ARTMAP.py b/artlib/supervised/ARTMAP.py index 999a9b1..a24a389 100644 --- a/artlib/supervised/ARTMAP.py +++ b/artlib/supervised/ARTMAP.py @@ -20,13 +20,14 @@ class ARTMAP(SimpleARTMAP): .. # by a self-organizing neural network. .. # Neural Networks, 4, 565 – 588. doi:10.1016/0893-6080(91)90012-T. - ARTMAP accepts two :class:`BaseART` modules A and B which cluster the dependent + ARTMAP accepts two :class:`~artlib.common.BaseART.BaseART` 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 :class:`artlib.supervised.SimpleARTMAP.SimpleARTMAP` for a faster and more + use :class:`~artlib.supervised.SimpleARTMAP.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, - :class:`artlib.fusion.FusionART.FusionART` provides substantially better fit for + :class:`~artlib.fusion.FusionART.FusionART` provides substantially better fit for regression problems which are not monotonic. """ diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index 66a1017..f77eff0 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -22,11 +22,11 @@ class SimpleARTMAP(BaseARTMAP): .. # Adaptive Resonance Theory Microchips: Circuit Design Techniques. .. # Norwell, MA, USA: Kluwer Academic Publishers. - SimpleARTMAP is a special case of :class:`artlib.supervised.ARTMAP.ARTMAP` + SimpleARTMAP is a special case of :class:`~artlib.supervised.ARTMAP.ARTMAP` specifically for classification. It allows the clustering of data samples while enforcing a many-to-one mapping from sample clusters to labels. It accepts an - instantiated :class:`artlib.common.BaseART.BaseART` module and dynamically adapts - the vigilance function to preventresonance when the many-to-one mapping is + instantiated :class:`~artlib.common.BaseART.BaseART` 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. diff --git a/artlib/topological/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index df9fb6f..29ea422 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -20,14 +20,14 @@ class DualVigilanceART(BaseART): .. # Dual vigilance fuzzy adaptive resonance theory. .. # Neural Networks, 109, 1–5. doi:10.1016/j.neunet.2018.09.015. - Dual Vigilance ART allows a :class:`artlib.common.BaseART.BaseART` module to + Dual Vigilance ART allows a :class:`~artlib.common.BaseART.BaseART` module to cluster with both an upper and lower vigilance value. The upper-vigilance value - allows the :class:`artlib.common.BaseART.BaseART` module to cluster normally, + allows the :class:`~artlib.common.BaseART.BaseART` 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 - :class:`artlib.common.BaseART.BaseART` module is - :class:`artlib.elementary.FuzzyART.FuzzyART`, a Dual Vigilance Fuzzy ART + :class:`~artlib.common.BaseART.BaseART` module is + :class:`~artlib.elementary.FuzzyART.FuzzyART`, a Dual Vigilance Fuzzy ART clustering result would look like a series of hyper-boxes forming an arbitrary geometry. diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index 0be2368..35efe23 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -29,7 +29,7 @@ class TopoART(BaseART): .. # Berlin, Heidelberg: Springer Berlin Heidelberg. .. # doi:10.1007/978-3-642-15825-4_21. - Topo ART clusters accepts an instantiated :class:`artlib.common.BaseART.BaseART` + Topo ART clusters accepts an instantiated :class:`~artlib.common.BaseART.BaseART` 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 diff --git a/artlib/topological/__init__.py b/artlib/topological/__init__.py index 0866df7..e8e583a 100644 --- a/artlib/topological/__init__.py +++ b/artlib/topological/__init__.py @@ -6,11 +6,11 @@ and Mapper, are often used in fields like data analysis and computational topology. The two modules herein provide contrasting advantages. -:class:`artlib.topological.TopoART` allows for the creation of an adjacency matrix -which can be useful when clusters overlap or are in close proximity. -:class:`artlib.topological.DualVigilanceART` 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. +:class:`~artlib.topological.TopoART.TopoART` allows for the creation of an adjacency +matrix which can be useful when clusters overlap or are in close proximity. +:class:`~artlib.topological.DualVigilanceART.DualVigilanceART` 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 `_ From 4c3bfae18b045b6bb7410b9bf06ed16e27e74881 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 18 Oct 2024 12:20:41 -0500 Subject: [PATCH 111/139] revert match_criterion typing --- artlib/common/BaseART.py | 2 +- artlib/elementary/ART1.py | 4 ++-- artlib/elementary/BayesianART.py | 2 +- artlib/elementary/EllipsoidART.py | 4 ++-- artlib/elementary/FuzzyART.py | 4 ++-- artlib/elementary/GaussianART.py | 4 ++-- artlib/elementary/QuadraticNeuronART.py | 4 ++-- artlib/experimental/ConvexHullART.py | 2 +- artlib/experimental/SeqART.py | 2 +- artlib/fusion/FusionART.py | 8 ++++---- artlib/topological/TopoART.py | 2 +- 11 files changed, 19 insertions(+), 19 deletions(-) diff --git a/artlib/common/BaseART.py b/artlib/common/BaseART.py index 6ab8255..e85a7c9 100644 --- a/artlib/common/BaseART.py +++ b/artlib/common/BaseART.py @@ -219,7 +219,7 @@ def match_criterion( w: np.ndarray, params: Dict, cache: Optional[Dict] = None, - ) -> Tuple[Union[float, List[float]], Optional[Dict]]: + ) -> Tuple[float, Optional[Dict]]: """Get the match criterion of the cluster. Parameters diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index bc6b1c2..ad4e10f 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -7,7 +7,7 @@ # Processing, 37, 54 – 115. doi:10. 1016/S0734-189X(87)80014-2. import numpy as np -from typing import Optional, List, Tuple, Union, Dict +from typing import Optional, List, Tuple, Dict from artlib.common.BaseART import BaseART from artlib.common.utils import l1norm @@ -108,7 +108,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> Tuple[Union[float, List[float]], Optional[Dict]]: + ) -> Tuple[float, Optional[Dict]]: """Get the match criterion of the cluster. Parameters diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index 6b19f9b..63bda72 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -127,7 +127,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> Tuple[Union[float, List[float]], Optional[Dict]]: + ) -> Tuple[float, Optional[Dict]]: """Get the match criterion of the cluster. Parameters diff --git a/artlib/elementary/EllipsoidART.py b/artlib/elementary/EllipsoidART.py index 0c5b9d2..47796e8 100644 --- a/artlib/elementary/EllipsoidART.py +++ b/artlib/elementary/EllipsoidART.py @@ -10,7 +10,7 @@ # International Society for Optics and Photonics. doi:10.1117/12.421180. import numpy as np -from typing import Optional, List, Tuple, Union, Dict +from typing import Optional, List, Tuple, Dict from matplotlib.axes import Axes from artlib.common.BaseART import BaseART from artlib.common.utils import l2norm2, IndexableOrKeyable @@ -166,7 +166,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> Tuple[Union[float, List[float]], Optional[Dict]]: + ) -> Tuple[float, Optional[Dict]]: """Get the match criterion of the cluster. Parameters diff --git a/artlib/elementary/FuzzyART.py b/artlib/elementary/FuzzyART.py index 5410b7e..ef666cb 100644 --- a/artlib/elementary/FuzzyART.py +++ b/artlib/elementary/FuzzyART.py @@ -5,7 +5,7 @@ # Neural Networks, 4, 759 – 771. doi:10.1016/0893-6080(91)90056-B. import numpy as np -from typing import Optional, Iterable, List, Tuple, Union, Dict +from typing import Optional, Iterable, List, Tuple, Dict from matplotlib.axes import Axes from artlib.common.BaseART import BaseART from artlib.common.utils import ( @@ -206,7 +206,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> Tuple[Union[float, List[float]], Optional[Dict]]: + ) -> Tuple[float, Optional[Dict]]: """Get the match criterion of the cluster. Parameters diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index 34a917b..ee6778a 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -5,7 +5,7 @@ # Neural Networks, 9, 881 – 897. doi:10.1016/0893-6080(95)00115-8. import numpy as np -from typing import Optional, Iterable, List, Tuple, Union, Dict +from typing import Optional, Iterable, List, Tuple, Dict from matplotlib.axes import Axes from artlib.common.BaseART import BaseART from artlib.common.visualization import plot_gaussian_contours_fading @@ -109,7 +109,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> Tuple[Union[float, List[float]], Optional[Dict]]: + ) -> Tuple[float, Optional[Dict]]: """Get the match criterion of the cluster. Parameters diff --git a/artlib/elementary/QuadraticNeuronART.py b/artlib/elementary/QuadraticNeuronART.py index 1d0c6f7..f997597 100644 --- a/artlib/elementary/QuadraticNeuronART.py +++ b/artlib/elementary/QuadraticNeuronART.py @@ -8,7 +8,7 @@ # Pattern Recognition, 38, 1887 – 1901. doi:10.1016/j.patcog.2005.04.010. import numpy as np -from typing import Optional, Iterable, List, Tuple, Union, Dict +from typing import Optional, Iterable, List, Tuple, Dict from matplotlib.axes import Axes from artlib.common.BaseART import BaseART from artlib.common.utils import l2norm2 @@ -133,7 +133,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> Tuple[Union[float, List[float]], Optional[Dict]]: + ) -> Tuple[float, Optional[Dict]]: """Get the match criterion of the cluster. Parameters diff --git a/artlib/experimental/ConvexHullART.py b/artlib/experimental/ConvexHullART.py index 0929134..3d12ea4 100644 --- a/artlib/experimental/ConvexHullART.py +++ b/artlib/experimental/ConvexHullART.py @@ -271,7 +271,7 @@ def match_criterion( w: HullTypes, params: dict, cache: Optional[dict] = None, - ) -> Tuple[Union[float, List[float]], Optional[Dict]]: + ) -> Tuple[float, Optional[Dict]]: """ Get the match criterion of the cluster. diff --git a/artlib/experimental/SeqART.py b/artlib/experimental/SeqART.py index 7621f60..1e052ee 100644 --- a/artlib/experimental/SeqART.py +++ b/artlib/experimental/SeqART.py @@ -234,7 +234,7 @@ def category_choice( def match_criterion( self, i: str, w: str, params: dict, cache: Optional[dict] = None - ) -> Tuple[Union[float, List[float]], Optional[Dict]]: + ) -> Tuple[float, Optional[Dict]]: """ Get the match criterion of the cluster. diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index 58449ef..5880b67 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -289,7 +289,7 @@ def match_criterion( params: Dict, cache: Optional[Dict] = None, skip_channels: List[int] = [], - ) -> Tuple[Union[float, List[float]], Optional[Dict]]: + ) -> Tuple[float, Optional[Dict]]: """Get the match criterion for the cluster. Parameters @@ -308,7 +308,7 @@ def match_criterion( Returns ------- tuple - List of match criteria for each channel and the updated cache. + max match_criterion across channels and the updated cache. """ if cache is None: @@ -322,12 +322,12 @@ def match_criterion( cache[k], ) if k not in skip_channels - else (np.inf, {"match_criterion": np.inf}) + else (np.nan, {"match_criterion": np.inf}) for k in range(self.n) ] ) cache = {k: cache_k for k, cache_k in enumerate(caches)} - return M, cache + return np.nanmax(M), cache def match_criterion_bin( self, diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index 35efe23..66489d3 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -192,7 +192,7 @@ def match_criterion( w: np.ndarray, params: dict, cache: Optional[dict] = None, - ) -> Tuple[Union[float, List[float]], Optional[Dict]]: + ) -> Tuple[float, Optional[Dict]]: """Get the match criterion of the cluster. Parameters From fc233b8e4dfa2195ecce925f5fa90ea1fd4a22e9 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 18 Oct 2024 12:23:32 -0500 Subject: [PATCH 112/139] revert match_criterion typing --- artlib/common/BaseART.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artlib/common/BaseART.py b/artlib/common/BaseART.py index e85a7c9..ea40e96 100644 --- a/artlib/common/BaseART.py +++ b/artlib/common/BaseART.py @@ -88,7 +88,7 @@ def set_params(self, **params): for key, value in params.items(): key, delim, sub_key = key.partition("__") if key not in valid_params: - local_valid_params = List(valid_params.keys()) + local_valid_params = list(valid_params.keys()) raise ValueError( f"Invalid parameter {key!r} for estimator {self}. " f"Valid parameters are: {local_valid_params!r}." From 9dc3c32e209f2bc8b974e378b622761f27412f26 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 18 Oct 2024 12:46:37 -0500 Subject: [PATCH 113/139] correct docstrings type for colors --- .pre-commit-config.yaml | 2 +- artlib/common/BaseART.py | 4 +- artlib/common/BaseARTMAP.py | 4 +- artlib/cvi/CVIART.py | 2 +- artlib/elementary/BayesianART.py | 2 +- artlib/elementary/EllipsoidART.py | 2 +- artlib/elementary/FuzzyART.py | 2 +- artlib/elementary/GaussianART.py | 2 +- artlib/elementary/HypersphereART.py | 2 +- artlib/elementary/QuadraticNeuronART.py | 2 +- artlib/experimental/ConvexHullART.py | 2 +- artlib/hierarchical/SMART.py | 4 +- artlib/supervised/SimpleARTMAP.py | 2 +- artlib/topological/DualVigilanceART.py | 2 +- artlib/topological/TopoART.py | 2 +- templates/ART_template.py | 221 ++++++++++++++---------- 16 files changed, 152 insertions(+), 105 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d52e41e..e6bd3d7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,4 +44,4 @@ repos: - --exclude=artlib/cvi/ - --follow-imports=silent -exclude: ^(unit_tests/|scripts/|artlib/experimental/|examples/|templates/|docs/|artlib/cvi/) +exclude: ^(unit_tests/|scripts/|artlib/experimental/|examples/|docs/|artlib/cvi/) diff --git a/artlib/common/BaseART.py b/artlib/common/BaseART.py index ea40e96..3af9e7f 100644 --- a/artlib/common/BaseART.py +++ b/artlib/common/BaseART.py @@ -696,7 +696,7 @@ def plot_cluster_bounds( ---------- ax : matplotlib.axes.Axes Figure axes. - colors : iterable + colors : IndexableOrKeyable Colors to use for each cluster. linewidth : int, default=1 Width of boundary line. @@ -738,7 +738,7 @@ def visualize( Size used for data points. linewidth : int, default=1 Width of boundary line. - colors : iterable, optional + colors : IndexableOrKeyable, optional Colors to use for each cluster. """ diff --git a/artlib/common/BaseARTMAP.py b/artlib/common/BaseARTMAP.py index 312dfac..fa5da4c 100644 --- a/artlib/common/BaseARTMAP.py +++ b/artlib/common/BaseARTMAP.py @@ -179,7 +179,7 @@ def plot_cluster_bounds( ---------- ax : matplotlib.axes.Axes Figure axes. - colors : iterable + colors : IndexableOrKeyable Colors to use for each cluster. linewidth : int, optional Width of boundary line, by default 1. @@ -210,7 +210,7 @@ def visualize( Size used for data points, by default 10. linewidth : int, optional Width of boundary line, by default 1. - colors : iterable, optional + colors : IndexableOrKeyable, optional Colors to use for each cluster, by default None. """ diff --git a/artlib/cvi/CVIART.py b/artlib/cvi/CVIART.py index 3eda039..ad9c495 100644 --- a/artlib/cvi/CVIART.py +++ b/artlib/cvi/CVIART.py @@ -381,7 +381,7 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): ---------- ax : matplotlib.axes.Axes Figure axes. - colors : iterable + colors : IndexableOrKeyable Colors to use for each cluster. linewidth : int, optional Width of boundary line, by default 1. diff --git a/artlib/elementary/BayesianART.py b/artlib/elementary/BayesianART.py index 63bda72..aadc2b3 100644 --- a/artlib/elementary/BayesianART.py +++ b/artlib/elementary/BayesianART.py @@ -330,7 +330,7 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): ---------- ax : matplotlib.axes.Axes Figure axes. - colors : iterable + colors : IndexableOrKeyable Colors to use for each cluster. linewidth : int, optional Width of boundary line, by default 1. diff --git a/artlib/elementary/EllipsoidART.py b/artlib/elementary/EllipsoidART.py index 47796e8..cfcc96b 100644 --- a/artlib/elementary/EllipsoidART.py +++ b/artlib/elementary/EllipsoidART.py @@ -302,7 +302,7 @@ def plot_cluster_bounds( ---------- ax : matplotlib.axes.Axes Figure axes. - colors : iterable + colors : IndexableOrKeyable Colors to use for each cluster. linewidth : int, optional Width of boundary line, by default 1. diff --git a/artlib/elementary/FuzzyART.py b/artlib/elementary/FuzzyART.py index ef666cb..60faaa8 100644 --- a/artlib/elementary/FuzzyART.py +++ b/artlib/elementary/FuzzyART.py @@ -340,7 +340,7 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): ---------- ax : matplotlib.axes.Axes Figure axes. - colors : iterable + colors : IndexableOrKeyable Colors to use for each cluster. linewidth : int, optional Width of boundary line, by default 1. diff --git a/artlib/elementary/GaussianART.py b/artlib/elementary/GaussianART.py index ee6778a..2c77ee1 100644 --- a/artlib/elementary/GaussianART.py +++ b/artlib/elementary/GaussianART.py @@ -220,7 +220,7 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): ---------- ax : matplotlib.axes.Axes Figure axes. - colors : iterable + colors : IndexableOrKeyable Colors to use for each cluster. linewidth : int, optional Width of boundary line, by default 1. diff --git a/artlib/elementary/HypersphereART.py b/artlib/elementary/HypersphereART.py index 73ff933..3f84595 100644 --- a/artlib/elementary/HypersphereART.py +++ b/artlib/elementary/HypersphereART.py @@ -244,7 +244,7 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): ---------- ax : matplotlib.axes.Axes Figure axes. - colors : iterable + colors : IndexableOrKeyable Colors to use for each cluster. linewidth : int, optional Width of boundary line, by default 1. diff --git a/artlib/elementary/QuadraticNeuronART.py b/artlib/elementary/QuadraticNeuronART.py index f997597..2ae001f 100644 --- a/artlib/elementary/QuadraticNeuronART.py +++ b/artlib/elementary/QuadraticNeuronART.py @@ -241,7 +241,7 @@ def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): ---------- ax : matplotlib.axes.Axes Figure axes. - colors : iterable + colors : IndexableOrKeyable Colors to use for each cluster. linewidth : int, optional Width of boundary line, by default 1. diff --git a/artlib/experimental/ConvexHullART.py b/artlib/experimental/ConvexHullART.py index 3d12ea4..30927be 100644 --- a/artlib/experimental/ConvexHullART.py +++ b/artlib/experimental/ConvexHullART.py @@ -437,7 +437,7 @@ def plot_cluster_bounds( ---------- ax : matplotlib.axes.Axes Figure axes. - colors : iterable + colors : IndexableOrKeyable Colors to use for each cluster. linewidth : int, optional Width of boundary line, by default 1. diff --git a/artlib/hierarchical/SMART.py b/artlib/hierarchical/SMART.py index 8952607..8c2d845 100644 --- a/artlib/hierarchical/SMART.py +++ b/artlib/hierarchical/SMART.py @@ -187,7 +187,7 @@ def plot_cluster_bounds( ---------- ax : Axes The matplotlib axes on which to plot the cluster boundaries. - colors : Iterable + colors : IndexableOrKeyable The colors to use for each cluster. linewidth : int, optional The width of the boundary lines. @@ -229,7 +229,7 @@ def visualize( The size of the data points in the plot. linewidth : int, optional The width of the cluster boundary lines. - colors : Iterable, optional + colors : IndexableOrKeyable, optional The colors to use for each cluster. Returns diff --git a/artlib/supervised/SimpleARTMAP.py b/artlib/supervised/SimpleARTMAP.py index f77eff0..34c59c9 100644 --- a/artlib/supervised/SimpleARTMAP.py +++ b/artlib/supervised/SimpleARTMAP.py @@ -458,7 +458,7 @@ def plot_cluster_bounds( ---------- ax : Axes Figure axes. - colors : Iterable + colors : IndexableOrKeyable Colors to use for each cluster. linewidth : int, default=1 Width of boundary lines. diff --git a/artlib/topological/DualVigilanceART.py b/artlib/topological/DualVigilanceART.py index 29ea422..145a38e 100644 --- a/artlib/topological/DualVigilanceART.py +++ b/artlib/topological/DualVigilanceART.py @@ -421,7 +421,7 @@ def plot_cluster_bounds( ---------- ax : matplotlib.axes.Axes Figure axes. - colors : iterable + colors : IndexableOrKeyable Colors to use for each cluster. linewidth : int, optional Width of boundary line, by default 1. diff --git a/artlib/topological/TopoART.py b/artlib/topological/TopoART.py index 66489d3..f0c226e 100644 --- a/artlib/topological/TopoART.py +++ b/artlib/topological/TopoART.py @@ -562,7 +562,7 @@ def plot_cluster_bounds( ---------- ax : Axes Figure axes. - colors : Iterable + colors : IndexableOrKeyable Colors to use for each cluster. linewidth : int, default=1 Width of boundary lines. diff --git a/templates/ART_template.py b/templates/ART_template.py index a354252..9d353c7 100644 --- a/templates/ART_template.py +++ b/templates/ART_template.py @@ -1,132 +1,179 @@ -import numpy as np -from typing import Optional -from artlib import BaseART -from artlib.common.utils import normalize +"""Template for custom ART Modules. +This template contains the minimum methods necessary to define for a custom ART +module. Additional functions may be defined as well depending on how customized the +behavior of the model is. See :class:`~artlib.common.BaseART.BaseART` for the +complete set of pre-defined methods. -def prepare_data(data: np.ndarray) -> np.ndarray: - normalized = normalize(data) - return normalized +""" +import numpy as np +from matplotlib.axes import Axes +from typing import Optional, List, Tuple, Dict +from artlib.common.BaseART import BaseART +from artlib.common.utils import IndexableOrKeyable -class myART(BaseART): - # template for ART module - @staticmethod - def validate_params(params: dict): - """ - validate clustering parameters +class MyART(BaseART): + """Generic Template for custom ART modules.""" - Parameters: - - params: dict containing parameters for the algorithm + def __init__(self, rho: float): + """Initializes the ConvexHullART object. - """ - raise NotImplementedError + Parameters + ---------- + rho : float + Vigilance parameter. - def validate_data(self, X: np.ndarray): """ - validates the data prior to clustering + # add additional parameters as needed + params = {"rho": rho} + super().__init__(params) + + @staticmethod + def validate_params(params: dict): + """Validates clustering parameters. - Parameters: - - X: data set + Parameters + ---------- + params : dict + Dictionary containing parameters for the algorithm. """ - raise NotImplementedError + assert "rho" in params + assert params["rho"] >= 0.0 + assert isinstance(params["rho"], float) + # check additional parameters if you add them 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 + self, i: np.ndarray, w: np.ndarray, params: Dict + ) -> Tuple[float, Optional[Dict]]: + """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 + ------- + tuple + Cluster activation and cache used for later processing. """ + # implement your custom logic here 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 - - 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 + params: Dict, + cache: Optional[Dict] = None, + ) -> Tuple[float, Optional[Dict]]: + """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 + ------- + tuple + Cluster match criterion and cache used for later processing. """ + # implement your custom logic here raise NotImplementedError - def match_criterion_bin( + def update( self, i: np.ndarray, w: np.ndarray, - params: dict, - cache: Optional[dict] = None, - ) -> 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 + params: Dict, + cache: Optional[Dict] = None, + ) -> np.ndarray: + """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. """ + # implement your custom logic here raise NotImplementedError - def update( - self, - i: np.ndarray, - w: np.ndarray, - params: dict, - cache: Optional[dict] = None, - ) -> np.ndarray: - """ - get the updated cluster weight + 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 - - cache: dict containing values cached from previous calculations + Parameters + ---------- + i : np.ndarray + Data sample. + params : dict + Dictionary containing parameters for the algorithm. - Returns: - updated cluster weight, cache used for later processing + Returns + ------- + np.ndarray + Updated cluster weight. """ + # implement your custom logic here raise NotImplementedError - def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: + # ================================================================================== + # These functions are not strictly necessary but should be defined if possible + # ================================================================================== + def get_cluster_centers(self) -> List[np.ndarray]: + """Undefined function for getting centers of each cluster. Used for regression. + + Returns + ------- + list of np.ndarray + Cluster centroids. + """ - generate a new cluster weight + # implement your custom logic here + raise NotImplementedError - Parameters: - - i: data sample - - w: cluster weight / info - - params: dict containing parameters for the algorithm + def plot_cluster_bounds( + self, ax: Axes, colors: IndexableOrKeyable, linewidth: int = 1 + ): + """Undefined function for visualizing the bounds of each cluster. - Returns: - updated cluster weight + Parameters + ---------- + ax : matplotlib.axes.Axes + Figure axes. + colors : IndexableOrKeyable + Colors to use for each cluster. + linewidth : int, default=1 + Width of boundary line. """ + # implement your custom logic here raise NotImplementedError From e47b04dfa8c26110bb81ae91fb5e57e311d5a589 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 18 Oct 2024 14:00:33 -0500 Subject: [PATCH 114/139] add examples and improve FusionART prepare_data --- README.md | 79 +++++++++++++++++++++++++++++++++++--- artlib/fusion/FusionART.py | 30 +++++++++++---- 2 files changed, 97 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 00cbaa9..0105cc1 100644 --- a/README.md +++ b/README.md @@ -69,24 +69,93 @@ 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: +Here are some quick examples to get you started with AdaptiveResonanceLib: + +### Clustering Data with the Fuzzy ART model ```python from artlib import FuzzyART import numpy as np # Your dataset -train_X = np.array([...]) +train_X = np.array([...]) # shape (n_samples, n_features) test_X = np.array([...]) # Initialize the Fuzzy ART model model = FuzzyART(rho=0.7, alpha = 0.0, beta=1.0) +# Prepare Data +train_X_prep = model.prepare_data(train_X) +test_X_prep = model.prepare_data(test_X) + +# Fit the model +model.fit(train_X_prep) + +# Predict data labels +predictions = model.predict(test_X_prep) +``` + +### Fitting a Classification Model with SimpleARTMAP + +```python +from artlib import GaussianART, SimpleARTMAP +import numpy as np + +# Your dataset +train_X = np.array([...]) # shape (n_samples, n_features) +train_y = np.array([...]) # shape (n_samples, ), must be integers +test_X = np.array([...]) + +# Initialize the Gaussian ART model +sigma_init = np.array([0.5]*train_X.shape[1]) # variance estimate for each feature +module_a = GaussianART(rho=0.0, sigma_init=sigma_init) + +# Initialize the SimpleARTMAP model +model = SimpleARTMAP(module_a=module_a) + +# Prepare Data +train_X_prep = model.prepare_data(train_X) +test_X_prep = model.prepare_data(test_X) + +# Fit the model +model.fit(train_X_prep, train_y) + +# Predict data labels +predictions = model.predict(test_X_prep) +``` + +### Fitting a Regression Model with FusionART + +```python +from artlib import FuzzyART, HypersphereART, FusionART +import numpy as np + +# Your dataset +train_X = np.array([...]) # shape (n_samples, n_features_X) +train_y = np.array([...]) # shape (n_samples, n_features_y) +test_X = np.array([...]) + +# Initialize the Fuzzy ART model +module_x = FuzzyART(rho=0.0, alpha = 0.0, beta=1.0) + +# Initialize the Hypersphere ART model +r_hat = 0.5*np.sqrt(train_X.shape[1]) # no restriction on hyperpshere size +module_y = HypersphereART(rho=0.0, alpha = 0.0, beta=1.0, r_hat=r_hat) + +# Initialize the SimpleARTMAP model +model = FusionART(modules=[module_x, module_y]) + +# Prepare Data +train_Xy = model.join_channel_data(channel_data=[train_X, train_y]) +train_Xy_prep = model.prepare_data(train_Xy) +test_Xy = model.join_channel_data(channel_data=[train_X], skip_channels=[1]) +test_Xy_prep = model.prepare_data(test_Xy) + # Fit the model -model.fit(train_X) +model.fit(train_X_prep, train_y) -# Predict new data points -predictions = model.predict(test_X) +# Predict data labels +predictions = model.predict(test_X_prep) ``` diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index 5880b67..bc8164a 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -198,7 +198,9 @@ def check_dimensions(self, X: np.ndarray): """ assert X.shape[1] == self.dim_, "Invalid data shape" - def prepare_data(self, channel_data: List[np.ndarray]) -> np.ndarray: + def prepare_data( + self, channel_data: List[np.ndarray], skip_channels: List[int] = [] + ) -> np.ndarray: """Prepare the input data by processing each channel's data through its respective ART module. @@ -206,6 +208,8 @@ def prepare_data(self, channel_data: List[np.ndarray]) -> np.ndarray: ---------- channel_data : list of np.ndarray List of arrays, one for each channel. + skip_channels : list of int, optional + Channels to be skipped (default is []). Returns ------- @@ -213,28 +217,40 @@ def prepare_data(self, channel_data: List[np.ndarray]) -> np.ndarray: Processed and concatenated data. """ + skip_channels = [self.n + k if k < 0 else k for k in skip_channels] prepared_channel_data = [ - self.modules[i].prepare_data(channel_data[i]) for i in range(self.n) + self.modules[i].prepare_data(channel_data[i]) + for i in range(self.n) + if i not in skip_channels ] - return self.join_channel_data(prepared_channel_data) - def restore_data(self, X: np.ndarray) -> List[np.ndarray]: + return self.join_channel_data( + prepared_channel_data, skip_channels=skip_channels + ) + + def restore_data( + self, X: np.ndarray, skip_channels: List[int] = [] + ) -> List[np.ndarray]: """Restore data to its original state before preparation. Parameters ---------- X : np.ndarray The prepared data. - + skip_channels : list of int, optional + Channels to be skipped (default is []). Returns ------- np.ndarray Restored data for each channel. """ - channel_data = self.split_channel_data(X) + skip_channels = [self.n + k if k < 0 else k for k in skip_channels] + channel_data = self.split_channel_data(X, skip_channels=skip_channels) restored_channel_data = [ - self.modules[i].restore_data(channel_data[i]) for i in range(self.n) + self.modules[i].restore_data(channel_data[i]) + for i in range(self.n) + if i not in skip_channels ] return restored_channel_data From 3ea1c7861f300f643af6b573a6656272cfaaebae Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 18 Oct 2024 14:08:05 -0500 Subject: [PATCH 115/139] add examples and improve FusionART prepare_data --- README.md | 17 +++++++++++++---- artlib/fusion/FusionART.py | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0105cc1..70a1101 100644 --- a/README.md +++ b/README.md @@ -142,8 +142,17 @@ module_x = FuzzyART(rho=0.0, alpha = 0.0, beta=1.0) r_hat = 0.5*np.sqrt(train_X.shape[1]) # no restriction on hyperpshere size module_y = HypersphereART(rho=0.0, alpha = 0.0, beta=1.0, r_hat=r_hat) -# Initialize the SimpleARTMAP model -model = FusionART(modules=[module_x, module_y]) +# Initialize the FusionARTMAP model +gamma_values = [0.5, 0.5] # eqaul weight to both channels +channel_dims = [ + 2*train_X.shape[1], # fuzzy ART complement codes data so channel dim is 2*n_features + train_y.shape[1] +] +model = FusionART( + modules=[module_x, module_y], + gamma_values=gamma_values, + channel_dims=channel_dims +) # Prepare Data train_Xy = model.join_channel_data(channel_data=[train_X, train_y]) @@ -154,8 +163,8 @@ test_Xy_prep = model.prepare_data(test_Xy) # Fit the model model.fit(train_X_prep, train_y) -# Predict data labels -predictions = model.predict(test_X_prep) +# Predict y-channel values +pred_y = model.predict_regression(test_Xy_prep, target_channels=[1]) ``` diff --git a/artlib/fusion/FusionART.py b/artlib/fusion/FusionART.py index bc8164a..781e66a 100644 --- a/artlib/fusion/FusionART.py +++ b/artlib/fusion/FusionART.py @@ -171,7 +171,7 @@ def validate_params(params: Dict): assert "gamma_values" in params assert all([1.0 >= g >= 0.0 for g in params["gamma_values"]]) assert sum(params["gamma_values"]) == 1.0 - assert isinstance(params["gamma_values"], np.ndarray) + assert isinstance(params["gamma_values"], (np.ndarray, list)) def validate_data(self, X: np.ndarray): """Validate the input data for clustering. From feaf56a501029a8b01c3c24c2fd54969eab85bee Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 18 Oct 2024 14:22:33 -0500 Subject: [PATCH 116/139] create paper.md --- paper.md | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 paper.md diff --git a/paper.md b/paper.md new file mode 100644 index 0000000..0e57635 --- /dev/null +++ b/paper.md @@ -0,0 +1,108 @@ +--- +title: 'Adaptive Resonance Lib: A Python package for Adaptive Resonance Theory (ART) +models' +tags: + - Python + - clustering + - classification + - regression + - reinforcement learning + - machine learning +authors: + - name: Niklas M. Melton + orcid: 0000-0001-9625-7086 + affiliation: "1" # (Multiple affiliations must be quoted) + - name: Donald C. Wunsch II + orcid: 0000-0002-9726-9051 + affiliation: "1, 2" # (Multiple affiliations must be quoted) +affiliations: + - name: Missouri University of Science and Technology, USA + index: 1 + - name: Kummer Institute Center for Artificial Intelligence and Autonomous Systems (KICAIAS), USA + index: 2 +date: 18 October 2024 +bibliography: references.bib +--- + +# Summary + +Adaptive Resonance Theory (ART) + +The forces on stars, galaxies, and dark matter under external gravitational +fields lead to the dynamical evolution of structures in the universe. The orbits +of these bodies are therefore key to understanding the formation, history, and +future state of galaxies. The field of "galactic dynamics," which aims to model +the gravitating components of galaxies to study their structure and evolution, +is now well-established, commonly taught, and frequently used in astronomy. +Aside from toy problems and demonstrations, the majority of problems require +efficient numerical tools, many of which require the same base code (e.g., for +performing numerical orbit integration). + +# Statement of need + +`Gala` is an Astropy-affiliated Python package for galactic dynamics. Python +enables wrapping low-level languages (e.g., C) for speed without losing +flexibility or ease-of-use in the user-interface. The API for `Gala` was +designed to provide a class-based and user-friendly interface to fast (C or +Cython-optimized) implementations of common operations such as gravitational +potential and force evaluation, orbit integration, dynamical transformations, +and chaos indicators for nonlinear dynamics. `Gala` also relies heavily on and +interfaces well with the implementations of physical units and astronomical +coordinate systems in the `Astropy` package [@astropy] (`astropy.units` and +`astropy.coordinates`). + +`Gala` was designed to be used by both astronomical researchers and by +students in courses on gravitational dynamics or astronomy. It has already been +used in a number of scientific publications [@Pearson:2017] and has also been +used in graduate courses on Galactic dynamics to, e.g., provide interactive +visualizations of textbook material [@Binney:2008]. The combination of speed, +design, and support for Astropy functionality in `Gala` will enable exciting +scientific explorations of forthcoming data releases from the *Gaia* mission +[@gaia] by students and experts alike. + +# Mathematics + +Single dollars ($) are required for inline mathematics e.g. $f(x) = e^{\pi/x}$ + +Double dollars make self-standing equations: + +$$\Theta(x) = \left\{\begin{array}{l} +0\textrm{ if } x < 0\cr +1\textrm{ else} +\end{array}\right.$$ + +You can also use plain \LaTeX for equations +\begin{equation}\label{eq:fourier} +\hat f(\omega) = \int_{-\infty}^{\infty} f(x) e^{i\omega x} dx +\end{equation} +and refer to \autoref{eq:fourier} from text. + +# Citations + +Citations to entries in paper.bib should be in +[rMarkdown](http://rmarkdown.rstudio.com/authoring_bibliographies_and_citations.html) +format. + +If you want to cite a software repository URL (e.g. something on GitHub without a preferred +citation) then you can do it with the example BibTeX entry below for @fidgit. + +For a quick reference, the following citation commands can be used: +- `@author:2001` -> "Author et al. (2001)" +- `[@author:2001]` -> "(Author et al., 2001)" +- `[@author1:2001; @author2:2001]` -> "(Author1 et al., 2001; Author2 et al., 2002)" + +# Figures + +Figures can be included like this: +![Caption for example figure.\label{fig:example}](figure.png) +and referenced from text using \autoref{fig:example}. + +Figure sizes can be customized by adding an optional second parameter: +![Caption for example figure.](figure.png){ width=20% } + +# Acknowledgements + +We acknowledge contributions from Brigitta Sipocz, Syrtis Major, and Semyeong +Oh, and support from Kathryn Johnston during the genesis of this project. + +# References From ec57d06d7ff03a7e7ac730a8afab5a5465173b63 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 18 Oct 2024 15:52:33 -0500 Subject: [PATCH 117/139] paper and updated bib --- paper.md | 155 ++++++++++++++++++++++++------------------------- references.bib | 105 +++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+), 78 deletions(-) diff --git a/paper.md b/paper.md index 0e57635..649ca1d 100644 --- a/paper.md +++ b/paper.md @@ -1,6 +1,6 @@ --- -title: 'Adaptive Resonance Lib: A Python package for Adaptive Resonance Theory (ART) -models' +title: 'Adaptive Resonance Lib: A Python package for Adaptive Resonance Theory (ART) models' + tags: - Python - clustering @@ -8,101 +8,100 @@ tags: - regression - reinforcement learning - machine learning + authors: - name: Niklas M. Melton orcid: 0000-0001-9625-7086 - affiliation: "1" # (Multiple affiliations must be quoted) + affiliation: "1, 2" + - name: Dustin Tanksley + orcid: 0000-0002-1677-0304 + affiliation: "1, 2" - name: Donald C. Wunsch II orcid: 0000-0002-9726-9051 - affiliation: "1, 2" # (Multiple affiliations must be quoted) + affiliation: "1, 2, 3" + affiliations: - name: Missouri University of Science and Technology, USA index: 1 - - name: Kummer Institute Center for Artificial Intelligence and Autonomous Systems (KICAIAS), USA +- name: National Science Foundation (NSF), USA index: 2 + - name: Kummer Institute Center for Artificial Intelligence and Autonomous Systems + (KICAIAS), USA + index: 3 + date: 18 October 2024 bibliography: references.bib --- # Summary -Adaptive Resonance Theory (ART) - -The forces on stars, galaxies, and dark matter under external gravitational -fields lead to the dynamical evolution of structures in the universe. The orbits -of these bodies are therefore key to understanding the formation, history, and -future state of galaxies. The field of "galactic dynamics," which aims to model -the gravitating components of galaxies to study their structure and evolution, -is now well-established, commonly taught, and frequently used in astronomy. -Aside from toy problems and demonstrations, the majority of problems require -efficient numerical tools, many of which require the same base code (e.g., for -performing numerical orbit integration). +The Adaptive Resonance Library (**artlib**) is a Python library that implements a wide +range of Adaptive Resonance Theory (ART) algorithms. ART is a class of neural networks +that is known for addressing the stability-plasticity dilemma, making it particularly +effective for classification, clustering, and incremental learning tasks +[@grossberg1976a, @grossberg1976a, @Grossberg1980HowDA, @grossberg2013adaptive, +@da2019survey]. ART models are designed to dynamically learn and adapt to new patterns +without catastrophic forgetting, making them suitable for real-time systems that require +continuous learning. + +**artlib** supports a variety of ART-based models including but not limited to +Fuzzy ART [@carpenter1991fuzzy], Hyperpshere ART [@anagnostopoulos2000hypersphere], +Gaussian ART [@williamson1996gaussian], ARTMAP [@carpenter1991artmap], +Fusion ART [@tan2007intelligence], and FALCON [@tan2004falcon]. These models +can be applied to tasks like unsupervised clustering, supervised classification or +regression, and reinforcement learning [@da2019survey]. This library provides an +extensible and modular framework where users can integrate custom models or extend +current implementations, which opens up opportunities for experimenting with existing +and novel machine learning techniques. + +In addition to the zoo of ART models, we provide +implementations of visualization methods for the various cluster geometries as well as +pre-processing techniques such as Visual Assessment of Tendency (VAT)[@bezdek2002vat], +data normalization, and complement coding. # Statement of need -`Gala` is an Astropy-affiliated Python package for galactic dynamics. Python -enables wrapping low-level languages (e.g., C) for speed without losing -flexibility or ease-of-use in the user-interface. The API for `Gala` was -designed to provide a class-based and user-friendly interface to fast (C or -Cython-optimized) implementations of common operations such as gravitational -potential and force evaluation, orbit integration, dynamical transformations, -and chaos indicators for nonlinear dynamics. `Gala` also relies heavily on and -interfaces well with the implementations of physical units and astronomical -coordinate systems in the `Astropy` package [@astropy] (`astropy.units` and -`astropy.coordinates`). - -`Gala` was designed to be used by both astronomical researchers and by -students in courses on gravitational dynamics or astronomy. It has already been -used in a number of scientific publications [@Pearson:2017] and has also been -used in graduate courses on Galactic dynamics to, e.g., provide interactive -visualizations of textbook material [@Binney:2008]. The combination of speed, -design, and support for Astropy functionality in `Gala` will enable exciting -scientific explorations of forthcoming data releases from the *Gaia* mission -[@gaia] by students and experts alike. - -# Mathematics - -Single dollars ($) are required for inline mathematics e.g. $f(x) = e^{\pi/x}$ - -Double dollars make self-standing equations: - -$$\Theta(x) = \left\{\begin{array}{l} -0\textrm{ if } x < 0\cr -1\textrm{ else} -\end{array}\right.$$ - -You can also use plain \LaTeX for equations -\begin{equation}\label{eq:fourier} -\hat f(\omega) = \int_{-\infty}^{\infty} f(x) e^{i\omega x} dx -\end{equation} -and refer to \autoref{eq:fourier} from text. - -# Citations - -Citations to entries in paper.bib should be in -[rMarkdown](http://rmarkdown.rstudio.com/authoring_bibliographies_and_citations.html) -format. - -If you want to cite a software repository URL (e.g. something on GitHub without a preferred -citation) then you can do it with the example BibTeX entry below for @fidgit. - -For a quick reference, the following citation commands can be used: -- `@author:2001` -> "Author et al. (2001)" -- `[@author:2001]` -> "(Author et al., 2001)" -- `[@author1:2001; @author2:2001]` -> "(Author1 et al., 2001; Author2 et al., 2002)" - -# Figures - -Figures can be included like this: -![Caption for example figure.\label{fig:example}](figure.png) -and referenced from text using \autoref{fig:example}. - -Figure sizes can be customized by adding an optional second parameter: -![Caption for example figure.](figure.png){ width=20% } +The Adaptive Resonance Library (**artlib**) is essential for researchers, developers, +and educators interested in adaptive neural networks, specifically ART algorithms. While +the field of machine learning has been dominated by architectures like deep learning, +ART models offer unique advantages in incremental and real-time learning environments +due to their ability to learn new data without forgetting previously learned +information. + +Currently, no comprehensive Python library exists that implements a variety of ART +models in an open-source, modular, and extensible manner. **artlib* fills this gap by +providing a range of ART implementations that are easy to integrate into various machine +learning workflows such as scikit-learn Pipelines and GridSearchCV [@scikit-learn]. +The library is designed for ease of use and performance, offering users fast numerical +computation via Python’s scientific stack (NumPy[@harris2020array], SciPy +[@2020SciPy-NMeth], and scikit-learn [@scikit-learn]). + +In particular, the modular nature of this library allows for the creation of +never-before published compound ART models such as Dual Vigilance +Fusion ART [@da2019dual, @tan2007intelligence] or Quadratic Neuron SMART +[@su2001application, @su2005new, @bartfai1994hierarchical]. Such flexibility offers +powerful experimental and time-saving advantages to researchers and practitioners when +evaluating models on their data. + +Additionally, the library is a valuable educational tool for students learning about +neural networks and adaptive systems. With well-documented code and clear APIs, it +supports hands-on experimentation with ART models, making it an excellent resource for +academic courses or personal projects in artificial intelligence and machine learning. + +Furthermore, **artlib** is actively maintained and designed to support further +extensions, allowing users to add new ART models, adjust parameters for specific use +cases, and leverage ART for novel research problems. Its integration with popular Python +libraries ensures that **artlib** remains adaptable and applicable to current machine +learning challenges. # Acknowledgements - -We acknowledge contributions from Brigitta Sipocz, Syrtis Major, and Semyeong -Oh, and support from Kathryn Johnston during the genesis of this project. +This research was supported by the National Science Foundation (NSF) under Award +Number 2420248. The project titled EAGER: Solving Representation Learning and +Catastrophic Forgetting with Adaptive Resonance Theory provided essential funding for +the completion of this work. + +We would also like to thank Gail Carpenter and Stephen Grossberg for their +feedback regarding this project and their immense contribution to machine learning by +pioneering Adaptive Resonance Theory. # References diff --git a/references.bib b/references.bib index 3188944..662ed44 100644 --- a/references.bib +++ b/references.bib @@ -194,3 +194,108 @@ @inproceedings{tscherepanow2010topoart year={2010}, organization={Springer} } +@article{scikit-learn, + title={Scikit-learn: Machine Learning in {P}ython}, + author={Pedregosa, F. and Varoquaux, G. and Gramfort, A. and Michel, V. + and Thirion, B. and Grisel, O. and Blondel, M. and Prettenhofer, P. + and Weiss, R. and Dubourg, V. and Vanderplas, J. and Passos, A. and + Cournapeau, D. and Brucher, M. and Perrot, M. and Duchesnay, E.}, + journal={Journal of Machine Learning Research}, + volume={12}, + pages={2825--2830}, + year={2011} +} +@ARTICLE{2020SciPy-NMeth, + author = {Virtanen, Pauli and Gommers, Ralf and Oliphant, Travis E. and + Haberland, Matt and Reddy, Tyler and Cournapeau, David and + Burovski, Evgeni and Peterson, Pearu and Weckesser, Warren and + Bright, Jonathan and {van der Walt}, St{\'e}fan J. and + Brett, Matthew and Wilson, Joshua and Millman, K. Jarrod and + Mayorov, Nikolay and Nelson, Andrew R. J. and Jones, Eric and + Kern, Robert and Larson, Eric and Carey, C J and + Polat, {\.I}lhan and Feng, Yu and Moore, Eric W. and + {VanderPlas}, Jake and Laxalde, Denis and Perktold, Josef and + Cimrman, Robert and Henriksen, Ian and Quintero, E. A. and + Harris, Charles R. and Archibald, Anne M. and + Ribeiro, Ant{\^o}nio H. and Pedregosa, Fabian and + {van Mulbregt}, Paul and {SciPy 1.0 Contributors}}, + title = {{{SciPy} 1.0: Fundamental Algorithms for Scientific + Computing in Python}}, + journal = {Nature Methods}, + year = {2020}, + volume = {17}, + pages = {261--272}, + adsurl = {https://rdcu.be/b08Wh}, + doi = {10.1038/s41592-019-0686-2}, +} +@Article{ harris2020array, + title = {Array programming with {NumPy}}, + author = {Charles R. Harris and K. Jarrod Millman and St{\'{e}}fan J. + van der Walt and Ralf Gommers and Pauli Virtanen and David + Cournapeau and Eric Wieser and Julian Taylor and Sebastian + Berg and Nathaniel J. Smith and Robert Kern and Matti Picus + and Stephan Hoyer and Marten H. van Kerkwijk and Matthew + Brett and Allan Haldane and Jaime Fern{\'{a}}ndez del + R{\'{i}}o and Mark Wiebe and Pearu Peterson and Pierre + G{\'{e}}rard-Marchant and Kevin Sheppard and Tyler Reddy and + Warren Weckesser and Hameer Abbasi and Christoph Gohlke and + Travis E. Oliphant}, + year = {2020}, + month = sep, + journal = {Nature}, + volume = {585}, + number = {7825}, + pages = {357--362}, + doi = {10.1038/s41586-020-2649-2}, + publisher = {Springer Science and Business Media {LLC}}, + url = {https://doi.org/10.1038/s41586-020-2649-2} +} +@article{da2019survey, + title={A survey of adaptive resonance theory neural network models for engineering applications}, + author={da Silva, Leonardo Enzo Brito and Elnabarawy, Islam and Wunsch II, Donald C}, + journal={Neural Networks}, + volume={120}, + pages={167--203}, + year={2019}, + publisher={Elsevier} +} + +@article{grossberg1976a, + title={Adaptive pattern classification and universal recoding: I. Parallel development and coding of neural feature detectors}, + author={Grossberg, Stephen}, + journal={Biological cybernetics}, + volume={23}, + number={3}, + pages={121--134}, + year={1976}, + publisher={Springer} +} +@article{grossberg1976ab, + title={Adaptive pattern classification and universal recoding: II. Feedback, expectation, olfaction, illusions}, + author={Grossberg, Stephen}, + journal={Biological cybernetics}, + volume={23}, + number={4}, + pages={187--202}, + year={1976}, + publisher={Springer} +} +@article{Grossberg1980HowDA, + title={How does a brain build a cognitive code?}, + author={Stephen Grossberg}, + journal={Psychological review}, + year={1980}, + volume={87 1}, + pages={ + 1-51 + } +} +@article{grossberg2013adaptive, + title={Adaptive Resonance Theory: How a brain learns to consciously attend, learn, and recognize a changing world}, + author={Grossberg, Stephen}, + journal={Neural networks}, + volume={37}, + pages={1--47}, + year={2013}, + publisher={Elsevier} +} From 8428491af0372d2e5f4951d936aa3eca47921fbf Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 18 Oct 2024 16:00:12 -0500 Subject: [PATCH 118/139] paper and updated bib --- .github/workflows/draft-pdf.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/draft-pdf.yml diff --git a/.github/workflows/draft-pdf.yml b/.github/workflows/draft-pdf.yml new file mode 100644 index 0000000..1d7dc44 --- /dev/null +++ b/.github/workflows/draft-pdf.yml @@ -0,0 +1,24 @@ +name: Draft PDF +on: [push] + +jobs: + paper: + runs-on: ubuntu-latest + name: Paper Draft + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Build draft PDF + uses: openjournals/openjournals-draft-action@master + with: + journal: joss + # This should be the path to the paper within your repo. + paper-path: paper.md + - name: Upload + uses: actions/upload-artifact@v4 + with: + name: paper + # This is the output path where Pandoc will write the compiled + # PDF. Note, this should be the same directory as the input + # paper.md + path: paper.pdf From 67d1aa981ec1216f1a1ac559ed1b151fc16ba8ce Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 18 Oct 2024 16:01:49 -0500 Subject: [PATCH 119/139] paper and updated bib --- paper.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paper.md b/paper.md index 649ca1d..121c308 100644 --- a/paper.md +++ b/paper.md @@ -39,7 +39,7 @@ The Adaptive Resonance Library (**artlib**) is a Python library that implements range of Adaptive Resonance Theory (ART) algorithms. ART is a class of neural networks that is known for addressing the stability-plasticity dilemma, making it particularly effective for classification, clustering, and incremental learning tasks -[@grossberg1976a, @grossberg1976a, @Grossberg1980HowDA, @grossberg2013adaptive, +[@grossberg1976a; @grossberg1976a; @Grossberg1980HowDA; @grossberg2013adaptive; @da2019survey]. ART models are designed to dynamically learn and adapt to new patterns without catastrophic forgetting, making them suitable for real-time systems that require continuous learning. @@ -78,8 +78,8 @@ computation via Python’s scientific stack (NumPy[@harris2020array], SciPy In particular, the modular nature of this library allows for the creation of never-before published compound ART models such as Dual Vigilance -Fusion ART [@da2019dual, @tan2007intelligence] or Quadratic Neuron SMART -[@su2001application, @su2005new, @bartfai1994hierarchical]. Such flexibility offers +Fusion ART [@da2019dual; @tan2007intelligence] or Quadratic Neuron SMART +[@su2001application; @su2005new; @bartfai1994hierarchical]. Such flexibility offers powerful experimental and time-saving advantages to researchers and practitioners when evaluating models on their data. From 3dd1db913682a3ac36442593e245659a6d749976 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 18 Oct 2024 16:03:21 -0500 Subject: [PATCH 120/139] paper and updated bib --- paper.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/paper.md b/paper.md index 121c308..2157dd1 100644 --- a/paper.md +++ b/paper.md @@ -1,6 +1,5 @@ --- title: 'Adaptive Resonance Lib: A Python package for Adaptive Resonance Theory (ART) models' - tags: - Python - clustering @@ -8,7 +7,6 @@ tags: - regression - reinforcement learning - machine learning - authors: - name: Niklas M. Melton orcid: 0000-0001-9625-7086 @@ -19,7 +17,6 @@ authors: - name: Donald C. Wunsch II orcid: 0000-0002-9726-9051 affiliation: "1, 2, 3" - affiliations: - name: Missouri University of Science and Technology, USA index: 1 @@ -28,7 +25,6 @@ affiliations: - name: Kummer Institute Center for Artificial Intelligence and Autonomous Systems (KICAIAS), USA index: 3 - date: 18 October 2024 bibliography: references.bib --- From e261f076dacbd35d70d6ffc70091776619f705f3 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 18 Oct 2024 16:06:35 -0500 Subject: [PATCH 121/139] add corresponding author --- paper.md | 1 + 1 file changed, 1 insertion(+) diff --git a/paper.md b/paper.md index 2157dd1..766ca6f 100644 --- a/paper.md +++ b/paper.md @@ -9,6 +9,7 @@ tags: - machine learning authors: - name: Niklas M. Melton + corresponding: true orcid: 0000-0001-9625-7086 affiliation: "1, 2" - name: Dustin Tanksley From bf63e000b2fc3c401ea140d0b02ce39b1798cf99 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 18 Oct 2024 16:11:44 -0500 Subject: [PATCH 122/139] add ror --- paper.md | 1 + 1 file changed, 1 insertion(+) diff --git a/paper.md b/paper.md index 766ca6f..e0a7e7a 100644 --- a/paper.md +++ b/paper.md @@ -21,6 +21,7 @@ authors: affiliations: - name: Missouri University of Science and Technology, USA index: 1 + ror: 00scwqd12 - name: National Science Foundation (NSF), USA index: 2 - name: Kummer Institute Center for Artificial Intelligence and Autonomous Systems From 497a302102d86f7a13b799fa27ab04bb1762962a Mon Sep 17 00:00:00 2001 From: niklas melton Date: Fri, 18 Oct 2024 16:18:15 -0500 Subject: [PATCH 123/139] update affiliation --- paper.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/paper.md b/paper.md index e0a7e7a..9ec3a94 100644 --- a/paper.md +++ b/paper.md @@ -11,22 +11,17 @@ authors: - name: Niklas M. Melton corresponding: true orcid: 0000-0001-9625-7086 - affiliation: "1, 2" + affiliation: 1 - name: Dustin Tanksley orcid: 0000-0002-1677-0304 - affiliation: "1, 2" + affiliation: 1 - name: Donald C. Wunsch II orcid: 0000-0002-9726-9051 - affiliation: "1, 2, 3" + affiliation: 1 affiliations: - name: Missouri University of Science and Technology, USA index: 1 ror: 00scwqd12 -- name: National Science Foundation (NSF), USA - index: 2 - - name: Kummer Institute Center for Artificial Intelligence and Autonomous Systems - (KICAIAS), USA - index: 3 date: 18 October 2024 bibliography: references.bib --- From a937f8ef4bf63c6eb868e369f2bc66fd3e5f122c Mon Sep 17 00:00:00 2001 From: niklas melton Date: Sat, 19 Oct 2024 16:36:56 -0500 Subject: [PATCH 124/139] new sections --- paper.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/paper.md b/paper.md index 9ec3a94..467005b 100644 --- a/paper.md +++ b/paper.md @@ -87,6 +87,12 @@ cases, and leverage ART for novel research problems. Its integration with popula libraries ensures that **artlib** remains adaptable and applicable to current machine learning challenges. +# Target Audience + +# Comparison to Existing Implementations + +# Adaptive Resonance Theory (ART) + # Acknowledgements This research was supported by the National Science Foundation (NSF) under Award Number 2420248. The project titled EAGER: Solving Representation Learning and From 65b3b19af9f9786fe8ebed341458a606d2e133e4 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Sat, 19 Oct 2024 17:08:38 -0500 Subject: [PATCH 125/139] comparison refs --- paper.md | 140 +++++++++++++++++++++++++++++-------------------- references.bib | 91 ++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 57 deletions(-) diff --git a/paper.md b/paper.md index 467005b..f9444f2 100644 --- a/paper.md +++ b/paper.md @@ -29,70 +29,96 @@ bibliography: references.bib # Summary The Adaptive Resonance Library (**artlib**) is a Python library that implements a wide -range of Adaptive Resonance Theory (ART) algorithms. ART is a class of neural networks -that is known for addressing the stability-plasticity dilemma, making it particularly -effective for classification, clustering, and incremental learning tasks -[@grossberg1976a; @grossberg1976a; @Grossberg1980HowDA; @grossberg2013adaptive; -@da2019survey]. ART models are designed to dynamically learn and adapt to new patterns -without catastrophic forgetting, making them suitable for real-time systems that require -continuous learning. - -**artlib** supports a variety of ART-based models including but not limited to -Fuzzy ART [@carpenter1991fuzzy], Hyperpshere ART [@anagnostopoulos2000hypersphere], -Gaussian ART [@williamson1996gaussian], ARTMAP [@carpenter1991artmap], -Fusion ART [@tan2007intelligence], and FALCON [@tan2004falcon]. These models -can be applied to tasks like unsupervised clustering, supervised classification or -regression, and reinforcement learning [@da2019survey]. This library provides an -extensible and modular framework where users can integrate custom models or extend -current implementations, which opens up opportunities for experimenting with existing -and novel machine learning techniques. - -In addition to the zoo of ART models, we provide -implementations of visualization methods for the various cluster geometries as well as -pre-processing techniques such as Visual Assessment of Tendency (VAT)[@bezdek2002vat], -data normalization, and complement coding. - -# Statement of need +range of Adaptive Resonance Theory (ART) algorithms. **artlib** supports eight elementary +ART modules and 11 compound ART modules, including but not limited to Fuzzy ART +[@carpenter1991fuzzy], Hypersphere ART [@anagnostopoulos2000hypersphere], Gaussian ART +[@williamson1996gaussian], ARTMAP [@carpenter1991artmap], Fusion ART +[@tan2007intelligence], and FALCON [@tan2004falcon]. These models can be applied to +tasks like unsupervised clustering, supervised classification, regression, and +reinforcement learning [@da2019survey]. This library provides an extensible and +modular framework where users can integrate custom models or extend current +implementations, allowing for experimentation with existing and novel machine learning +techniques. + +In addition to the diverse ART models, **artlib** offers implementations of +visualization methods for various cluster geometries, along with pre-processing +techniques such as Visual Assessment of Tendency (VAT) [@bezdek2002vat], data +normalization, and complement coding. + +# Statement of Need The Adaptive Resonance Library (**artlib**) is essential for researchers, developers, -and educators interested in adaptive neural networks, specifically ART algorithms. While -the field of machine learning has been dominated by architectures like deep learning, -ART models offer unique advantages in incremental and real-time learning environments -due to their ability to learn new data without forgetting previously learned -information. - -Currently, no comprehensive Python library exists that implements a variety of ART -models in an open-source, modular, and extensible manner. **artlib* fills this gap by -providing a range of ART implementations that are easy to integrate into various machine -learning workflows such as scikit-learn Pipelines and GridSearchCV [@scikit-learn]. -The library is designed for ease of use and performance, offering users fast numerical -computation via Python’s scientific stack (NumPy[@harris2020array], SciPy -[@2020SciPy-NMeth], and scikit-learn [@scikit-learn]). - -In particular, the modular nature of this library allows for the creation of -never-before published compound ART models such as Dual Vigilance -Fusion ART [@da2019dual; @tan2007intelligence] or Quadratic Neuron SMART -[@su2001application; @su2005new; @bartfai1994hierarchical]. Such flexibility offers -powerful experimental and time-saving advantages to researchers and practitioners when -evaluating models on their data. - -Additionally, the library is a valuable educational tool for students learning about -neural networks and adaptive systems. With well-documented code and clear APIs, it -supports hands-on experimentation with ART models, making it an excellent resource for -academic courses or personal projects in artificial intelligence and machine learning. - -Furthermore, **artlib** is actively maintained and designed to support further -extensions, allowing users to add new ART models, adjust parameters for specific use -cases, and leverage ART for novel research problems. Its integration with popular Python -libraries ensures that **artlib** remains adaptable and applicable to current machine -learning challenges. - -# Target Audience +and educators interested in adaptive neural networks, specifically ART algorithms. +While deep learning dominates machine learning, ART models offer unique advantages +in incremental and real-time learning environments due to their ability to learn new +data without forgetting previously learned information. + +Currently, no comprehensive Python library implements a variety of ART models in an +open-source, modular, and extensible manner. **artlib** fills this gap by offering a +range of ART implementations that integrate seamlessly with machine learning workflows, +including scikit-learn Pipelines and GridSearchCV [@scikit-learn]. The library is +designed for ease of use and high performance, leveraging Python's scientific stack +(NumPy [@harris2020array], SciPy [@2020SciPy-NMeth], and scikit-learn [@scikit-learn]) +for fast numerical computation. + +The modular design of **artlib** also enables users to create novel compound ART models, +such as Dual Vigilance Fusion ART [@da2019dual; @tan2007intelligence] or +Quadratic Neuron SMART [@su2001application; @su2005new; @bartfai1994hierarchical]. +This flexibility offers powerful experimental and time-saving benefits, allowing +researchers and practitioners to evaluate models on diverse datasets efficiently. + +Additionally, the library serves as a valuable educational tool, providing +well-documented code and clear APIs to support hands-on experimentation with ART +models. It is ideal for academic courses or personal projects in artificial +intelligence and machine learning, making **artlib** a versatile resource. + +**artlib** is actively maintained and designed for future extension, allowing users +to add new ART models, adjust parameters for specific applications, and explore ART's +potential for novel research problems. Its integration with popular Python libraries +ensures its adaptability to current machine learning challenges. # Comparison to Existing Implementations +While there are a few libraries and toolboxes that provide implementations of specific +ART models [@birkjohann2023artpython; @aiopenlab2023art; @dilekman2022artificial; +@artpy2022; @dixit2020adaptive; @inyoot2021art; @valixandra2021adaptive; +@wan2022art2; @ray2023artpy], +they tend to be limited +in scope or +platform-specific. For instance, +MATLAB-based ART toolboxes often provide implementations of Fuzzy ART and ARTMAP models, +but they lack the flexibility and modularity required for broader experimentation, and +they are not easily accessible to Python-based workflows. + +Other existing implementations of ART models may provide standalone versions of +individual models, but they are often not designed to integrate seamlessly with modern +Python libraries such as scikit-learn, NumPy, and SciPy. As a result, researchers and +developers working in Python-based environments face challenges when trying to +incorporate ART models into their machine learning pipelines. + +**artlib** fills this gap by offering a comprehensive and modular collection of ART +models, including both elementary and compound ART architectures. It is designed for +interoperability with popular Python tools, enabling users to easily integrate ART +models into machine learning workflows, optimize models using scikit-learn's +GridSearchCV, and preprocess data using standard libraries. This flexibility and +integration make **artlib** a powerful resource for both research and practical +applications. + # Adaptive Resonance Theory (ART) +ART is a class of neural networks known for solving the stability-plasticity dilemma, +making it particularly effective for classification, clustering, and incremental +learning tasks [@grossberg1976a; @grossberg1976a; @Grossberg1980HowDA; +@grossberg2013adaptive; @da2019survey]. ART models are designed to dynamically learn +and adapt to new patterns without catastrophic forgetting, making them ideal for +real-time systems requiring continuous learning. + +The ability of ART to preserve previously learned patterns while learning new data +in real-time has made it a powerful tool in domains such as robotics, medical +diagnosis, and adaptive control systems. **artlib** aims to extend the application of +these models in modern machine learning pipelines, offering a unique toolkit for +leveraging ART's strengths. + # Acknowledgements This research was supported by the National Science Foundation (NSF) under Award Number 2420248. The project titled EAGER: Solving Representation Learning and diff --git a/references.bib b/references.bib index 662ed44..acb8fb2 100644 --- a/references.bib +++ b/references.bib @@ -299,3 +299,94 @@ @article{grossberg2013adaptive year={2013}, publisher={Elsevier} } + +@misc{birkjohann2023artpython, + author = {Christian Birkjohann}, + title = {art-python: Adaptive Resonance Theory in Python}, + year = {2023}, + url = {https://github.com/cbirkj/art-python}, + note = {GitHub repository}, + howpublished = {\url{https://github.com/cbirkj/art-python}}, + version = {v0.1.0}, + urldate = {2024-10-19} +} +@misc{aiopenlab2023art, + author = {{AI OpenLab}}, + title = {ART: Adaptive Resonance Theory Implementation}, + year = {2023}, + url = {https://github.com/AIOpenLab/art}, + note = {GitHub repository}, + howpublished = {\url{https://github.com/AIOpenLab/art}}, + version = {v1.0.0}, + urldate = {2024-10-19} +} +@misc{dilekman2022artificial, + author = {Kerem Dilekman}, + title = {Artificial Neural Network - Adaptive Resonance Theory}, + year = {2022}, + url = {https://github.com/KeremDlkmn/artificial-neural-network-adaptive-resonance-theory}, + note = {GitHub repository}, + howpublished = {\url{https://github.com/KeremDlkmn/artificial-neural-network-adaptive-resonance-theory}}, + version = {v0.1.0}, + urldate = {2024-10-19} +} +@misc{artpy2022, + author = {Sergey Stepanov}, + title = {ARTpy: A Python Implementation of Adaptive Resonance Theory}, + year = {2022}, + url = {https://github.com/DeadAt0m/ARTpy}, + note = {GitHub repository}, + howpublished = {\url{https://github.com/DeadAt0m/ARTpy}}, + version = {v1.0.0}, + urldate = {2024-10-19} +} +@misc{dixit2020adaptive, + author = {Meeti Dixit}, + title = {Adaptive Resonance Theory}, + year = {2020}, + url = {https://github.com/MeetiDixit/AdaptiveResonanceTheory}, + note = {GitHub repository}, + howpublished = {\url{https://github.com/MeetiDixit/AdaptiveResonanceTheory}}, + version = {v1.0.0}, + urldate = {2024-10-19} +} +@misc{inyoot2021art, + author = {Inyoot}, + title = {ART: Adaptive Resonance Theory Implementation in Python}, + year = {2021}, + url = {https://github.com/inyoot/art}, + note = {GitHub repository}, + howpublished = {\url{https://github.com/inyoot/art}}, + version = {v0.1.0}, + urldate = {2024-10-19} +} +@misc{valixandra2021adaptive, + author = {Filip Valixandra}, + title = {Adaptive Resonance Theory}, + year = {2021}, + url = {https://github.com/flppvalxndra/Adaptive-Resonance-Theory}, + note = {GitHub repository}, + howpublished = {\url{https://github.com/flppvalxndra/Adaptive-Resonance-Theory}}, + version = {v1.0.0}, + urldate = {2024-10-19} +} +@misc{wan2022art2, + author = {YuChang Wan}, + title = {ART2: Adaptive Resonance Theory 2 Implementation}, + year = {2022}, + url = {https://github.com/YuChangWan/ART2}, + note = {GitHub repository}, + howpublished = {\url{https://github.com/YuChangWan/ART2}}, + version = {v1.0.0}, + urldate = {2024-10-19} +} +@misc{ray2023artpy, + author = {Ray11641}, + title = {artpy: Adaptive Resonance Theory in Python}, + year = {2023}, + url = {https://github.com/Ray11641/artpy}, + note = {GitHub repository}, + howpublished = {\url{https://github.com/Ray11641/artpy}}, + version = {v0.1.0}, + urldate = {2024-10-19} +} From 37252ce7c3abcf67948540ec7c630709038fd686 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Sun, 20 Oct 2024 21:23:01 -0500 Subject: [PATCH 126/139] paper draft and bartmap citations --- artlib/biclustering/BARTMAP.py | 45 +++++++++++---------- docs/source/references.rst | 3 +- paper.md | 71 +++++++++++++++++++++------------- references.bib | 70 +++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 51 deletions(-) diff --git a/artlib/biclustering/BARTMAP.py b/artlib/biclustering/BARTMAP.py index 8aab640..57b5b79 100644 --- a/artlib/biclustering/BARTMAP.py +++ b/artlib/biclustering/BARTMAP.py @@ -1,16 +1,13 @@ -"""BARTMAP. - -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. - -Xu, R., Wunsch II, D. C., & Kim, S. (2012). -Methods and systems for biclustering algorithm. -U.S. Patent 9,043,326 Filed January 28, 2012, -claiming priority to Provisional U.S. Patent Application, -January 28, 2011, issued May 26, 2015. - -""" +"""BARTMAP :cite:`xu2011bartmap`, :cite:`xu2012biclustering`.""" +# 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. +# +# Xu, R., Wunsch II, D. C., & Kim, S. (2012). +# Methods and systems for biclustering algorithm. +# U.S. Patent 9,043,326 Filed January 28, 2012, +# claiming priority to Provisional U.S. Patent Application, +# January 28, 2011, issued May 26, 2015. import numpy as np from typing import Optional @@ -25,16 +22,18 @@ class BARTMAP(BaseEstimator, BiclusterMixin): """BARTMAP for Biclustering. 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 - 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. + :cite:`xu2011bartmap`. + + .. # 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 :class:`~artlib.common.BaseART.BaseART` 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. """ diff --git a/docs/source/references.rst b/docs/source/references.rst index 45f1346..4a4759a 100644 --- a/docs/source/references.rst +++ b/docs/source/references.rst @@ -2,5 +2,4 @@ References ================================== .. bibliography:: ../../references.bib - :style: unsrt - :all: \ No newline at end of file + :style: unsrt \ No newline at end of file diff --git a/paper.md b/paper.md index f9444f2..79b81ac 100644 --- a/paper.md +++ b/paper.md @@ -30,12 +30,17 @@ bibliography: references.bib The Adaptive Resonance Library (**artlib**) is a Python library that implements a wide range of Adaptive Resonance Theory (ART) algorithms. **artlib** supports eight elementary -ART modules and 11 compound ART modules, including but not limited to Fuzzy ART -[@carpenter1991fuzzy], Hypersphere ART [@anagnostopoulos2000hypersphere], Gaussian ART -[@williamson1996gaussian], ARTMAP [@carpenter1991artmap], Fusion ART -[@tan2007intelligence], and FALCON [@tan2004falcon]. These models can be applied to -tasks like unsupervised clustering, supervised classification, regression, and -reinforcement learning [@da2019survey]. This library provides an extensible and +ART modules and 11 compound ART modules, including Fuzzy ART [@carpenter1991fuzzy], +Hypersphere ART [@anagnostopoulos2000hypersphere], Ellipsoid ART +[@anagnostopoulos2001a; @anagnostopoulos2001b], Gaussian ART +[@williamson1996gaussian], Bayesian ART [@vigdor2007bayesian], Quadratic Neuron +ART [@su2001application; @su2005new], ARTMAP [@carpenter1991artmap], Simplified +ARTMAP [@gotarredona1998adaptive], SMART [@bartfai1994hierarchical], TopoART +[@tscherepanow2010topoart], Dual Vigilance ART [@da2019dual], CVIART [@da2022icvi], +BARTMAP [@xu2011bartmap; @xu2012biclustering], Fusion ART [@tan2007intelligence], +FALCON [@tan2004falcon], and TD-FALCON [@tan2008integrating]. These models can be +applied to tasks like unsupervised clustering, supervised classification, regression, +and reinforcement learning [@da2019survey]. This library provides an extensible and modular framework where users can integrate custom models or extend current implementations, allowing for experimentation with existing and novel machine learning techniques. @@ -56,12 +61,12 @@ data without forgetting previously learned information. Currently, no comprehensive Python library implements a variety of ART models in an open-source, modular, and extensible manner. **artlib** fills this gap by offering a range of ART implementations that integrate seamlessly with machine learning workflows, -including scikit-learn Pipelines and GridSearchCV [@scikit-learn]. The library is +including scikit-learn's `Pipeline` and `GridSearchCV` [@scikit-learn]. The library is designed for ease of use and high performance, leveraging Python's scientific stack (NumPy [@harris2020array], SciPy [@2020SciPy-NMeth], and scikit-learn [@scikit-learn]) for fast numerical computation. -The modular design of **artlib** also enables users to create novel compound ART models, +The modular design of **artlib** enables users to create novel compound ART models, such as Dual Vigilance Fusion ART [@da2019dual; @tan2007intelligence] or Quadratic Neuron SMART [@su2001application; @su2005new; @bartfai1994hierarchical]. This flexibility offers powerful experimental and time-saving benefits, allowing @@ -73,24 +78,26 @@ models. It is ideal for academic courses or personal projects in artificial intelligence and machine learning, making **artlib** a versatile resource. **artlib** is actively maintained and designed for future extension, allowing users -to add new ART models, adjust parameters for specific applications, and explore ART's +to create new ART models, adjust parameters for specific applications, and explore ART's potential for novel research problems. Its integration with popular Python libraries ensures its adaptability to current machine learning challenges. # Comparison to Existing Implementations -While there are a few libraries and toolboxes that provide implementations of specific -ART models [@birkjohann2023artpython; @aiopenlab2023art; @dilekman2022artificial; -@artpy2022; @dixit2020adaptive; @inyoot2021art; @valixandra2021adaptive; -@wan2022art2; @ray2023artpy], -they tend to be limited -in scope or -platform-specific. For instance, -MATLAB-based ART toolboxes often provide implementations of Fuzzy ART and ARTMAP models, -but they lack the flexibility and modularity required for broader experimentation, and -they are not easily accessible to Python-based workflows. - -Other existing implementations of ART models may provide standalone versions of +While there are several open-source repositories that provide +python implementations of specific ART models [@birkjohann2023artpython; +@aiopenlab2023art; @dilekman2022artificial; @artpy2022; @dixit2020adaptive; +@inyoot2021art; @valixandra2021adaptive; @wan2022art2; @ray2023artpy], they lack +modularity and are limited in scope, often implementing just one or two models. For +instance, MATLAB-based ART toolboxes [@mathworks_art1s; @mathworks_fuzzyart_fuzzyartmap; +@mathworks_topoart; @mathworks_art_fuzzyart_artmap] provide implementations of +Fuzzy ART, TopoART, ART1, and ARTMAP models, but they lack the flexibility and +modularity required for broader experimentation. The most significant existing ART +implementation exists in julia and provides just five models +[@Petrenko_AdaptiveResonance_jl_A_Julia_2022] but, like the previously listed +MATLAB-based toolboxes, it is not easily accessible to Python-based work flows. + +These existing implementations of ART models may provide standalone versions of individual models, but they are often not designed to integrate seamlessly with modern Python libraries such as scikit-learn, NumPy, and SciPy. As a result, researchers and developers working in Python-based environments face challenges when trying to @@ -100,10 +107,11 @@ incorporate ART models into their machine learning pipelines. models, including both elementary and compound ART architectures. It is designed for interoperability with popular Python tools, enabling users to easily integrate ART models into machine learning workflows, optimize models using scikit-learn's -GridSearchCV, and preprocess data using standard libraries. This flexibility and +`GridSearchCV`, and preprocess data using standard libraries. This flexibility and integration make **artlib** a powerful resource for both research and practical applications. + # Adaptive Resonance Theory (ART) ART is a class of neural networks known for solving the stability-plasticity dilemma, @@ -113,12 +121,23 @@ learning tasks [@grossberg1976a; @grossberg1976a; @Grossberg1980HowDA; and adapt to new patterns without catastrophic forgetting, making them ideal for real-time systems requiring continuous learning. -The ability of ART to preserve previously learned patterns while learning new data -in real-time has made it a powerful tool in domains such as robotics, medical -diagnosis, and adaptive control systems. **artlib** aims to extend the application of -these models in modern machine learning pipelines, offering a unique toolkit for +Over the years, dozens of ART variations have been published [@da2019survey], +extending the applicability of ART to nearly all learning regimes, including +reinforcement learning [@tan2004falcon; @tan2008integrating], hierarchical and +topological clustering [@tscherepanow2010topoart; @bartfai1994hierarchical], and +biclustering [@xu2011bartmap; @xu2012biclustering]. These numerous models provide an +ART-based solution for most machine learning use cases. However, the rapid development +of bespoke models and the difficulty in understanding the core principles of ART +have resulted in a lack of open-source and approachable implementations of most +ART variants. + +The ability of ART to preserve previously learned patterns while learning new data in +real-time has made it a powerful tool in domains such as robotics, medical diagnosis, +and adaptive control systems. **artlib** aims to extend the application of these models +in modern machine learning pipelines, offering a unique and approachable toolkit for leveraging ART's strengths. + # Acknowledgements This research was supported by the National Science Foundation (NSF) under Award Number 2420248. The project titled EAGER: Solving Representation Learning and diff --git a/references.bib b/references.bib index acb8fb2..50abb3d 100644 --- a/references.bib +++ b/references.bib @@ -390,3 +390,73 @@ @misc{ray2023artpy version = {v0.1.0}, urldate = {2024-10-19} } +@article{Petrenko_AdaptiveResonance_jl_A_Julia_2022, +author = {Petrenko, Sasha and Wunsch, II, Donald}, +journal = {Journal of Open Source Software}, +month = apr, +title = {{AdaptiveResonance.jl: A Julia Implementation of Adaptive Resonance Theory (ART) Algorithms}}, +url = {https://doi.org/10.21105/joss.03671}, +volume = {7}, +year = {2022} +} + +@misc{mathworks_art1s, + author = {Mark Harris}, + title = {ART1S: Adaptive Resonance Theory 1 (ART1)}, + year = {2000}, + url = {https://www.mathworks.com/matlabcentral/fileexchange/93-art1s-zip}, + note = {MATLAB Central File Exchange}, + howpublished = {\url{https://www.mathworks.com/matlabcentral/fileexchange/93-art1s-zip}}, + urldate = {2024-10-19} +} + +@misc{mathworks_fuzzyart_fuzzyartmap, + author = {Friedhelm Schwenker}, + title = {Fuzzy ART and Fuzzy ARTMAP Neural Networks}, + year = {2004}, + url = {https://www.mathworks.com/matlabcentral/fileexchange/4306-fuzzy-art-and-fuzzy-artmap-neural-networks}, + note = {MATLAB Central File Exchange}, + howpublished = {\url{https://www.mathworks.com/matlabcentral/fileexchange/4306-fuzzy-art-and-fuzzy-artmap-neural-networks}}, + urldate = {2024-10-19} +} + +@misc{mathworks_topoart, + author = {Martial Boitet}, + title = {TopoART Neural Networks}, + year = {2022}, + url = {https://www.mathworks.com/matlabcentral/fileexchange/118455-topoart-neural-networks}, + note = {MATLAB Central File Exchange}, + howpublished = {\url{https://www.mathworks.com/matlabcentral/fileexchange/118455-topoart-neural-networks}}, + urldate = {2024-10-19} +} + +@misc{mathworks_art_fuzzyart_artmap, + author = {Friedhelm Schwenker}, + title = {ART1, Fuzzy ART, ARTMAP, Fuzzy ARTMAP Neural Networks}, + year = {2004}, + url = {https://www.mathworks.com/matlabcentral/fileexchange/3070-art1-fuzzyart-artmap-fuzzyartmap}, + note = {MATLAB Central File Exchange}, + howpublished = {\url{https://www.mathworks.com/matlabcentral/fileexchange/3070-art1-fuzzyart-artmap-fuzzyartmap}}, + urldate = {2024-10-19} +} +@article{xu2011bartmap, + author = {Rui Xu and Donald C. Wunsch II}, + title = {{BARTMAP: A viable structure for biclustering}}, + journal = {Neural Networks}, + volume = {24}, + pages = {709--716}, + year = {2011}, + doi = {10.1016/j.neunet.2011.03.020}, + url = {https://doi.org/10.1016/j.neunet.2011.03.020} +} + +@patent{xu2012biclustering, + author = {Rui Xu and Donald C. Wunsch II and Sang Yong Kim}, + title = {Methods and systems for biclustering algorithm}, + number = {US9,043,326B2}, + year = {2012}, + month = {January 28}, + note = {U.S. Patent}, + url = {https://patents.google.com/patent/US9043326B2/en}, + howpublished = {Filed January 28, 2012, issued May 26, 2015} +} From 6b68b177311929fe6366728309f084c38b89c0c6 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Sun, 20 Oct 2024 21:34:21 -0500 Subject: [PATCH 127/139] rephrase paper a bit --- paper.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/paper.md b/paper.md index 79b81ac..da37ae6 100644 --- a/paper.md +++ b/paper.md @@ -29,9 +29,9 @@ bibliography: references.bib # Summary The Adaptive Resonance Library (**artlib**) is a Python library that implements a wide -range of Adaptive Resonance Theory (ART) algorithms. **artlib** supports eight elementary -ART modules and 11 compound ART modules, including Fuzzy ART [@carpenter1991fuzzy], -Hypersphere ART [@anagnostopoulos2000hypersphere], Ellipsoid ART +range of Adaptive Resonance Theory (ART) algorithms. **artlib** currently supports eight +elementary ART models and 11 compound ART models, including Fuzzy ART +[@carpenter1991fuzzy], Hypersphere ART [@anagnostopoulos2000hypersphere], Ellipsoid ART [@anagnostopoulos2001a; @anagnostopoulos2001b], Gaussian ART [@williamson1996gaussian], Bayesian ART [@vigdor2007bayesian], Quadratic Neuron ART [@su2001application; @su2005new], ARTMAP [@carpenter1991artmap], Simplified @@ -39,13 +39,13 @@ ARTMAP [@gotarredona1998adaptive], SMART [@bartfai1994hierarchical], TopoART [@tscherepanow2010topoart], Dual Vigilance ART [@da2019dual], CVIART [@da2022icvi], BARTMAP [@xu2011bartmap; @xu2012biclustering], Fusion ART [@tan2007intelligence], FALCON [@tan2004falcon], and TD-FALCON [@tan2008integrating]. These models can be -applied to tasks like unsupervised clustering, supervised classification, regression, +applied to tasks such as unsupervised clustering, supervised classification, regression, and reinforcement learning [@da2019survey]. This library provides an extensible and modular framework where users can integrate custom models or extend current implementations, allowing for experimentation with existing and novel machine learning techniques. -In addition to the diverse ART models, **artlib** offers implementations of +In addition to the diverse set of ART models, **artlib** offers implementations of visualization methods for various cluster geometries, along with pre-processing techniques such as Visual Assessment of Tendency (VAT) [@bezdek2002vat], data normalization, and complement coding. @@ -73,7 +73,7 @@ This flexibility offers powerful experimental and time-saving benefits, allowing researchers and practitioners to evaluate models on diverse datasets efficiently. Additionally, the library serves as a valuable educational tool, providing -well-documented code and clear APIs to support hands-on experimentation with ART +well-documented code and familiar APIs to support hands-on experimentation with ART models. It is ideal for academic courses or personal projects in artificial intelligence and machine learning, making **artlib** a versatile resource. @@ -103,14 +103,13 @@ Python libraries such as scikit-learn, NumPy, and SciPy. As a result, researcher developers working in Python-based environments face challenges when trying to incorporate ART models into their machine learning pipelines. -**artlib** fills this gap by offering a comprehensive and modular collection of ART -models, including both elementary and compound ART architectures. It is designed for -interoperability with popular Python tools, enabling users to easily integrate ART -models into machine learning workflows, optimize models using scikit-learn's -`GridSearchCV`, and preprocess data using standard libraries. This flexibility and -integration make **artlib** a powerful resource for both research and practical -applications. - +**artlib** addresses these challenges by offering a comprehensive and modular +collection of ART models, including both elementary and compound ART architectures. +It is designed for interoperability with popular Python tools, enabling users to easily +integrate ART models into machine learning workflows, optimize models using +scikit-learn's `GridSearchCV`, and preprocess data using standard libraries. This +flexibility and integration make **artlib** a powerful resource for both research and +practical applications. # Adaptive Resonance Theory (ART) @@ -137,8 +136,8 @@ and adaptive control systems. **artlib** aims to extend the application of these in modern machine learning pipelines, offering a unique and approachable toolkit for leveraging ART's strengths. - # Acknowledgements + This research was supported by the National Science Foundation (NSF) under Award Number 2420248. The project titled EAGER: Solving Representation Learning and Catastrophic Forgetting with Adaptive Resonance Theory provided essential funding for From 5c831a49fdc93c75dafc1a04baf3efb6bb6cef00 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Tue, 22 Oct 2024 02:06:42 -0500 Subject: [PATCH 128/139] pre-commit workflow --- .github/workflows/pre_commit_checks.yml | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/pre_commit_checks.yml diff --git a/.github/workflows/pre_commit_checks.yml b/.github/workflows/pre_commit_checks.yml new file mode 100644 index 0000000..4b32f6a --- /dev/null +++ b/.github/workflows/pre_commit_checks.yml @@ -0,0 +1,45 @@ +name: Pre-commit checks + +on: + pull_request: + push: + branches: + - develop + - main + +jobs: + pre-commit: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' # Adjust this to your desired Python version + + - name: Install Poetry + run: | + curl -sSL https://install.python-poetry.org | python3 - + + - name: Install dependencies + run: | + poetry install --with dev + + - name: Install pre-commit + run: | + poetry run pip install pre-commit + + - name: Fetch the base branch + run: | + git fetch origin ${GITHUB_BASE_REF}:${GITHUB_BASE_REF} + env: + GITHUB_BASE_REF: ${{ github.event.pull_request.base.ref }} + + - name: Run pre-commit on changed files + run: | + poetry run pre-commit run --from-ref origin/${GITHUB_BASE_REF} --to-ref HEAD + env: + GITHUB_BASE_REF: ${{ github.event.pull_request.base.ref }} From 4f221e5cb4979679521b1ef28fc2669947766f3b Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Tue, 22 Oct 2024 03:30:19 -0500 Subject: [PATCH 129/139] improve convex hull ART --- artlib/experimental/ConvexHullART.py | 233 +++++++++++---------------- examples/demo_convex_hull_art.py | 2 +- 2 files changed, 93 insertions(+), 142 deletions(-) diff --git a/artlib/experimental/ConvexHullART.py b/artlib/experimental/ConvexHullART.py index 30927be..c0f37f3 100644 --- a/artlib/experimental/ConvexHullART.py +++ b/artlib/experimental/ConvexHullART.py @@ -64,71 +64,6 @@ def volume_of_simplex(vertices: np.ndarray) -> float: return np.abs(np.linalg.det(matrix)) / np.math.factorial(len(vertices) - 1) -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) - - def point_to_line_segment_distance(P, Q1, Q2): - """Calculate the minimum distance between point P and line segment Q1-Q2.""" - line_vec = Q2 - Q1 - point_vec = P - Q1 - line_len = np.dot(line_vec, line_vec) - - if line_len == 0: # Q1 and Q2 are the same point - return np.linalg.norm(P - Q1) - - t = max(0, min(1, np.dot(point_vec, line_vec) / line_len)) - projection = Q1 + t * line_vec - return np.linalg.norm(P - projection) - - def line_segment_to_line_segment_distance(A1, A2, B1, B2): - """Calculate the minimum distance between two line segments A1-A2 and B1-B2.""" - distances = [ - point_to_line_segment_distance(A1, B1, B2), - point_to_line_segment_distance(A2, B1, B2), - point_to_line_segment_distance(B1, A1, A2), - point_to_line_segment_distance(B2, A1, A2), - ] - return min(distances) - - # Determine the cases and compute the distance - if a1.shape[0] == 1 and a2.shape[0] == 1: - # Case 1: Point to point - return point_to_point_distance(a1[0], a2[0]) - - elif a1.shape[0] == 1 and a2.shape[0] == 2: - # Case 2: Point to line segment - return point_to_line_segment_distance(a1[0], a2[0], a2[1]) - - elif a1.shape[0] == 2 and a2.shape[0] == 1: - # Case 3: Line segment to point - return point_to_line_segment_distance(a2[0], a1[0], a1[1]) - - elif a1.shape[0] == 2 and a2.shape[0] == 2: - # Case 4: Line segment to line segment - return line_segment_to_line_segment_distance(a1[0], a1[1], a2[0], a2[1]) - else: - raise RuntimeError("INVALID DISTANCE CALCULATION") - - class PseudoConvexHull: def __init__(self, points: np.ndarray): """ @@ -149,6 +84,13 @@ def vertices(self): def add_points(self, points): self.points = np.vstack([self.points, points]) + @property + def area(self): + if self.points.shape[0] == 1: + return 0 + else: + return 2*np.linalg.norm(self.points[0,:]-self.points[1,:],ord=2) + HullTypes = Union[ConvexHull, PseudoConvexHull] @@ -193,7 +135,7 @@ class ConvexHullART(BaseART): ConvexHull ART for Clustering """ - def __init__(self, rho: float, merge_rho: float): + def __init__(self, rho: float, alpha: float): """ Initializes the ConvexHullART object. @@ -201,11 +143,9 @@ def __init__(self, rho: float, merge_rho: float): ---------- rho : float Vigilance parameter. - merge_rho : float - Merge vigilance parameter. """ - params = {"rho": rho, "merge_rho": merge_rho} + params = {"rho": rho, "alpha": alpha} super().__init__(params) @staticmethod @@ -222,6 +162,9 @@ def validate_params(params: dict): assert "rho" in params assert params["rho"] >= 0.0 assert isinstance(params["rho"], float) + assert "alpha" in params + assert params["alpha"] >= 0.0 + assert isinstance(params["alpha"], float) def category_choice( self, i: np.ndarray, w: HullTypes, params: dict @@ -246,9 +189,9 @@ def category_choice( Cache used for later processing. """ + if isinstance(w, PseudoConvexHull): - d = minimum_distance(i.reshape((1, -1)), w.points) - activation = 1.0 - d ** len(i) + if w.points.shape[0] == 1: new_w = deepcopy(w) new_w.add_points(i.reshape((1, -1))) @@ -260,9 +203,13 @@ def category_choice( else: new_w = ConvexHull(w.points[w.vertices, :], incremental=True) new_w.add_points(i.reshape((1, -1))) - activation = w.area / new_w.area - cache = {"new_w": new_w, "activation": activation} + a_max = float(2*len(i)) + new_area = a_max - new_w.area + activation = new_area / (a_max-w.area + params["alpha"]) + + cache = {"new_w": new_w, "new_area": new_area} + return activation, cache def match_criterion( @@ -294,7 +241,11 @@ def match_criterion( Cache used for later processing. """ - return cache["activation"], cache + assert cache is not None + M = float(cache["new_area"]) / float(2*len(i)) + # cache["match_criterion"] = M + + return M, cache def update( self, @@ -344,73 +295,73 @@ def new_weight(self, i: np.ndarray, params: dict) -> HullTypes: """ 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, :]] - ) - - if isinstance(w1, PseudoConvexHull) and isinstance( - w2, PseudoConvexHull - ): - d = minimum_distance(w1.points, w2.points) - activation = 1.0 - d ** w1.points.shape[1] - else: - new_w = ConvexHull(combined_points) - if isinstance(w1, ConvexHull): - a1 = w1.area / new_w.area - else: - a1 = np.nan - if isinstance(w2, ConvexHull): - a2 = w2.area / new_w.area - else: - a2 = np.nan - activation = np.max([a1, a2]) - - if activation > self.params["merge_rho"]: - return True - else: - return False - - merges = merge_objects(self.W, can_merge) - - new_W = [] - new_sample_counter = [] - new_labels = np.copy(self.labels_) - for i in range(len(merges)): - new_labels[np.isin(self.labels_, merges[i])] = i - new_sample_counter.append( - sum(self.weight_sample_counter_[j] for j in merges[i]) - ) - if len(merges[i]) > 1: - new_points = np.vstack([self.W[j].points for j in merges[i]]) - if new_points.shape[0] > 2: - new_W.append(ConvexHull(new_points, incremental=True)) - else: - new_W.append(PseudoConvexHull(new_points)) - else: - new_W.append(self.W[merges[i][0]]) - self.W = new_W - self.weight_sample_counter_ = new_sample_counter - self.labels_ = new_labels - - def post_fit(self, X: np.ndarray): - """ - Function called after fit. Useful for cluster pruning. - - Parameters - ---------- - X : np.ndarray - Data set. - - """ - self.merge_clusters() + # + # 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, :]] + # ) + # + # if isinstance(w1, PseudoConvexHull) and isinstance( + # w2, PseudoConvexHull + # ): + # d = minimum_distance(w1.points, w2.points) + # activation = 1.0 - d ** w1.points.shape[1] + # else: + # new_w = ConvexHull(combined_points) + # if isinstance(w1, ConvexHull): + # a1 = w1.area / new_w.area + # else: + # a1 = np.nan + # if isinstance(w2, ConvexHull): + # a2 = w2.area / new_w.area + # else: + # a2 = np.nan + # activation = np.max([a1, a2]) + # + # if activation > self.params["merge_rho"]: + # return True + # else: + # return False + # + # merges = merge_objects(self.W, can_merge) + # + # new_W = [] + # new_sample_counter = [] + # new_labels = np.copy(self.labels_) + # for i in range(len(merges)): + # new_labels[np.isin(self.labels_, merges[i])] = i + # new_sample_counter.append( + # sum(self.weight_sample_counter_[j] for j in merges[i]) + # ) + # if len(merges[i]) > 1: + # new_points = np.vstack([self.W[j].points for j in merges[i]]) + # if new_points.shape[0] > 2: + # new_W.append(ConvexHull(new_points, incremental=True)) + # else: + # new_W.append(PseudoConvexHull(new_points)) + # else: + # new_W.append(self.W[merges[i][0]]) + # self.W = new_W + # self.weight_sample_counter_ = new_sample_counter + # self.labels_ = new_labels + # + # def post_fit(self, X: np.ndarray): + # """ + # Function called after fit. Useful for cluster pruning. + # + # Parameters + # ---------- + # X : np.ndarray + # Data set. + # + # """ + # self.merge_clusters() def get_cluster_centers(self) -> List[np.ndarray]: """ diff --git a/examples/demo_convex_hull_art.py b/examples/demo_convex_hull_art.py index 1642553..326b7fc 100644 --- a/examples/demo_convex_hull_art.py +++ b/examples/demo_convex_hull_art.py @@ -16,7 +16,7 @@ def cluster_blobs(): ) print("Data has shape:", data.shape) - params = {"rho": 0.85, "merge_rho": 0.8} + params = {"rho": 0.6, "alpha": 1e-3} cls = ConvexHullART(**params) X = cls.prepare_data(data) From 7b86c335d5b67d06447cb3aa797f1d7e3a8e1550 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Tue, 22 Oct 2024 03:33:08 -0500 Subject: [PATCH 130/139] improve convex hull ART --- artlib/experimental/ConvexHullART.py | 71 ++-------------------------- 1 file changed, 3 insertions(+), 68 deletions(-) diff --git a/artlib/experimental/ConvexHullART.py b/artlib/experimental/ConvexHullART.py index c0f37f3..4a5bf8a 100644 --- a/artlib/experimental/ConvexHullART.py +++ b/artlib/experimental/ConvexHullART.py @@ -143,6 +143,8 @@ def __init__(self, rho: float, alpha: float): ---------- rho : float Vigilance parameter. + alpha : float + Choice parameter. """ params = {"rho": rho, "alpha": alpha} @@ -243,7 +245,7 @@ def match_criterion( """ assert cache is not None M = float(cache["new_area"]) / float(2*len(i)) - # cache["match_criterion"] = M + cache["match_criterion"] = M return M, cache @@ -295,73 +297,6 @@ def new_weight(self, i: np.ndarray, params: dict) -> HullTypes: """ 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, :]] - # ) - # - # if isinstance(w1, PseudoConvexHull) and isinstance( - # w2, PseudoConvexHull - # ): - # d = minimum_distance(w1.points, w2.points) - # activation = 1.0 - d ** w1.points.shape[1] - # else: - # new_w = ConvexHull(combined_points) - # if isinstance(w1, ConvexHull): - # a1 = w1.area / new_w.area - # else: - # a1 = np.nan - # if isinstance(w2, ConvexHull): - # a2 = w2.area / new_w.area - # else: - # a2 = np.nan - # activation = np.max([a1, a2]) - # - # if activation > self.params["merge_rho"]: - # return True - # else: - # return False - # - # merges = merge_objects(self.W, can_merge) - # - # new_W = [] - # new_sample_counter = [] - # new_labels = np.copy(self.labels_) - # for i in range(len(merges)): - # new_labels[np.isin(self.labels_, merges[i])] = i - # new_sample_counter.append( - # sum(self.weight_sample_counter_[j] for j in merges[i]) - # ) - # if len(merges[i]) > 1: - # new_points = np.vstack([self.W[j].points for j in merges[i]]) - # if new_points.shape[0] > 2: - # new_W.append(ConvexHull(new_points, incremental=True)) - # else: - # new_W.append(PseudoConvexHull(new_points)) - # else: - # new_W.append(self.W[merges[i][0]]) - # self.W = new_W - # self.weight_sample_counter_ = new_sample_counter - # self.labels_ = new_labels - # - # def post_fit(self, X: np.ndarray): - # """ - # Function called after fit. Useful for cluster pruning. - # - # Parameters - # ---------- - # X : np.ndarray - # Data set. - # - # """ - # self.merge_clusters() def get_cluster_centers(self) -> List[np.ndarray]: """ From 33bad62ca8ac479736a844853212ae1d582ddfd0 Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Tue, 22 Oct 2024 14:44:41 -0500 Subject: [PATCH 131/139] generalize ConvexHullART to allow alpha-shapes --- .../{ConvexHullART.py => HullART.py} | 163 +++++++++++++----- docs/source/artlib.experimental.rst | 4 +- examples/demo_convex_hull_art.py | 15 +- templates/ART_template.py | 2 +- 4 files changed, 134 insertions(+), 50 deletions(-) rename artlib/experimental/{ConvexHullART.py => HullART.py} (62%) diff --git a/artlib/experimental/ConvexHullART.py b/artlib/experimental/HullART.py similarity index 62% rename from artlib/experimental/ConvexHullART.py rename to artlib/experimental/HullART.py index 4a5bf8a..df472b5 100644 --- a/artlib/experimental/ConvexHullART.py +++ b/artlib/experimental/HullART.py @@ -3,12 +3,14 @@ from copy import deepcopy from typing import Optional, Iterable, List, Tuple, Union, Dict from scipy.spatial import ConvexHull +from shapely import Polygon +from alphashape import alphashape from artlib.common.BaseART import BaseART from artlib.experimental.merging import merge_objects -def plot_convex_polygon( +def plot_polygon( vertices: np.ndarray, ax: Axes, line_color: str = "b", @@ -91,17 +93,13 @@ def area(self): else: return 2*np.linalg.norm(self.points[0,:]-self.points[1,:],ord=2) - -HullTypes = Union[ConvexHull, PseudoConvexHull] - - -def centroid_of_convex_hull(hull: HullTypes): +def centroid_of_convex_hull(hull: Union[PseudoConvexHull, ConvexHull]): """ Finds the centroid of the volume of a convex hull in n-dimensional space. Parameters ---------- - hull : HullTypes + hull : Union[PseudoConvexHull, ConvexHull] A ConvexHull or PseudoConvexHull object. Returns @@ -129,15 +127,98 @@ def centroid_of_convex_hull(hull: HullTypes): centroid /= total_volume return centroid +class GeneralHull: + def __init__(self, points: np.ndarray, alpha: float = 0.0): + self.dim = points.shape[1] + self.alpha = alpha + if points.shape[0] <= 2: + self.hull = PseudoConvexHull(points) + elif points.shape[0] == 3 or alpha == 0.0: + self.hull = ConvexHull(points, incremental=True) + else: + self.hull = alphashape(points, alpha=self.alpha) + + def add_points(self, points: np.ndarray): + if isinstance(self.hull, PseudoConvexHull): + if self.hull.points.shape[0] == 1: + self.hull.add_points(points.reshape((-1, self.dim))) + else: + new_points = np.vstack( + [ + self.hull.points[self.hull.vertices, :], + points.reshape((-1, self.dim)) + ] + ) + self.hull = ConvexHull(new_points, incremental=True) + elif isinstance(self.hull, ConvexHull) and self.alpha == 0.0: + self.hull.add_points(i.reshape((-1, self.dim))) + else: + if isinstance(self.hull, ConvexHull): + new_points = np.vstack( + [ + self.hull.points[self.hull.vertices, :], + points.reshape((-1, self.dim)) + ] + ) + self.hull = alphashape(new_points, alpha=self.alpha) + else: + new_points = np.vstack( + [ + np.asarray(self.hull.exterior.coords), + points.reshape((-1, self.dim)) + ] + ) + self.hull = alphashape(new_points, alpha=self.alpha) + + @property + def area(self): + if isinstance(self.hull, (PseudoConvexHull, ConvexHull)) or self.dim > 2: + return self.hull.area + else: + return self.hull.length + + @property + def centroid(self): + if isinstance(self.hull, (PseudoConvexHull, ConvexHull)): + return centroid_of_convex_hull(self.hull) + else: + return self.hull.centroid + + @property + def is_empty(self): + if isinstance(self.hull, (PseudoConvexHull, ConvexHull)): + return False + else: + return self.hull.is_empty + + @property + def vertices(self): + if isinstance(self.hull, ConvexHull): + return self.hull.points[self.hull.vertices, :] + elif isinstance(self.hull, Polygon): + return np.asarray(self.hull.exterior.coords) + else: + return self.hull.points + + def deepcopy(self): + if isinstance(self.hull, Polygon): + points = np.asarray(self.hull.exterior.coords) + return GeneralHull(points, alpha=float(self.alpha)) + elif isinstance(self.hull, ConvexHull): + points = self.hull.points[self.hull.vertices, :] + return GeneralHull(points, alpha=float(self.alpha)) + else: + return deepcopy(self) -class ConvexHullART(BaseART): + +class HullART(BaseART): """ - ConvexHull ART for Clustering + Hull ART for Clustering """ - def __init__(self, rho: float, alpha: float): + def __init__(self, rho: float, alpha: float, alpha_hat: float): """ - Initializes the ConvexHullART object. + Initializes the HullART object. Parameters ---------- @@ -145,9 +226,11 @@ def __init__(self, rho: float, alpha: float): Vigilance parameter. alpha : float Choice parameter. + alpha_hat : float + alpha shape parameter. """ - params = {"rho": rho, "alpha": alpha} + params = {"rho": rho, "alpha": alpha, "alpha_hat": alpha_hat} super().__init__(params) @staticmethod @@ -167,9 +250,12 @@ def validate_params(params: dict): assert "alpha" in params assert params["alpha"] >= 0.0 assert isinstance(params["alpha"], float) + assert "alpha_hat" in params + assert params["alpha_hat"] >= 0.0 + assert isinstance(params["alpha_hat"], float) def category_choice( - self, i: np.ndarray, w: HullTypes, params: dict + self, i: np.ndarray, w: GeneralHull, params: dict ) -> tuple[float, Optional[dict]]: """ Get the activation of the cluster. @@ -178,7 +264,7 @@ def category_choice( ---------- i : np.ndarray Data sample. - w : HullTypes + w : GeneralHull Cluster weight or information. params : dict Dictionary containing parameters for the algorithm. @@ -192,32 +278,25 @@ def category_choice( """ - if isinstance(w, PseudoConvexHull): - - if w.points.shape[0] == 1: - new_w = deepcopy(w) - new_w.add_points(i.reshape((1, -1))) - else: - new_points = np.vstack( - [w.points[w.vertices, :], i.reshape((1, -1))] - ) - new_w = ConvexHull(new_points, incremental=True) - else: - new_w = ConvexHull(w.points[w.vertices, :], incremental=True) - new_w.add_points(i.reshape((1, -1))) + new_w = w.deepcopy() + new_w.add_points(i.reshape((1,-1))) + if new_w.is_empty: + raise RuntimeError( + f"alpha_hat={params['alpha_hat']} results in invalid geometry" + ) a_max = float(2*len(i)) new_area = a_max - new_w.area activation = new_area / (a_max-w.area + params["alpha"]) - cache = {"new_w": new_w, "new_area": new_area} + cache = {"new_w": new_w, "new_area": new_area, "activation": activation} return activation, cache def match_criterion( self, i: np.ndarray, - w: HullTypes, + w: GeneralHull, params: dict, cache: Optional[dict] = None, ) -> Tuple[float, Optional[Dict]]: @@ -228,7 +307,7 @@ def match_criterion( ---------- i : np.ndarray Data sample. - w : HullTypes + w : GeneralHull Cluster weight or information. params : dict Dictionary containing parameters for the algorithm. @@ -252,10 +331,10 @@ def match_criterion( def update( self, i: np.ndarray, - w: HullTypes, + w: GeneralHull, params: dict, cache: Optional[dict] = None, - ) -> HullTypes: + ) -> GeneralHull: """ Get the updated cluster weight. @@ -263,7 +342,7 @@ def update( ---------- i : np.ndarray Data sample. - w : HullTypes + w : GeneralHull Cluster weight or information. params : dict Dictionary containing parameters for the algorithm. @@ -272,13 +351,13 @@ def update( Returns ------- - HullTypes + GeneralHull Updated cluster weight. """ return cache["new_w"] - def new_weight(self, i: np.ndarray, params: dict) -> HullTypes: + def new_weight(self, i: np.ndarray, params: dict) -> GeneralHull: """ Generate a new cluster weight. @@ -291,11 +370,11 @@ def new_weight(self, i: np.ndarray, params: dict) -> HullTypes: Returns ------- - HullTypes + GeneralHull New cluster weight. """ - new_w = PseudoConvexHull(i.reshape((1, -1))) + new_w = GeneralHull(i.reshape((1, -1)), alpha=params["alpha_hat"]) return new_w def get_cluster_centers(self) -> List[np.ndarray]: @@ -310,7 +389,7 @@ def get_cluster_centers(self) -> List[np.ndarray]: """ centers = [] for w in self.W: - centers.append(centroid_of_convex_hull(w)) + centers.append(w.centroid) return centers def plot_cluster_bounds( @@ -330,10 +409,8 @@ def plot_cluster_bounds( """ for c, w in zip(colors, self.W): - if isinstance(w, ConvexHull): - vertices = w.points[w.vertices, :2] - else: - vertices = w.points[:, :2] - plot_convex_polygon( + vertices = w.vertices[:,:2] + + plot_polygon( vertices, ax, line_width=linewidth, line_color=c ) diff --git a/docs/source/artlib.experimental.rst b/docs/source/artlib.experimental.rst index 3e991ae..c973744 100644 --- a/docs/source/artlib.experimental.rst +++ b/docs/source/artlib.experimental.rst @@ -12,10 +12,10 @@ Module contents Submodules ---------- -artlib.experimental.ConvexHullART module +artlib.experimental.HullART module ---------------------------------------- -.. automodule:: artlib.experimental.ConvexHullART +.. automodule:: artlib.experimental.HullART :members: :undoc-members: :show-inheritance: diff --git a/examples/demo_convex_hull_art.py b/examples/demo_convex_hull_art.py index 326b7fc..efa7f77 100644 --- a/examples/demo_convex_hull_art.py +++ b/examples/demo_convex_hull_art.py @@ -2,8 +2,8 @@ import matplotlib.pyplot as plt import numpy as np -from artlib.experimental.ConvexHullART import ConvexHullART, plot_convex_polygon -from scipy.spatial import ConvexHull +from artlib.experimental.HullART import HullART, plot_polygon +from alphashape import alphashape def cluster_blobs(): @@ -16,8 +16,8 @@ def cluster_blobs(): ) print("Data has shape:", data.shape) - params = {"rho": 0.6, "alpha": 1e-3} - cls = ConvexHullART(**params) + params = {"rho": 0.6, "alpha": 1e-3, "alpha_hat": 1.0} + cls = HullART(**params) X = cls.prepare_data(data) print("Prepared data has shape:", X.shape) @@ -29,6 +29,13 @@ def cluster_blobs(): cls.visualize(X, y) plt.show() +def test(): + points = np.array( + [(0.0, 0.0), (0.0, 1.0), (1.0,1.0), (1.0, 0.0)] + ) + x = alphashape(points, alpha=1.0) + print(x.length) if __name__ == "__main__": cluster_blobs() + # test() \ No newline at end of file diff --git a/templates/ART_template.py b/templates/ART_template.py index 9d353c7..7fcd79b 100644 --- a/templates/ART_template.py +++ b/templates/ART_template.py @@ -18,7 +18,7 @@ class MyART(BaseART): """Generic Template for custom ART modules.""" def __init__(self, rho: float): - """Initializes the ConvexHullART object. + """Initializes the ART object. Parameters ---------- From b73d69d8bf98e6161b402e9f803513c48391766e Mon Sep 17 00:00:00 2001 From: niklas melton Date: Tue, 29 Oct 2024 12:44:21 -0500 Subject: [PATCH 132/139] add fit_gif method --- artlib/common/BaseART.py | 92 +++++++++++++++++++++++++++++++++++++++ examples/demo_make_gif.py | 33 ++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 examples/demo_make_gif.py diff --git a/artlib/common/BaseART.py b/artlib/common/BaseART.py index 3af9e7f..82a2128 100644 --- a/artlib/common/BaseART.py +++ b/artlib/common/BaseART.py @@ -646,6 +646,98 @@ def partial_fit( self.labels_[i + j] = c return self + def fit_gif( + self, + X: np.ndarray, + y: Optional[np.ndarray] = None, + match_reset_func: Optional[Callable] = None, + max_iter=1, + match_tracking: Literal["MT+", "MT-", "MT0", "MT1", "MT~"] = "MT+", + epsilon: float = 0.0, + verbose: bool = False, + ax: Optional[Axes] = None, + filename: Optional[str] = None, + colors: Optional[IndexableOrKeyable] = None, + n_cluster_estimate: int = 20, + fps: int = 5, + **kwargs, + ): + """Fit the model to the data and make a gif of the process. + + 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_tracking : {"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. + ax : matplotlib.axes.Axes, optional + Figure axes. + colors : IndexableOrKeyable, optional + Colors to use for each cluster. + **kwargs : dict + see :func: `artlib.common.BaseART.visualize` + + """ + import matplotlib.pyplot as plt + from matplotlib.animation import PillowWriter + + if ax is None: + fig, ax = plt.subplots() + ax.set_xlim(-0.1, 1.1) + ax.set_ylim(-0.1, 1.1) + if filename is None: + filename = f"fit_gif_{self.__class__.__name__}.gif" + if colors is None: + from matplotlib.pyplot import cm + + colors = cm.rainbow(np.linspace(0, 1, n_cluster_estimate)) + black = np.array([[0, 0, 0, 1]]) # RGBA for black + colors = np.vstack((colors, black)) # Add black at the end + + self.validate_data(X) + self.check_dimensions(X) + self.is_fitted_ = True + + self.W = [] + self.labels_ = -np.ones((X.shape[0],), dtype=int) + + writer = PillowWriter(fps=fps) + with writer.saving(fig, filename, dpi=80): + for _ in range(max_iter): + if verbose: + from tqdm import tqdm + + x_iter = tqdm(enumerate(X), total=int(X.shape[0])) + else: + x_iter = enumerate(X) + for i, x in x_iter: + self.pre_step_fit(X) + c = self.step_fit( + x, + match_reset_func=match_reset_func, + match_tracking=match_tracking, + epsilon=epsilon, + ) + self.labels_[i] = c + self.post_step_fit(X) + ax.clear() + ax.set_xlim(-0.1, 1.1) + ax.set_ylim(-0.1, 1.1) + self.visualize(X, self.labels_, ax, colors=colors, **kwargs) + writer.grab_frame() + self.post_fit(X) + return self + def predict(self, X: np.ndarray) -> np.ndarray: """Predict labels for the data. diff --git a/examples/demo_make_gif.py b/examples/demo_make_gif.py new file mode 100644 index 0000000..687e4a0 --- /dev/null +++ b/examples/demo_make_gif.py @@ -0,0 +1,33 @@ +from sklearn.datasets import make_blobs +import matplotlib.pyplot as plt + +from artlib import FuzzyART + + +def cluster_blobs(): + data, target = make_blobs( + n_samples=150, + centers=3, + cluster_std=0.50, + random_state=0, + shuffle=False, + ) + print("Data has shape:", data.shape) + + params = {"rho": 0.5, "alpha": 0.0, "beta": 1.0} + cls = FuzzyART(**params) + + X = cls.prepare_data(data) + print("Prepared data has shape:", X.shape) + + cls = cls.fit_gif(X, filename="fit_gif_FuzzyART.gif", n_cluster_estimate=3) + y = cls.labels_ + + print(f"{cls.n_clusters} clusters found") + + cls.visualize(X, y) + plt.show() + + +if __name__ == "__main__": + cluster_blobs() From 08404f9f5c36855750a1be5dea8b94703b748803 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Tue, 29 Oct 2024 12:46:09 -0500 Subject: [PATCH 133/139] add fit_gif method --- artlib/common/BaseART.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/artlib/common/BaseART.py b/artlib/common/BaseART.py index 82a2128..6e6abce 100644 --- a/artlib/common/BaseART.py +++ b/artlib/common/BaseART.py @@ -684,6 +684,10 @@ def fit_gif( Figure axes. colors : IndexableOrKeyable, optional Colors to use for each cluster. + n_cluster_estimate : int, default=20 + estimate of number of clusters. Used for coloring plot. + fps : int, default=5 + gif frames per second **kwargs : dict see :func: `artlib.common.BaseART.visualize` From 97a7e95d4c896c95c3974fb99c36db95f9e6cecd Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Tue, 5 Nov 2024 14:12:04 -0600 Subject: [PATCH 134/139] update joss paper --- paper.md | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/paper.md b/paper.md index da37ae6..0c78635 100644 --- a/paper.md +++ b/paper.md @@ -34,7 +34,8 @@ elementary ART models and 11 compound ART models, including Fuzzy ART [@carpenter1991fuzzy], Hypersphere ART [@anagnostopoulos2000hypersphere], Ellipsoid ART [@anagnostopoulos2001a; @anagnostopoulos2001b], Gaussian ART [@williamson1996gaussian], Bayesian ART [@vigdor2007bayesian], Quadratic Neuron -ART [@su2001application; @su2005new], ARTMAP [@carpenter1991artmap], Simplified +ART [@su2001application; @su2005new], ART1 [@carpenter1987massively], ART2 +[@carpenter1987art; @carpenter1991art], ARTMAP [@carpenter1991artmap], Simplified ARTMAP [@gotarredona1998adaptive], SMART [@bartfai1994hierarchical], TopoART [@tscherepanow2010topoart], Dual Vigilance ART [@da2019dual], CVIART [@da2022icvi], BARTMAP [@xu2011bartmap; @xu2012biclustering], Fusion ART [@tan2007intelligence], @@ -95,7 +96,8 @@ Fuzzy ART, TopoART, ART1, and ARTMAP models, but they lack the flexibility and modularity required for broader experimentation. The most significant existing ART implementation exists in julia and provides just five models [@Petrenko_AdaptiveResonance_jl_A_Julia_2022] but, like the previously listed -MATLAB-based toolboxes, it is not easily accessible to Python-based work flows. +MATLAB-based toolboxes, it is not easily accessible to Python-based work flows and +lacks a modular design. These existing implementations of ART models may provide standalone versions of individual models, but they are often not designed to integrate seamlessly with modern @@ -103,13 +105,17 @@ Python libraries such as scikit-learn, NumPy, and SciPy. As a result, researcher developers working in Python-based environments face challenges when trying to incorporate ART models into their machine learning pipelines. -**artlib** addresses these challenges by offering a comprehensive and modular -collection of ART models, including both elementary and compound ART architectures. -It is designed for interoperability with popular Python tools, enabling users to easily -integrate ART models into machine learning workflows, optimize models using -scikit-learn's `GridSearchCV`, and preprocess data using standard libraries. This -flexibility and integration make **artlib** a powerful resource for both research and -practical applications. +In contrast, **artlib** offers a comprehensive and modular collection of ART models, +including both elementary and compound ART architectures. It is designed for +interoperability with popular Python tools, enabling users to easily integrate ART +models into machine learning workflows, optimize models using scikit-learn's +`GridSearchCV`, and preprocess data using standard libraries. Further, **artlib** +provides users the flexibility to construct their own compound ART modules (those +art modules deriving properties from other, elementary modules) which +may or may not exist in published literature. **artlib** also provides a template +in the source code to encourage users to develop and experiment with their own custom +ART algorithms. This flexibility and integration make **artlib** a powerful resource +for both research and practical applications. # Adaptive Resonance Theory (ART) @@ -122,13 +128,13 @@ real-time systems requiring continuous learning. Over the years, dozens of ART variations have been published [@da2019survey], extending the applicability of ART to nearly all learning regimes, including -reinforcement learning [@tan2004falcon; @tan2008integrating], hierarchical and -topological clustering [@tscherepanow2010topoart; @bartfai1994hierarchical], and -biclustering [@xu2011bartmap; @xu2012biclustering]. These numerous models provide an -ART-based solution for most machine learning use cases. However, the rapid development -of bespoke models and the difficulty in understanding the core principles of ART -have resulted in a lack of open-source and approachable implementations of most -ART variants. +reinforcement learning [@tan2004falcon; @tan2008integrating], hierarchical +clustering [@bartfai1994hierarchical], topological clustering +[@tscherepanow2010topoart], and biclustering [@xu2011bartmap; @xu2012biclustering]. +These numerous models provide an ART-based solution for most machine learning use cases. +However, the rapid pace of bespoke model development, coupled with the challenges +students face in learning ART's foundational principles, has contributed to a +scarcity of open-source, user-friendly implementations for most ART variants. The ability of ART to preserve previously learned patterns while learning new data in real-time has made it a powerful tool in domains such as robotics, medical diagnosis, From f35ed6c3e9937b3c819851a628b8b16bc232c0c0 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 14 Nov 2024 00:29:41 -0600 Subject: [PATCH 135/139] update joss paper --- artlib/elementary/ART1.py | 10 +-- examples/demo_art1.py | 2 +- paper.md | 162 +++++++++++++++++++++++++++++++++++--- 3 files changed, 155 insertions(+), 19 deletions(-) diff --git a/artlib/elementary/ART1.py b/artlib/elementary/ART1.py index ad4e10f..2c7e78e 100644 --- a/artlib/elementary/ART1.py +++ b/artlib/elementary/ART1.py @@ -28,21 +28,18 @@ class ART1(BaseART): """ - def __init__(self, rho: float, beta: float, L: float): + def __init__(self, rho: float, L: float): """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 = {"rho": rho, "beta": beta, "L": L} + params = {"rho": rho, "L": L} super().__init__(params) @staticmethod @@ -56,13 +53,10 @@ def validate_params(params: dict): """ assert "rho" in params - assert "beta" in params assert "L" in params assert 1.0 >= params["rho"] >= 0.0 - assert 1.0 >= params["beta"] >= 0.0 assert params["L"] >= 1.0 assert isinstance(params["rho"], float) - assert isinstance(params["beta"], float) assert isinstance(params["L"], float) def validate_data(self, X: np.ndarray): diff --git a/examples/demo_art1.py b/examples/demo_art1.py index fb04ea3..304db79 100644 --- a/examples/demo_art1.py +++ b/examples/demo_art1.py @@ -15,7 +15,7 @@ def cluster_blobs(): data = (data > 0.5).astype(int) print("Data has shape:", data.shape) - params = {"rho": 0.7, "beta": 1.0, "L": 1.0} + params = {"rho": 0.7, "L": 1.0} cls = ART1(**params) X = cls.prepare_data(data) diff --git a/paper.md b/paper.md index 0c78635..d9cc6b6 100644 --- a/paper.md +++ b/paper.md @@ -30,16 +30,28 @@ bibliography: references.bib The Adaptive Resonance Library (**artlib**) is a Python library that implements a wide range of Adaptive Resonance Theory (ART) algorithms. **artlib** currently supports eight -elementary ART models and 11 compound ART models, including Fuzzy ART -[@carpenter1991fuzzy], Hypersphere ART [@anagnostopoulos2000hypersphere], Ellipsoid ART -[@anagnostopoulos2001a; @anagnostopoulos2001b], Gaussian ART -[@williamson1996gaussian], Bayesian ART [@vigdor2007bayesian], Quadratic Neuron -ART [@su2001application; @su2005new], ART1 [@carpenter1987massively], ART2 -[@carpenter1987art; @carpenter1991art], ARTMAP [@carpenter1991artmap], Simplified -ARTMAP [@gotarredona1998adaptive], SMART [@bartfai1994hierarchical], TopoART -[@tscherepanow2010topoart], Dual Vigilance ART [@da2019dual], CVIART [@da2022icvi], -BARTMAP [@xu2011bartmap; @xu2012biclustering], Fusion ART [@tan2007intelligence], -FALCON [@tan2004falcon], and TD-FALCON [@tan2008integrating]. These models can be +elementary ART models and 11 compound ART models + +[comment]: <> (, including Fuzzy ART) + +[comment]: <> ([@carpenter1991fuzzy], Hypersphere ART [@anagnostopoulos2000hypersphere], Ellipsoid ART) + +[comment]: <> ([@anagnostopoulos2001a; @anagnostopoulos2001b], Gaussian ART) + +[comment]: <> ([@williamson1996gaussian], Bayesian ART [@vigdor2007bayesian], Quadratic Neuron) + +[comment]: <> (ART [@su2001application; @su2005new], ART1 [@carpenter1987massively], ART2) + +[comment]: <> ([@carpenter1987art; @carpenter1991art], ARTMAP [@carpenter1991artmap], Simplified) + +[comment]: <> (ARTMAP [@gotarredona1998adaptive], SMART [@bartfai1994hierarchical], TopoART) + +[comment]: <> ([@tscherepanow2010topoart], Dual Vigilance ART [@da2019dual], CVIART [@da2022icvi],) + +[comment]: <> (BARTMAP [@xu2011bartmap; @xu2012biclustering], Fusion ART [@tan2007intelligence],) + +[comment]: <> (FALCON [@tan2004falcon], and TD-FALCON [@tan2008integrating]. ) +These models can be applied to tasks such as unsupervised clustering, supervised classification, regression, and reinforcement learning [@da2019survey]. This library provides an extensible and modular framework where users can integrate custom models or extend current @@ -51,6 +63,136 @@ visualization methods for various cluster geometries, along with pre-processing techniques such as Visual Assessment of Tendency (VAT) [@bezdek2002vat], data normalization, and complement coding. +## Elementary Models Provided + +1. ART1 [@carpenter1987massively]: ART1 was the first ART clustering algorithm to be + developed. It clusters binary vectors using a similarity metric based on the Hamming + distance. + +2. ART2 [@carpenter1987art; @carpenter1991art]: ART2 was the first attempt to extend + ART1 to the domain of continuous-valued data. ART2-A was developed shortly after and + improved the algorithmic complexity while retaining the properties of the original + ART2 implementation. ART2 more closely resembles a multi-layer perceptron network as + it uses an adaptive weight vector in the activation layer and a Heaviside function + for the resonance layer. ART2 is widely considered to not work and is not + recommended for use. It is included here for historical purposes. + +3. Fuzzy ART [@carpenter1991fuzzy]: is the most cited and arguably most widely used + ART variant at this time. Fuzzy ART is a hyper-box based clustering method, + capable of clustering continuous-valued data. Data is pre-processed into zero-volume + hyper-boxes through the process of complement coding before being used to + initialize or expand the volume of a cluster hyper-box. In the fast-learning regime, + Fuzzy ART suffers no catastrophic forgetting. It is exceptionally fast and + explainable. + +4. Hyperpshere ART [@anagnostopoulos2000hypersphere]: Hypersphere ART was designed + to succeed Fuzzy ART with a more efficient internal knowledge representation. + Categories are hyperpspheres and require less internal memory however computational + complexity is increased relative to Fuzzy ART. + +5. Ellipsoid ART[@anagnostopoulos2001a; @anagnostopoulos2001b]: Ellipsoid ART is a + generalization of Hyperpshere ART which permits ellipsoids with arbitrary + rotation. Ellipsoid ART is highly order dependent as the second sample added + to any cluster sets the axes orientations. + +6. Guassian ART [@williamson1996gaussian]: 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. + +7. Bayesian ART [@vigdor2007bayesian]: 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. + +8. Quadratic Neuron ART [@su2001application; @su2005new]: QN-ART utilizes a weight + vector and a quadratic term to create clusters in a hyper-ellipsoid structure. It + is superficially similar to ART2-A but is more sophisticated in that neurons also + learn a bias and quadratic activation term. + +## Compound Models Provided + +1. ARTMAP [@carpenter1991artmap]: ARTMAP uses two ART modules to separately cluster + two parallel data streams (A and B). An inter-ART module regulates the clustering + such that clusters in the `module_A` maintain a many-to-one mapping with the + clusters in the `module_B` by using a match-tracking function. When the data stream + are independent and dependent variable for the A and B side respectively, ARTMAP + learns a functional mapping the describes their relationship. ARTMAP can + therefore be used for both classification and regression tasks. However, ARTMAP + does not perform as well as Fusion ART for regression tasks where data is not + monotonic. + +2. Simple ARTMAP [@gotarredona1998adaptive]: Simple ARTMAP (or Simplified ARTMAP) + was developed to streamline the ARTMAP algorith for classification task. As most + classification problems provide discrete labels, it is possible to replace the + B-side of the ARTMAP algorithm with the class labels directly. Simple ARTMAP does + this and creates a mapping from B-side class labels to A-side cluster labels. The + many-to-one mapping property is preserved, but the learning process is + significantly faster and less computationally intensive. Simple ARTMAP is the + recommended model for classification tasks. + +3. Fusion ART [@tan2007intelligence]: Fusion ART allows for the fusion of + multi-channel data by leveraging a discrete ART module for each channel. + Activation for each category is calculated as a weighted sum of all channel + activations and resonance only occurs if all channels are simultaneously resonant. + This allows Fusion ART to learn mappings across all channels. Fusion ART works + exceptionally well for regression problems and is the recommended model for this + task. + +4. FALCON [@tan2004falcon]: The Reactive FALCON model is a special case of Fusion + ART designed for reinforcement learning. A Fusion ART model is used for learning + the mapping between state, action, and reward channels. Special functions are + implemented for selecting the best action and for predicting reward values. + +5. TD-FALCON [@tan2008integrating]: Temporal Difference FALCON is an extension of + Reactive FALCON which utilizes the SARSA method for temporal difference + reinforcement learning. TD-FALCON is the recommended model for reinforcement + learning tasks. + +6. Dual Vigilance ART [@da2019dual]: Dual Vigilance ART utilizes an elementary ART + module with a second, less restrictive vigilance parameter. Clusters are formed + using the typical process for the underlying art module unless no resonant + category is found. When this occurs, the less-restrictive vigilance parameter is + used to determine if a near-resonant category can be found. If one can be found, + a new cluster is formed, and the near-resonant category label is copied to the new + cluster. If neither resonant nor near resonant categories can be found, a new + cluster and new category label are both created. In this way, Dual Vigilance ART + is capable of finding arbitrarily shaped structures as composites of the + underlying ART geometry (i.e Hyper-ellipsoids or Hyper-boxes). + +7. SMART [@bartfai1994hierarchical]: Self-consistent Modular ART is a special case + of Deep ARTMAP and an extension of ARTMAP. SMART permits n-many modules (in + contrast to ARTMAPS 2-modules) which passes the same sample vector to each + module. Each module has a vigilance parameter monotonically increasing with depth. + This permits SMART to create self-consistent hierarchies of clusters through a + divisive clustering approach. The number of modules and granularity at each module + are both parameterizable. + +8. Deep ARTMAP: Deep ARTMAP is a novel contribution of this library. It generalizes + SMART by permitting each module to accept some function $f^i(x)$. This + generalization allows the user to find hierarchical relationships between an + abritrary number of functional transformations of some input data. When only two + modules are used and $f^1(x) = target$ and $f^2(x) = x$ Deep ARTMAP reduces to + standard ARTMAP. + +9. Topo ART [@tscherepanow2010topoart]: Topo ART is a topological clustering + approach which uses an elementary ART module to learn a distributed cluster graph + where samples can belong to multiple distinct clusters. The co-resonant clusters are + tracked using an adjacency matrix which describes the cluster relationships of + the entire model. + +10. CVI ART [@da2022icvi]: CVI ART maps the clusters of an elementary ART module to + category label identified by the optimal Cluster Validity Index (CV). This + mapping occurs similarly to simplified ARTMAP. An iterative implementation (iCVI + ART) is also provided, however it is currently only compatible with Fuzzy ART. + +11. BARTMAP [@xu2011bartmap; @xu2012biclustering]: BARTMAP is a Biclustering + algorithm based loosely on ARTMAP. The algorithm accepts two instantiated + elementary 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 Pearson correlation + within the subset of features belonging to at least one of the feature clusters. + # Statement of Need The Adaptive Resonance Library (**artlib**) is essential for researchers, developers, From 4bf2f8bd7cc29c65776e7756a70d11733b2f7746 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 14 Nov 2024 17:05:55 -0600 Subject: [PATCH 136/139] updated joss paper --- paper.md | 224 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 167 insertions(+), 57 deletions(-) diff --git a/paper.md b/paper.md index d9cc6b6..cb7c187 100644 --- a/paper.md +++ b/paper.md @@ -1,7 +1,7 @@ --- title: 'Adaptive Resonance Lib: A Python package for Adaptive Resonance Theory (ART) models' tags: - - Python + - python - clustering - classification - regression @@ -30,7 +30,17 @@ bibliography: references.bib The Adaptive Resonance Library (**artlib**) is a Python library that implements a wide range of Adaptive Resonance Theory (ART) algorithms. **artlib** currently supports eight -elementary ART models and 11 compound ART models +elementary ART models and 11 compound ART models. These models can be applied to +tasks such as unsupervised clustering, supervised classification, regression, and +reinforcement learning [@da2019survey]. This library provides an extensible and +modular framework where users can integrate custom models or extend current +implementations, allowing for experimentation with existing and novel machine +learning techniques. + +In addition to the diverse set of ART models, **artlib** offers implementations of +visualization methods for various cluster geometries, along with pre-processing +techniques such as Visual Assessment of Tendency (VAT) [@bezdek2002vat], data +normalization, and complement coding. [comment]: <> (, including Fuzzy ART) @@ -51,19 +61,37 @@ elementary ART models and 11 compound ART models [comment]: <> (BARTMAP [@xu2011bartmap; @xu2012biclustering], Fusion ART [@tan2007intelligence],) [comment]: <> (FALCON [@tan2004falcon], and TD-FALCON [@tan2008integrating]. ) -These models can be -applied to tasks such as unsupervised clustering, supervised classification, regression, -and reinforcement learning [@da2019survey]. This library provides an extensible and -modular framework where users can integrate custom models or extend current -implementations, allowing for experimentation with existing and novel machine learning -techniques. -In addition to the diverse set of ART models, **artlib** offers implementations of -visualization methods for various cluster geometries, along with pre-processing -techniques such as Visual Assessment of Tendency (VAT) [@bezdek2002vat], data -normalization, and complement coding. +# Adaptive Resonance Theory (ART) + +ART is a class of neural networks known for solving the stability-plasticity dilemma, +making it particularly effective for classification, clustering, and incremental +learning tasks [@grossberg1976a; @grossberg1976a; @Grossberg1980HowDA; +@grossberg2013adaptive; @da2019survey]. ART models are designed to dynamically learn +and adapt to new patterns without catastrophic forgetting, making them ideal for +real-time systems requiring continuous learning. + +Over the years, dozens of ART variations have been published [@da2019survey], +extending the applicability of ART to nearly all learning regimes, including +reinforcement learning [@tan2004falcon; @tan2008integrating], hierarchical +clustering [@bartfai1994hierarchical], topological clustering +[@tscherepanow2010topoart], and biclustering [@xu2011bartmap; @xu2012biclustering]. +These numerous models provide an ART-based solution for most machine learning use cases. +However, the rapid pace of bespoke model development, coupled with the challenges +students face in learning ART's foundational principles, has contributed to a +scarcity of open-source, user-friendly implementations for most ART variants. + +The ability of ART to preserve previously learned patterns while learning new data in +real-time has made it a powerful tool in domains such as robotics, medical diagnosis, +and adaptive control systems. **artlib** aims to extend the application of these models +in modern machine learning pipelines, offering a unique and approachable toolkit for +leveraging ART's strengths. -## Elementary Models Provided + +## Elementary Models +An elementary ART model is a model capable of unsupervised learning and which relies +on no subordinate ART model for its behavior. The following elementary ART +models are currently implemented: 1. ART1 [@carpenter1987massively]: ART1 was the first ART clustering algorithm to be developed. It clusters binary vectors using a similarity metric based on the Hamming @@ -109,20 +137,22 @@ normalization, and complement coding. is superficially similar to ART2-A but is more sophisticated in that neurons also learn a bias and quadratic activation term. -## Compound Models Provided +## Compound Models +A compound ART model is one which extends the functionality of one or more +underlying ART models. Functional extension may include topology learning, +supervised learning, reinforcement learning, and more. The following compound ART +models are currently implemented: 1. ARTMAP [@carpenter1991artmap]: ARTMAP uses two ART modules to separately cluster two parallel data streams (A and B). An inter-ART module regulates the clustering such that clusters in the `module_A` maintain a many-to-one mapping with the clusters in the `module_B` by using a match-tracking function. When the data stream - are independent and dependent variable for the A and B side respectively, ARTMAP - learns a functional mapping the describes their relationship. ARTMAP can - therefore be used for both classification and regression tasks. However, ARTMAP - does not perform as well as Fusion ART for regression tasks where data is not - monotonic. + are independent and dependent variables for the A and B side respectively, ARTMAP + learns a functional mapping that describes their relationship. ARTMAP can + therefore be used for both classification and regression tasks. 2. Simple ARTMAP [@gotarredona1998adaptive]: Simple ARTMAP (or Simplified ARTMAP) - was developed to streamline the ARTMAP algorith for classification task. As most + was developed to streamline the ARTMAP algorithm for classification task. As most classification problems provide discrete labels, it is possible to replace the B-side of the ARTMAP algorithm with the class labels directly. Simple ARTMAP does this and creates a mapping from B-side class labels to A-side cluster labels. The @@ -131,47 +161,48 @@ normalization, and complement coding. recommended model for classification tasks. 3. Fusion ART [@tan2007intelligence]: Fusion ART allows for the fusion of - multi-channel data by leveraging a discrete ART module for each channel. + multi-channel data by leveraging a discrete elementary ART module for each channel. Activation for each category is calculated as a weighted sum of all channel activations and resonance only occurs if all channels are simultaneously resonant. This allows Fusion ART to learn mappings across all channels. Fusion ART works exceptionally well for regression problems and is the recommended model for this task. -4. FALCON [@tan2004falcon]: The Reactive FALCON model is a special case of Fusion - ART designed for reinforcement learning. A Fusion ART model is used for learning - the mapping between state, action, and reward channels. Special functions are - implemented for selecting the best action and for predicting reward values. +4. FALCON [@tan2004falcon]: The Reactive Fusion Architecture for Learning, Cognition, + and Navigation (FALCON) model is a special case of Fusion ART designed for + reinforcement learning. A Fusion ART model is used for learning the mapping + between state, action, and reward channels. Special functions are implemented for + selecting the optimal action and for predicting reward values. -5. TD-FALCON [@tan2008integrating]: Temporal Difference FALCON is an extension of +5. TD-FALCON [@tan2008integrating]: Temporal Difference (TD) FALCON is an extension of Reactive FALCON which utilizes the SARSA method for temporal difference reinforcement learning. TD-FALCON is the recommended model for reinforcement learning tasks. -6. Dual Vigilance ART [@da2019dual]: Dual Vigilance ART utilizes an elementary ART +6. Dual Vigilance ART [@da2019dual]: Dual Vigilance ART extends an elementary ART module with a second, less restrictive vigilance parameter. Clusters are formed using the typical process for the underlying art module unless no resonant category is found. When this occurs, the less-restrictive vigilance parameter is used to determine if a near-resonant category can be found. If one can be found, a new cluster is formed, and the near-resonant category label is copied to the new - cluster. If neither resonant nor near resonant categories can be found, a new + cluster. If neither a resonant nor near-resonant category can be found, a new cluster and new category label are both created. In this way, Dual Vigilance ART - is capable of finding arbitrarily shaped structures as composites of the + is capable of finding arbitrarily shaped structures as topological composites of the underlying ART geometry (i.e Hyper-ellipsoids or Hyper-boxes). 7. SMART [@bartfai1994hierarchical]: Self-consistent Modular ART is a special case of Deep ARTMAP and an extension of ARTMAP. SMART permits n-many modules (in - contrast to ARTMAPS 2-modules) which passes the same sample vector to each - module. Each module has a vigilance parameter monotonically increasing with depth. - This permits SMART to create self-consistent hierarchies of clusters through a - divisive clustering approach. The number of modules and granularity at each module - are both parameterizable. + contrast to ARTMAP's 2-modules) which have vigilance parameters monotonically + increasing with depth. + By passing the same sample vector to all modules, SMART creates a self-consistent + hierarchy of clusters through a divisive clustering approach. The number of + modules and granularity at each module are both parameterizable. 8. Deep ARTMAP: Deep ARTMAP is a novel contribution of this library. It generalizes - SMART by permitting each module to accept some function $f^i(x)$. This + SMART by permitting each module `module_i` to accept some function $f^i(x)$. This generalization allows the user to find hierarchical relationships between an abritrary number of functional transformations of some input data. When only two - modules are used and $f^1(x) = target$ and $f^2(x) = x$ Deep ARTMAP reduces to + modules are used and $f^1(x) = target(x)$ and $f^2(x) = x$ Deep ARTMAP reduces to standard ARTMAP. 9. Topo ART [@tscherepanow2010topoart]: Topo ART is a topological clustering @@ -259,30 +290,109 @@ in the source code to encourage users to develop and experiment with their own c ART algorithms. This flexibility and integration make **artlib** a powerful resource for both research and practical applications. -# Adaptive Resonance Theory (ART) +## General Usage +To install AdaptiveResonanceLib, simply use pip: -ART is a class of neural networks known for solving the stability-plasticity dilemma, -making it particularly effective for classification, clustering, and incremental -learning tasks [@grossberg1976a; @grossberg1976a; @Grossberg1980HowDA; -@grossberg2013adaptive; @da2019survey]. ART models are designed to dynamically learn -and adapt to new patterns without catastrophic forgetting, making them ideal for -real-time systems requiring continuous learning. +```bash +pip install artlib +``` -Over the years, dozens of ART variations have been published [@da2019survey], -extending the applicability of ART to nearly all learning regimes, including -reinforcement learning [@tan2004falcon; @tan2008integrating], hierarchical -clustering [@bartfai1994hierarchical], topological clustering -[@tscherepanow2010topoart], and biclustering [@xu2011bartmap; @xu2012biclustering]. -These numerous models provide an ART-based solution for most machine learning use cases. -However, the rapid pace of bespoke model development, coupled with the challenges -students face in learning ART's foundational principles, has contributed to a -scarcity of open-source, user-friendly implementations for most ART variants. +### Clustering Data with the Fuzzy ART model + +```python +from artlib import FuzzyART +import numpy as np + +# Your dataset +train_X = np.array([...]) # shape (n_samples, n_features) +test_X = np.array([...]) + +# Initialize the Fuzzy ART model +model = FuzzyART(rho=0.7, alpha = 0.0, beta=1.0) + +# Prepare Data +train_X_prep = model.prepare_data(train_X) +test_X_prep = model.prepare_data(test_X) + +# Fit the model +model.fit(train_X_prep) + +# Predict data labels +predictions = model.predict(test_X_prep) +``` + +### Fitting a Classification Model with SimpleARTMAP + +```python +from artlib import GaussianART, SimpleARTMAP +import numpy as np + +# Your dataset +train_X = np.array([...]) # shape (n_samples, n_features) +train_y = np.array([...]) # shape (n_samples, ), must be integers +test_X = np.array([...]) + +# Initialize the Gaussian ART model +sigma_init = np.array([0.5]*train_X.shape[1]) # variance estimate for each feature +module_a = GaussianART(rho=0.0, sigma_init=sigma_init) + +# Initialize the SimpleARTMAP model +model = SimpleARTMAP(module_a=module_a) + +# Prepare Data +train_X_prep = model.prepare_data(train_X) +test_X_prep = model.prepare_data(test_X) + +# Fit the model +model.fit(train_X_prep, train_y) + +# Predict data labels +predictions = model.predict(test_X_prep) +``` + +### Fitting a Regression Model with FusionART + +```python +from artlib import FuzzyART, HypersphereART, FusionART +import numpy as np + +# Your dataset +train_X = np.array([...]) # shape (n_samples, n_features_X) +train_y = np.array([...]) # shape (n_samples, n_features_y) +test_X = np.array([...]) + +# Initialize the Fuzzy ART model +module_x = FuzzyART(rho=0.0, alpha = 0.0, beta=1.0) + +# Initialize the Hypersphere ART model +r_hat = 0.5*np.sqrt(train_X.shape[1]) # no restriction on hyperpshere size +module_y = HypersphereART(rho=0.0, alpha = 0.0, beta=1.0, r_hat=r_hat) + +# Initialize the FusionARTMAP model +gamma_values = [0.5, 0.5] # eqaul weight to both channels +channel_dims = [ + 2*train_X.shape[1], # fuzzy ART complement codes data so channel dim is 2*n_features + train_y.shape[1] +] +model = FusionART( + modules=[module_x, module_y], + gamma_values=gamma_values, + channel_dims=channel_dims +) + +# Prepare Data +train_Xy = model.join_channel_data(channel_data=[train_X, train_y]) +train_Xy_prep = model.prepare_data(train_Xy) +test_Xy = model.join_channel_data(channel_data=[train_X], skip_channels=[1]) +test_Xy_prep = model.prepare_data(test_Xy) + +# Fit the model +model.fit(train_X_prep, train_y) + +# Predict y-channel values +pred_y = model.predict_regression(test_Xy_prep, target_channels=[1]) +``` -The ability of ART to preserve previously learned patterns while learning new data in -real-time has made it a powerful tool in domains such as robotics, medical diagnosis, -and adaptive control systems. **artlib** aims to extend the application of these models -in modern machine learning pipelines, offering a unique and approachable toolkit for -leveraging ART's strengths. # Acknowledgements From c3add464a4afcd292711fe97b291ee4a1768fe12 Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 14 Nov 2024 17:14:47 -0600 Subject: [PATCH 137/139] updated joss paper --- paper.md | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/paper.md b/paper.md index cb7c187..abe28ae 100644 --- a/paper.md +++ b/paper.md @@ -175,38 +175,42 @@ models are currently implemented: selecting the optimal action and for predicting reward values. 5. TD-FALCON [@tan2008integrating]: Temporal Difference (TD) FALCON is an extension of - Reactive FALCON which utilizes the SARSA method for temporal difference - reinforcement learning. TD-FALCON is the recommended model for reinforcement - learning tasks. + Reactive FALCON which utilizes the SARSA (State-Action-Reward-State-Action) method + for temporal difference reinforcement learning. TD-FALCON is the recommended + model for reinforcement learning tasks. 6. Dual Vigilance ART [@da2019dual]: Dual Vigilance ART extends an elementary ART - module with a second, less restrictive vigilance parameter. Clusters are formed + module with a second, less restrictive vigilance parameter. Further, Dual + Vigilance ART distinguishes between clusters and category lables by mapping + clusters to category labels in a many-to-one fashion. Clusters are formed using the typical process for the underlying art module unless no resonant - category is found. When this occurs, the less-restrictive vigilance parameter is - used to determine if a near-resonant category can be found. If one can be found, - a new cluster is formed, and the near-resonant category label is copied to the new - cluster. If neither a resonant nor near-resonant category can be found, a new - cluster and new category label are both created. In this way, Dual Vigilance ART - is capable of finding arbitrarily shaped structures as topological composites of the - underlying ART geometry (i.e Hyper-ellipsoids or Hyper-boxes). - -7. SMART [@bartfai1994hierarchical]: Self-consistent Modular ART is a special case - of Deep ARTMAP and an extension of ARTMAP. SMART permits n-many modules (in + cluster is found. When this occurs, the less-restrictive vigilance parameter is + used to determine if a near-resonant cluster can be found. If one can be found, + a new cluster is formed, and the near-resonant cluster's category label is mapped to + the new cluster. If neither a resonant nor near-resonant category can be found, a new + cluster and new category label are both created and mapped to eachother. In this + way, Dual Vigilance ART is capable of finding arbitrarily shaped structures as + topological composites of the underlying ART geometry (i.e Hyper-ellipsoids or + Hyper-boxes). + +7. SMART [@bartfai1994hierarchical]: Self-consistent Modular ART (SMART) is a special + case of Deep ARTMAP and an extension of ARTMAP. SMART permits n-many modules (in contrast to ARTMAP's 2-modules) which have vigilance parameters monotonically - increasing with depth. - By passing the same sample vector to all modules, SMART creates a self-consistent - hierarchy of clusters through a divisive clustering approach. The number of - modules and granularity at each module are both parameterizable. + increasing with depth. By passing the same sample vector to all modules, SMART + creates a self-consistent hierarchy of clusters through a divisive clustering + approach. The number of modules and granularity at each module are both + parameterizable. 8. Deep ARTMAP: Deep ARTMAP is a novel contribution of this library. It generalizes SMART by permitting each module `module_i` to accept some function $f^i(x)$. This generalization allows the user to find hierarchical relationships between an abritrary number of functional transformations of some input data. When only two modules are used and $f^1(x) = target(x)$ and $f^2(x) = x$ Deep ARTMAP reduces to - standard ARTMAP. + standard ARTMAP. Similarly, when the functional transformation at each layer is + the identity function, Deep ARTMAP reduces to SMART. 9. Topo ART [@tscherepanow2010topoart]: Topo ART is a topological clustering - approach which uses an elementary ART module to learn a distributed cluster graph + approach which uses an elementary ART module to learn a distributed clustering graph where samples can belong to multiple distinct clusters. The co-resonant clusters are tracked using an adjacency matrix which describes the cluster relationships of the entire model. From b733372a0e86378ced83b95d82df10a70b3f601f Mon Sep 17 00:00:00 2001 From: niklas melton Date: Thu, 14 Nov 2024 17:17:04 -0600 Subject: [PATCH 138/139] updated ART1 tests --- unit_tests/test_ART1.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/unit_tests/test_ART1.py b/unit_tests/test_ART1.py index 27088e8..bd317bc 100644 --- a/unit_tests/test_ART1.py +++ b/unit_tests/test_ART1.py @@ -7,26 +7,23 @@ @pytest.fixture def art_model(): rho = 0.7 - beta = 0.5 L = 2.0 - return ART1(rho=rho, beta=beta, L=L) + return ART1(rho=rho, L=L) def test_initialization(art_model): # Test that the model initializes correctly assert art_model.params["rho"] == 0.7 - assert art_model.params["beta"] == 0.5 assert art_model.params["L"] == 2.0 def test_validate_params(): # Test the validate_params method - valid_params = {"rho": 0.7, "beta": 0.5, "L": 2.0} + valid_params = {"rho": 0.7, "L": 2.0} ART1.validate_params(valid_params) invalid_params = { "rho": -0.7, # Invalid vigilance parameter - "beta": -0.5, # Invalid beta "L": 0.5, # Invalid L (must be >= 1) } with pytest.raises(AssertionError): @@ -81,7 +78,7 @@ def test_update(art_model): w = np.array( [0.5, 0.5, 1, 1] ) # Mock weight with both bottom-up and top-down weights - params = {"L": 2.0, "beta": 0.5} + params = {"L": 2.0} updated_weight = art_model.update(i, w, params) assert len(updated_weight) == 4 # Bottom-up and top-down weights From 01215acc5f63691cf0591c785dca3eb336c56f5f Mon Sep 17 00:00:00 2001 From: Niklas Melton Date: Fri, 15 Nov 2024 03:23:28 -0600 Subject: [PATCH 139/139] update joss paper --- paper.md | 182 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 92 insertions(+), 90 deletions(-) diff --git a/paper.md b/paper.md index abe28ae..ffbae9f 100644 --- a/paper.md +++ b/paper.md @@ -28,14 +28,14 @@ bibliography: references.bib # Summary -The Adaptive Resonance Library (**artlib**) is a Python library that implements a wide -range of Adaptive Resonance Theory (ART) algorithms. **artlib** currently supports eight -elementary ART models and 11 compound ART models. These models can be applied to -tasks such as unsupervised clustering, supervised classification, regression, and -reinforcement learning [@da2019survey]. This library provides an extensible and -modular framework where users can integrate custom models or extend current -implementations, allowing for experimentation with existing and novel machine -learning techniques. +The Adaptive Resonance Theory Library (**artlib**) is a Python library that +implements a wide range of Adaptive Resonance Theory (ART) algorithms. **artlib** +currently supports eight elementary ART models and 11 compound ART models. These +models can be applied to tasks such as unsupervised clustering, supervised +classification, regression, and reinforcement learning [@da2019survey]. This library +provides an extensible and modular framework where users can integrate custom models +or extend current implementations, allowing for experimentation with existing and +novel machine learning techniques. In addition to the diverse set of ART models, **artlib** offers implementations of visualization methods for various cluster geometries, along with pre-processing @@ -105,7 +105,7 @@ models are currently implemented: for the resonance layer. ART2 is widely considered to not work and is not recommended for use. It is included here for historical purposes. -3. Fuzzy ART [@carpenter1991fuzzy]: is the most cited and arguably most widely used +3. Fuzzy ART [@carpenter1991fuzzy]: is arguably the most widely used ART variant at this time. Fuzzy ART is a hyper-box based clustering method, capable of clustering continuous-valued data. Data is pre-processed into zero-volume hyper-boxes through the process of complement coding before being used to @@ -115,27 +115,26 @@ models are currently implemented: 4. Hyperpshere ART [@anagnostopoulos2000hypersphere]: Hypersphere ART was designed to succeed Fuzzy ART with a more efficient internal knowledge representation. - Categories are hyperpspheres and require less internal memory however computational - complexity is increased relative to Fuzzy ART. + Categories are hyper-pspheres and require less internal memory. However, + computational complexity is increased relative to Fuzzy ART. 5. Ellipsoid ART[@anagnostopoulos2001a; @anagnostopoulos2001b]: Ellipsoid ART is a - generalization of Hyperpshere ART which permits ellipsoids with arbitrary - rotation. Ellipsoid ART is highly order dependent as the second sample added - to any cluster sets the axes orientations. + generalization of Hyperpshere ART which finds ellipsoidal clusters with arbitrary + rotation. Ellipsoid ART is highly order-dependent. -6. Guassian ART [@williamson1996gaussian]: 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. +6. Guassian ART [@williamson1996gaussian]: Gaussian 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. -7. Bayesian ART [@vigdor2007bayesian]: 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. +7. Bayesian ART [@vigdor2007bayesian]: 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. -8. Quadratic Neuron ART [@su2001application; @su2005new]: QN-ART utilizes a weight - vector and a quadratic term to create clusters in a hyper-ellipsoid structure. It - is superficially similar to ART2-A but is more sophisticated in that neurons also - learn a bias and quadratic activation term. +8. Quadratic Neuron ART [@su2001application; @su2005new]: QN-ART uses a neural + activation and resonance method to create clusters in a hyper-ellipsoid structure. It + is superficially similar to ART2-A in that it uses a weight vector but it is more + sophisticated in that neurons also learn a bias and quadratic activation term. ## Compound Models A compound ART model is one which extends the functionality of one or more @@ -152,7 +151,7 @@ models are currently implemented: therefore be used for both classification and regression tasks. 2. Simple ARTMAP [@gotarredona1998adaptive]: Simple ARTMAP (or Simplified ARTMAP) - was developed to streamline the ARTMAP algorithm for classification task. As most + was developed to streamline the ARTMAP algorithm for classification tasks. As most classification problems provide discrete labels, it is possible to replace the B-side of the ARTMAP algorithm with the class labels directly. Simple ARTMAP does this and creates a mapping from B-side class labels to A-side cluster labels. The @@ -228,71 +227,7 @@ models are currently implemented: cluster as well as the candidate sample and enforcing a minimum Pearson correlation within the subset of features belonging to at least one of the feature clusters. -# Statement of Need - -The Adaptive Resonance Library (**artlib**) is essential for researchers, developers, -and educators interested in adaptive neural networks, specifically ART algorithms. -While deep learning dominates machine learning, ART models offer unique advantages -in incremental and real-time learning environments due to their ability to learn new -data without forgetting previously learned information. - -Currently, no comprehensive Python library implements a variety of ART models in an -open-source, modular, and extensible manner. **artlib** fills this gap by offering a -range of ART implementations that integrate seamlessly with machine learning workflows, -including scikit-learn's `Pipeline` and `GridSearchCV` [@scikit-learn]. The library is -designed for ease of use and high performance, leveraging Python's scientific stack -(NumPy [@harris2020array], SciPy [@2020SciPy-NMeth], and scikit-learn [@scikit-learn]) -for fast numerical computation. - -The modular design of **artlib** enables users to create novel compound ART models, -such as Dual Vigilance Fusion ART [@da2019dual; @tan2007intelligence] or -Quadratic Neuron SMART [@su2001application; @su2005new; @bartfai1994hierarchical]. -This flexibility offers powerful experimental and time-saving benefits, allowing -researchers and practitioners to evaluate models on diverse datasets efficiently. - -Additionally, the library serves as a valuable educational tool, providing -well-documented code and familiar APIs to support hands-on experimentation with ART -models. It is ideal for academic courses or personal projects in artificial -intelligence and machine learning, making **artlib** a versatile resource. - -**artlib** is actively maintained and designed for future extension, allowing users -to create new ART models, adjust parameters for specific applications, and explore ART's -potential for novel research problems. Its integration with popular Python libraries -ensures its adaptability to current machine learning challenges. - -# Comparison to Existing Implementations -While there are several open-source repositories that provide -python implementations of specific ART models [@birkjohann2023artpython; -@aiopenlab2023art; @dilekman2022artificial; @artpy2022; @dixit2020adaptive; -@inyoot2021art; @valixandra2021adaptive; @wan2022art2; @ray2023artpy], they lack -modularity and are limited in scope, often implementing just one or two models. For -instance, MATLAB-based ART toolboxes [@mathworks_art1s; @mathworks_fuzzyart_fuzzyartmap; -@mathworks_topoart; @mathworks_art_fuzzyart_artmap] provide implementations of -Fuzzy ART, TopoART, ART1, and ARTMAP models, but they lack the flexibility and -modularity required for broader experimentation. The most significant existing ART -implementation exists in julia and provides just five models -[@Petrenko_AdaptiveResonance_jl_A_Julia_2022] but, like the previously listed -MATLAB-based toolboxes, it is not easily accessible to Python-based work flows and -lacks a modular design. - -These existing implementations of ART models may provide standalone versions of -individual models, but they are often not designed to integrate seamlessly with modern -Python libraries such as scikit-learn, NumPy, and SciPy. As a result, researchers and -developers working in Python-based environments face challenges when trying to -incorporate ART models into their machine learning pipelines. - -In contrast, **artlib** offers a comprehensive and modular collection of ART models, -including both elementary and compound ART architectures. It is designed for -interoperability with popular Python tools, enabling users to easily integrate ART -models into machine learning workflows, optimize models using scikit-learn's -`GridSearchCV`, and preprocess data using standard libraries. Further, **artlib** -provides users the flexibility to construct their own compound ART modules (those -art modules deriving properties from other, elementary modules) which -may or may not exist in published literature. **artlib** also provides a template -in the source code to encourage users to develop and experiment with their own custom -ART algorithms. This flexibility and integration make **artlib** a powerful resource -for both research and practical applications. ## General Usage To install AdaptiveResonanceLib, simply use pip: @@ -398,6 +333,73 @@ pred_y = model.predict_regression(test_Xy_prep, target_channels=[1]) ``` +# Statement of Need + +The Adaptive Resonance Library (**artlib**) is essential for researchers, developers, +and educators interested in adaptive neural networks, specifically ART algorithms. +While deep learning dominates machine learning, ART models offer unique advantages +in incremental and real-time learning environments due to their ability to learn new +data without forgetting previously learned information. + +Currently, no comprehensive Python library implements a variety of ART models in an +open-source, modular, and extensible manner. **artlib** fills this gap by offering a +range of ART implementations that integrate seamlessly with machine learning workflows, +including scikit-learn's `Pipeline` and `GridSearchCV` [@scikit-learn]. The library is +designed for ease of use and high performance, leveraging Python's scientific stack +(NumPy [@harris2020array], SciPy [@2020SciPy-NMeth], and scikit-learn [@scikit-learn]) +for fast numerical computation. + +The modular design of **artlib** enables users to create novel compound ART models, +such as Dual Vigilance Fusion ART [@da2019dual; @tan2007intelligence] or +Quadratic Neuron SMART [@su2001application; @su2005new; @bartfai1994hierarchical]. +This flexibility offers powerful experimental and time-saving benefits, allowing +researchers and practitioners to evaluate models on diverse datasets efficiently. + +Additionally, the library serves as a valuable educational tool, providing +well-documented code and familiar APIs to support hands-on experimentation with ART +models. It is ideal for academic courses or personal projects in artificial +intelligence and machine learning, making **artlib** a versatile resource. + +**artlib** is actively maintained and designed for future extension, allowing users +to create new ART models, adjust parameters for specific applications, and explore ART's +potential for novel research problems. Its integration with popular Python libraries +ensures its adaptability to current machine learning challenges. + +# Comparison to Existing Implementations + +While there are several open-source repositories that provide +python implementations of specific ART models [@birkjohann2023artpython; +@aiopenlab2023art; @dilekman2022artificial; @artpy2022; @dixit2020adaptive; +@inyoot2021art; @valixandra2021adaptive; @wan2022art2; @ray2023artpy], they lack +modularity and are limited in scope, often implementing just one or two models. For +instance, MATLAB-based ART toolboxes [@mathworks_art1s; @mathworks_fuzzyart_fuzzyartmap; +@mathworks_topoart; @mathworks_art_fuzzyart_artmap] provide implementations of +Fuzzy ART, TopoART, ART1, and ARTMAP models, but they lack the flexibility and +modularity required for broader experimentation. The most significant existing ART +implementation exists in julia and provides just five models +[@Petrenko_AdaptiveResonance_jl_A_Julia_2022] but, like the previously listed +MATLAB-based toolboxes, it is not easily accessible to Python-based work flows and +lacks a modular design. + +These existing implementations of ART models may provide standalone versions of +individual models, but they are often not designed to integrate seamlessly with modern +Python libraries such as scikit-learn, NumPy, and SciPy. As a result, researchers and +developers working in Python-based environments face challenges when trying to +incorporate ART models into their machine learning pipelines. + +In contrast, **artlib** offers a comprehensive and modular collection of ART models, +including both elementary and compound ART architectures. It is designed for +interoperability with popular Python tools, enabling users to easily integrate ART +models into machine learning workflows, optimize models using scikit-learn's +`GridSearchCV`, and preprocess data using standard libraries. Further, **artlib** +provides users the flexibility to construct their own compound ART modules (those +art modules deriving properties from other, elementary modules) which +may or may not exist in published literature. **artlib** also provides a template +in the source code to encourage users to develop and experiment with their own custom +ART algorithms. This flexibility and integration make **artlib** a powerful resource +for both research and practical applications. + + # Acknowledgements This research was supported by the National Science Foundation (NSF) under Award