aisp 0.3.2__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aisp-0.3.2 → aisp-0.4.0}/PKG-INFO +2 -1
- {aisp-0.3.2 → aisp-0.4.0}/README.md +1 -0
- aisp-0.4.0/aisp/__init__.py +39 -0
- aisp-0.4.0/aisp/base/__init__.py +26 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/base/_classifier.py +4 -2
- aisp-0.4.0/aisp/base/_optimizer.py +188 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/base/mutation.py +86 -18
- aisp-0.4.0/aisp/base/populations.py +49 -0
- aisp-0.4.0/aisp/csa/__init__.py +20 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/csa/_ai_recognition_sys.py +10 -8
- {aisp-0.3.2 → aisp-0.4.0}/aisp/csa/_cell.py +2 -2
- aisp-0.4.0/aisp/csa/_clonalg.py +369 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/ina/__init__.py +2 -2
- {aisp-0.3.2 → aisp-0.4.0}/aisp/ina/_ai_network.py +18 -9
- {aisp-0.3.2 → aisp-0.4.0}/aisp/ina/_base.py +0 -40
- {aisp-0.3.2 → aisp-0.4.0}/aisp/nsa/__init__.py +7 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/nsa/_binary_negative_selection.py +1 -1
- {aisp-0.3.2 → aisp-0.4.0}/aisp/nsa/_negative_selection.py +4 -4
- aisp-0.4.0/aisp/utils/display.py +185 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/utils/distance.py +4 -4
- aisp-0.4.0/aisp/utils/sanitizers.py +116 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/utils/types.py +13 -5
- {aisp-0.3.2 → aisp-0.4.0}/aisp.egg-info/PKG-INFO +2 -1
- {aisp-0.3.2 → aisp-0.4.0}/aisp.egg-info/SOURCES.txt +4 -0
- {aisp-0.3.2 → aisp-0.4.0}/pyproject.toml +1 -1
- aisp-0.3.2/aisp/__init__.py +0 -26
- aisp-0.3.2/aisp/base/__init__.py +0 -7
- aisp-0.3.2/aisp/csa/__init__.py +0 -9
- aisp-0.3.2/aisp/utils/sanitizers.py +0 -64
- {aisp-0.3.2 → aisp-0.4.0}/LICENSE +0 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/base/_base.py +0 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/base/_clusterer.py +0 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/csa/_base.py +0 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/exceptions.py +0 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/nsa/_base.py +0 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/nsa/_ns_core.py +0 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/utils/__init__.py +0 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/utils/_multiclass.py +0 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/utils/metrics.py +0 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp/utils/validation.py +0 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp.egg-info/dependency_links.txt +0 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp.egg-info/requires.txt +0 -0
- {aisp-0.3.2 → aisp-0.4.0}/aisp.egg-info/top_level.txt +0 -0
- {aisp-0.3.2 → aisp-0.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: aisp
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0
|
4
4
|
Summary: Package with techniques of artificial immune systems.
|
5
5
|
Author-email: João Paulo da Silva Barros <jpsilvabarr@gmail.com>
|
6
6
|
Maintainer-email: Alison Zille Lopes <alisonzille@gmail.com>
|
@@ -86,6 +86,7 @@ Artificial Immune Systems (AIS) are inspired by the vertebrate immune system, cr
|
|
86
86
|
> - [x] [**Negative Selection.**](https://ais-package.github.io/docs/aisp-techniques/negative-selection/)
|
87
87
|
> - [x] [**Clonal Selection Algorithms.**](https://ais-package.github.io/docs/aisp-techniques/clonal-selection-algorithms/)
|
88
88
|
> * [AIRS - Artificial Immune Recognition System](https://ais-package.github.io/docs/aisp-techniques/clonal-selection-algorithms/airs/)
|
89
|
+
> * [CLONALG - Clonal Selection Algorithm](https://ais-package.github.io/docs/aisp-techniques/clonal-selection-algorithms/clonalg)
|
89
90
|
> - [ ] *Danger Theory.*
|
90
91
|
> - [x] [*Immune Network Theory.*](https://ais-package.github.io/docs/aisp-techniques/immune-network-theory/)
|
91
92
|
> - [AiNet - Artificial Immune Network para Clustering and Compression](https://ais-package.github.io/docs/aisp-techniques/immune-network-theory/ainet)
|
@@ -51,6 +51,7 @@ Artificial Immune Systems (AIS) are inspired by the vertebrate immune system, cr
|
|
51
51
|
> - [x] [**Negative Selection.**](https://ais-package.github.io/docs/aisp-techniques/negative-selection/)
|
52
52
|
> - [x] [**Clonal Selection Algorithms.**](https://ais-package.github.io/docs/aisp-techniques/clonal-selection-algorithms/)
|
53
53
|
> * [AIRS - Artificial Immune Recognition System](https://ais-package.github.io/docs/aisp-techniques/clonal-selection-algorithms/airs/)
|
54
|
+
> * [CLONALG - Clonal Selection Algorithm](https://ais-package.github.io/docs/aisp-techniques/clonal-selection-algorithms/clonalg)
|
54
55
|
> - [ ] *Danger Theory.*
|
55
56
|
> - [x] [*Immune Network Theory.*](https://ais-package.github.io/docs/aisp-techniques/immune-network-theory/)
|
56
57
|
> - [AiNet - Artificial Immune Network para Clustering and Compression](https://ais-package.github.io/docs/aisp-techniques/immune-network-theory/ainet)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
"""AISP - Artificial Immune Systems Package.
|
2
|
+
|
3
|
+
AISP is a Python package of immunoinspired techniques that apply metaphors from the vertebrate
|
4
|
+
immune system to pattern recognition and optimization tasks.
|
5
|
+
|
6
|
+
The package is organized into specialized modules, each dedicated to a family of Artificial
|
7
|
+
Immune Systems algorithms:
|
8
|
+
|
9
|
+
Modules
|
10
|
+
-------
|
11
|
+
csa : Clonal Selection Algorithms.
|
12
|
+
Inspired by the processes of antibody proliferation and mutation.
|
13
|
+
- AIRS: Artificial Immune Recognition System for classification.
|
14
|
+
- Clonalg: Clonal Selection Algorithm for optimization.
|
15
|
+
|
16
|
+
nsa : Negative Selection Algorithms
|
17
|
+
Simulates T cell maturation and is capable of detecting non-self cells.
|
18
|
+
- RNSA: Real-value Negative Selection Algorithm for classification.
|
19
|
+
- BNSA: Binary Negative Selection Algorithm for classification.
|
20
|
+
|
21
|
+
ina : Immune Network Algorithms
|
22
|
+
Based on immune network theory.
|
23
|
+
- AiNet: Artificial Immune Network for clustering.
|
24
|
+
|
25
|
+
For detailed documentation and examples, visit:
|
26
|
+
https://ais-package.github.io/docs/intro
|
27
|
+
"""
|
28
|
+
|
29
|
+
from . import csa
|
30
|
+
from . import ina
|
31
|
+
from . import nsa
|
32
|
+
|
33
|
+
__author__ = "AISP Development Team"
|
34
|
+
__version__ = "0.4.0"
|
35
|
+
__all__ = [
|
36
|
+
'csa',
|
37
|
+
'nsa',
|
38
|
+
'ina'
|
39
|
+
]
|
@@ -0,0 +1,26 @@
|
|
1
|
+
"""Base Classes and Core Utilities.
|
2
|
+
|
3
|
+
This module provides the fundamental classes and utilities that serve as the basis for all
|
4
|
+
Artificial Immune Systems algorithms implemented in the AISP package.
|
5
|
+
|
6
|
+
Classes
|
7
|
+
-------
|
8
|
+
BaseClassifier
|
9
|
+
Abstract Base Class for Classification Algorithms.
|
10
|
+
BaseClusterer
|
11
|
+
Abstract Base Class for Clustering Algorithms.
|
12
|
+
BaseOptimizer
|
13
|
+
Abstract Base Class for optimization algorithms.
|
14
|
+
|
15
|
+
Functions
|
16
|
+
---------
|
17
|
+
set_seed_numba
|
18
|
+
Set Random Seed for Numba JIT Compilation.
|
19
|
+
"""
|
20
|
+
|
21
|
+
from ._base import set_seed_numba
|
22
|
+
from ._classifier import BaseClassifier
|
23
|
+
from ._clusterer import BaseClusterer
|
24
|
+
from ._optimizer import BaseOptimizer
|
25
|
+
|
26
|
+
__all__ = ['BaseClassifier', 'BaseClusterer', 'BaseOptimizer', 'set_seed_numba']
|
@@ -13,9 +13,11 @@ from ..utils.metrics import accuracy_score
|
|
13
13
|
|
14
14
|
|
15
15
|
class BaseClassifier(ABC, Base):
|
16
|
-
"""
|
16
|
+
"""Abstract base class for classification algorithms.
|
17
17
|
|
18
|
-
|
18
|
+
This class defines the core interface for classification models. It enforces the
|
19
|
+
implementation of the ``fit`` and ``predict`` methods in all derived classes,
|
20
|
+
and provides a default implementation of ``score`` and utility functions.
|
19
21
|
"""
|
20
22
|
|
21
23
|
classes: Union[npt.NDArray, list] = []
|
@@ -0,0 +1,188 @@
|
|
1
|
+
"""Base class for optimization algorithms."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from abc import ABC, abstractmethod
|
6
|
+
from typing import Optional, List, Any, Callable
|
7
|
+
|
8
|
+
from ..utils.display import TableFormatter
|
9
|
+
|
10
|
+
|
11
|
+
class BaseOptimizer(ABC):
|
12
|
+
"""Abstract base class for optimization algorithms.
|
13
|
+
|
14
|
+
This class defines the core interface for optimization strategies. It keeps track of cost
|
15
|
+
history, evaluated solutions, and the best solution found during the optimization process.
|
16
|
+
Subclasses must implement ``optimize`` and ``affinity_function``.
|
17
|
+
"""
|
18
|
+
|
19
|
+
def __init__(self) -> None:
|
20
|
+
self._cost_history: List[float] = []
|
21
|
+
self._solution_history: list = []
|
22
|
+
self._best_solution: Optional[Any] = None
|
23
|
+
self._best_cost: Optional[float] = None
|
24
|
+
self._protected_aliases = [
|
25
|
+
"__init__",
|
26
|
+
"optimize",
|
27
|
+
"register",
|
28
|
+
"get_report"
|
29
|
+
]
|
30
|
+
self.mode = "min"
|
31
|
+
|
32
|
+
@property
|
33
|
+
def cost_history(self) -> List[float]:
|
34
|
+
"""Return the history of costs during optimization."""
|
35
|
+
return self._cost_history
|
36
|
+
|
37
|
+
@property
|
38
|
+
def solution_history(self) -> List:
|
39
|
+
"""Returns the history of evaluated solutions."""
|
40
|
+
return self._solution_history
|
41
|
+
|
42
|
+
@property
|
43
|
+
def best_solution(self) -> Optional[Any]:
|
44
|
+
"""Return the best solution found so far, or ``None`` if unavailable."""
|
45
|
+
return self._best_solution
|
46
|
+
|
47
|
+
@property
|
48
|
+
def best_cost(self) -> Optional[float]:
|
49
|
+
"""Return the cost of the best solution found so far, or ``None`` if unavailable."""
|
50
|
+
return self._best_cost
|
51
|
+
|
52
|
+
def _record_best(self, cost: float, best_solution: Any) -> None:
|
53
|
+
"""Record a new cost value and update the best solution if improved.
|
54
|
+
|
55
|
+
Parameters
|
56
|
+
----------
|
57
|
+
cost : float
|
58
|
+
Cost value to be added to the history.
|
59
|
+
"""
|
60
|
+
self._solution_history.append(best_solution)
|
61
|
+
self._cost_history.append(cost)
|
62
|
+
is_better = (
|
63
|
+
self._best_cost is None or
|
64
|
+
(self.mode == "min" and cost < self._best_cost) or
|
65
|
+
(self.mode == "max" and cost > self._best_cost)
|
66
|
+
)
|
67
|
+
if is_better:
|
68
|
+
self._best_solution = best_solution
|
69
|
+
self._best_cost = cost
|
70
|
+
|
71
|
+
def get_report(self) -> str:
|
72
|
+
"""Generate a formatted summary report of the optimization process.
|
73
|
+
|
74
|
+
The report includes the best solution, its associated cost, and the evolution of cost
|
75
|
+
values per iteration.
|
76
|
+
|
77
|
+
Returns
|
78
|
+
-------
|
79
|
+
report : str
|
80
|
+
A formatted string containing the optimization summary.
|
81
|
+
"""
|
82
|
+
if not self._cost_history:
|
83
|
+
return "Optimization has not been run. The report is empty."
|
84
|
+
|
85
|
+
header = "\n" + "=" * 45 + "\n"
|
86
|
+
report_parts = [
|
87
|
+
header,
|
88
|
+
f"{'Optimization Summary':^45}",
|
89
|
+
header,
|
90
|
+
f"Best cost : {self.best_cost}\n",
|
91
|
+
f"Best solution : {self.best_solution}\n",
|
92
|
+
"Cost History per Iteration:\n"
|
93
|
+
]
|
94
|
+
table_formatter = TableFormatter(
|
95
|
+
{
|
96
|
+
'Iteration': 12,
|
97
|
+
'Cost': 28
|
98
|
+
}
|
99
|
+
)
|
100
|
+
|
101
|
+
report_parts.extend([table_formatter.get_header()])
|
102
|
+
|
103
|
+
for i, cost in enumerate(self._cost_history, start=1):
|
104
|
+
report_parts.append(
|
105
|
+
'\n' + table_formatter.get_row(
|
106
|
+
{
|
107
|
+
'Iteration': f"{i:>11} ",
|
108
|
+
'Cost': f"{cost:>27.6f} "
|
109
|
+
}
|
110
|
+
)
|
111
|
+
)
|
112
|
+
|
113
|
+
report_parts.append(table_formatter.get_bottom(True))
|
114
|
+
return "".join(report_parts)
|
115
|
+
|
116
|
+
@abstractmethod
|
117
|
+
def optimize(self, max_iters: int = 50, n_iter_no_change=10, verbose: bool = True) -> Any:
|
118
|
+
"""Execute the optimization process.
|
119
|
+
|
120
|
+
This abstract method must be implemented by the subclass, defining
|
121
|
+
how the optimization strategy explores the search space.
|
122
|
+
|
123
|
+
Parameters
|
124
|
+
----------
|
125
|
+
max_iters : int
|
126
|
+
Maximum number of interactions
|
127
|
+
n_iter_no_change: int, default=10
|
128
|
+
the maximum number of iterations without updating the best
|
129
|
+
verbose : bool, default=True
|
130
|
+
Flag to enable or disable detailed output during optimization.
|
131
|
+
|
132
|
+
Returns
|
133
|
+
-------
|
134
|
+
best_solution : Any
|
135
|
+
The best solution found by the optimization algorithm.
|
136
|
+
"""
|
137
|
+
|
138
|
+
@abstractmethod
|
139
|
+
def affinity_function(self, solution: Any) -> float:
|
140
|
+
"""Evaluate the affinity of a candidate solution.
|
141
|
+
|
142
|
+
This abstract method must be implemented by the subclass to define the problem-specific.
|
143
|
+
|
144
|
+
Parameters
|
145
|
+
----------
|
146
|
+
solution : Any
|
147
|
+
Candidate solution to be evaluated.
|
148
|
+
|
149
|
+
Returns
|
150
|
+
-------
|
151
|
+
cost : float
|
152
|
+
Cost value associated with the given solution.
|
153
|
+
"""
|
154
|
+
|
155
|
+
def register(self, alias: str, function: Callable[..., Any]) -> None:
|
156
|
+
"""Register a function dynamically in the optimizer instance.
|
157
|
+
|
158
|
+
Parameters
|
159
|
+
----------
|
160
|
+
alias : str
|
161
|
+
Name used to access the function as an attribute.
|
162
|
+
function : Callable[..., Any]
|
163
|
+
Callable to be registered.
|
164
|
+
|
165
|
+
Raises
|
166
|
+
------
|
167
|
+
TypeError
|
168
|
+
If `function` is not callable.
|
169
|
+
AttributeError
|
170
|
+
If `alias` is protected and cannot be modified. Or if `alias` does not exist in the
|
171
|
+
optimizer class.
|
172
|
+
"""
|
173
|
+
if not callable(function):
|
174
|
+
raise TypeError(f"Expected a function for '{alias}', got {type(function).__name__}")
|
175
|
+
if alias in self._protected_aliases or alias.startswith("_"):
|
176
|
+
raise AttributeError(f"The alias '{alias}' is protected and cannot be modified.")
|
177
|
+
if not hasattr(self, alias):
|
178
|
+
raise AttributeError(
|
179
|
+
f"Alias '{alias}' is not a valid method of {self.__class__.__name__}"
|
180
|
+
)
|
181
|
+
setattr(self, alias, function)
|
182
|
+
|
183
|
+
def reset(self):
|
184
|
+
"""Reset the object's internal state, clearing history and resetting values."""
|
185
|
+
self._cost_history: List[float] = []
|
186
|
+
self._solution_history: list = []
|
187
|
+
self._best_solution: Optional[Any] = None
|
188
|
+
self._best_cost: Optional[float] = None
|
@@ -10,10 +10,11 @@ import numpy.typing as npt
|
|
10
10
|
from numba import njit, types
|
11
11
|
|
12
12
|
|
13
|
-
@njit([(types.float64[:], types.int64)], cache=True)
|
13
|
+
@njit([(types.float64[:], types.int64, types.float64)], cache=True)
|
14
14
|
def clone_and_mutate_continuous(
|
15
15
|
vector: npt.NDArray[np.float64],
|
16
|
-
n: int
|
16
|
+
n: int,
|
17
|
+
mutation_rate: float
|
17
18
|
) -> npt.NDArray[np.float64]:
|
18
19
|
"""
|
19
20
|
Generate a set of mutated clones from a cell represented by a continuous vector.
|
@@ -28,6 +29,10 @@ def clone_and_mutate_continuous(
|
|
28
29
|
The original immune cell with continuous values to be cloned and mutated.
|
29
30
|
n : int
|
30
31
|
The number of mutated clones to be generated.
|
32
|
+
mutation_rate : float, default=1
|
33
|
+
If 0 <= mutation_rate < 1: probability of mutating each component.
|
34
|
+
If mutation_rate >= 1 or mutation_rate <= 0: the mutation randomizes
|
35
|
+
a number of components between 1 and len(vector).
|
31
36
|
|
32
37
|
Returns
|
33
38
|
-------
|
@@ -37,13 +42,23 @@ def clone_and_mutate_continuous(
|
|
37
42
|
n_features = vector.shape[0]
|
38
43
|
clone_set = np.empty((n, n_features), dtype=np.float64)
|
39
44
|
for i in range(n):
|
40
|
-
n_mutations = np.random.randint(1, n_features)
|
41
45
|
clone = vector.copy()
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
46
|
+
if 0 <= mutation_rate < 1:
|
47
|
+
any_mutation = False
|
48
|
+
for j in range(n_features):
|
49
|
+
if np.random.random() < mutation_rate:
|
50
|
+
clone[j] = np.random.random()
|
51
|
+
any_mutation = True
|
52
|
+
if not any_mutation:
|
53
|
+
idx = np.random.randint(0, n_features)
|
54
|
+
clone[idx] = np.random.random()
|
55
|
+
else:
|
56
|
+
n_mutations = np.random.randint(1, n_features)
|
57
|
+
position_mutations = np.random.permutation(n_features)[:n_mutations]
|
58
|
+
for j in range(n_mutations):
|
59
|
+
idx = position_mutations[j]
|
60
|
+
clone[idx] = np.float64(np.random.random())
|
61
|
+
clone_set[i] = clone
|
47
62
|
|
48
63
|
return clone_set
|
49
64
|
|
@@ -75,8 +90,8 @@ def clone_and_mutate_binary(
|
|
75
90
|
n_features = vector.shape[0]
|
76
91
|
clone_set = np.empty((n, n_features), dtype=np.bool_)
|
77
92
|
for i in range(n):
|
78
|
-
n_mutations = np.random.randint(1, n_features)
|
79
93
|
clone = vector.copy()
|
94
|
+
n_mutations = np.random.randint(1, n_features)
|
80
95
|
position_mutations = np.random.permutation(n_features)[:n_mutations]
|
81
96
|
for j in range(n_mutations):
|
82
97
|
idx = position_mutations[j]
|
@@ -86,11 +101,12 @@ def clone_and_mutate_binary(
|
|
86
101
|
return clone_set
|
87
102
|
|
88
103
|
|
89
|
-
@njit([(types.float64[:], types.int64, types.float64[:, :])], cache=True)
|
104
|
+
@njit([(types.float64[:], types.int64, types.float64[:, :], types.float64)], cache=True)
|
90
105
|
def clone_and_mutate_ranged(
|
91
106
|
vector: npt.NDArray[np.float64],
|
92
107
|
n: int,
|
93
|
-
bounds: npt.NDArray[np.float64]
|
108
|
+
bounds: npt.NDArray[np.float64],
|
109
|
+
mutation_rate: float
|
94
110
|
) -> npt.NDArray[np.float64]:
|
95
111
|
"""
|
96
112
|
Generate a set of mutated clones from a cell represented by custom ranges per dimension.
|
@@ -107,6 +123,10 @@ def clone_and_mutate_ranged(
|
|
107
123
|
The number of mutated clones to be generated.
|
108
124
|
bounds : np.ndarray
|
109
125
|
Array (n_features, 2) with min and max per dimension.
|
126
|
+
mutation_rate : float, default=1
|
127
|
+
If 0 <= mutation_rate < 1: probability of mutating each component.
|
128
|
+
If mutation_rate >= 1 or mutation_rate <= 0: the mutation randomizes
|
129
|
+
a number of components between 1 and len(vector).
|
110
130
|
|
111
131
|
Returns
|
112
132
|
-------
|
@@ -117,14 +137,62 @@ def clone_and_mutate_ranged(
|
|
117
137
|
clone_set = np.empty((n, n_features), dtype=np.float64)
|
118
138
|
|
119
139
|
for i in range(n):
|
120
|
-
n_mutations = np.random.randint(1, n_features)
|
121
140
|
clone = vector.copy()
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
141
|
+
if 0 <= mutation_rate < 1:
|
142
|
+
any_mutation = False
|
143
|
+
for j in range(n_features):
|
144
|
+
if np.random.random() < mutation_rate:
|
145
|
+
clone[j] = np.random.uniform(low=bounds[0][j], high=bounds[1][j])
|
146
|
+
any_mutation = True
|
147
|
+
if not any_mutation:
|
148
|
+
idx = np.random.randint(0, n_features)
|
149
|
+
clone[idx] = np.random.uniform(low=bounds[0][idx], high=bounds[1][idx])
|
150
|
+
else:
|
151
|
+
n_mutations = np.random.randint(1, n_features)
|
152
|
+
position_mutations = np.random.permutation(n_features)[:n_mutations]
|
153
|
+
for j in range(n_mutations):
|
154
|
+
idx = position_mutations[j]
|
155
|
+
min_limit = bounds[0][idx]
|
156
|
+
max_limit = bounds[1][idx]
|
157
|
+
clone[idx] = np.random.uniform(low=min_limit, high=max_limit)
|
158
|
+
clone_set[i] = clone
|
159
|
+
|
160
|
+
return clone_set
|
161
|
+
|
162
|
+
|
163
|
+
@njit([(types.int64[:], types.int64, types.float64)], cache=True)
|
164
|
+
def clone_and_mutate_permutation(
|
165
|
+
vector: npt.NDArray[np.int64],
|
166
|
+
n: int,
|
167
|
+
mutation_rate: float
|
168
|
+
) -> npt.NDArray[np.int64]:
|
169
|
+
"""Generate a set of mutated clones by random permutation.
|
170
|
+
|
171
|
+
Parameters
|
172
|
+
----------
|
173
|
+
vector : npt.NDArray[np.int64]
|
174
|
+
The original immune cell with permutation values to be cloned and mutated.
|
175
|
+
n : int
|
176
|
+
The number of mutated clones to be generated.
|
177
|
+
mutation_rate : float
|
178
|
+
Probability of mutating each component 0 <= mutation_rate < 1.
|
179
|
+
|
180
|
+
Returns
|
181
|
+
-------
|
182
|
+
clone_set : npt.NDArray
|
183
|
+
An Array(n, len(vector)) containing the `n` mutated clones of the original vector.
|
184
|
+
"""
|
185
|
+
n_features = vector.shape[0]
|
186
|
+
clone_set = np.empty((n, n_features), dtype=np.int64)
|
187
|
+
|
188
|
+
for i in range(n):
|
189
|
+
clone = vector.copy()
|
190
|
+
for j in range(n_features):
|
191
|
+
if np.random.random() < mutation_rate:
|
192
|
+
idx = np.random.randint(0, n_features)
|
193
|
+
tmp = clone[j]
|
194
|
+
clone[j] = clone[idx]
|
195
|
+
clone[idx] = tmp
|
128
196
|
clone_set[i] = clone
|
129
197
|
|
130
198
|
return clone_set
|
@@ -0,0 +1,49 @@
|
|
1
|
+
"""Provide utility functions for generating antibody populations in immunological algorithms."""
|
2
|
+
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
import numpy as np
|
6
|
+
import numpy.typing as npt
|
7
|
+
|
8
|
+
from ..utils.types import FeatureTypeAll
|
9
|
+
|
10
|
+
|
11
|
+
def generate_random_antibodies(
|
12
|
+
n_samples: int,
|
13
|
+
n_features: int,
|
14
|
+
feature_type: FeatureTypeAll = "continuous-features",
|
15
|
+
bounds: Optional[npt.NDArray[np.float64]] = None
|
16
|
+
) -> npt.NDArray:
|
17
|
+
"""
|
18
|
+
Generate a random antibody population.
|
19
|
+
|
20
|
+
Parameters
|
21
|
+
----------
|
22
|
+
n_samples : int
|
23
|
+
Number of antibodies (samples) to generate.
|
24
|
+
n_features : int
|
25
|
+
Number of features (dimensions) for each antibody.
|
26
|
+
feature_type : FeatureType, default="continuous-features"
|
27
|
+
Specifies the type of features: "continuous-features", "binary-features",
|
28
|
+
"ranged-features", or "permutation-features".
|
29
|
+
bounds : np.ndarray
|
30
|
+
Array (n_features, 2) with min and max per dimension.
|
31
|
+
|
32
|
+
Returns
|
33
|
+
-------
|
34
|
+
npt.NDArray
|
35
|
+
Array of shape (n_samples, n_features) containing the generated antibodies.
|
36
|
+
"""
|
37
|
+
if n_features <= 0:
|
38
|
+
raise ValueError("Number of features must be greater than zero.")
|
39
|
+
|
40
|
+
if feature_type == "binary-features":
|
41
|
+
return np.random.randint(0, 2, size=(n_samples, n_features)).astype(np.bool_)
|
42
|
+
if feature_type == "ranged-features" and bounds is not None:
|
43
|
+
return np.random.uniform(low=bounds[0], high=bounds[1], size=(n_samples, n_features))
|
44
|
+
if feature_type == "permutation-features":
|
45
|
+
return np.array(
|
46
|
+
[np.random.permutation(n_features) for _ in range(n_samples)]
|
47
|
+
).astype(dtype=np.int64)
|
48
|
+
|
49
|
+
return np.random.random_sample(size=(n_samples, n_features))
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"""Module (CSA) Clonal Selection Algorithm.
|
2
|
+
|
3
|
+
CSAs are inspired by the process of antibody proliferation upon detecting an antigen, during which
|
4
|
+
the generated antibodies undergo mutations in an attempt to enhance pathogen recognition.
|
5
|
+
|
6
|
+
Classes
|
7
|
+
-------
|
8
|
+
AIRS : Artificial Immune Recognition System.
|
9
|
+
A supervised learning algorithm for classification tasks based on the clonal
|
10
|
+
selection principle.
|
11
|
+
Clonalg : Clonal Selection Algorithm.
|
12
|
+
Implementation of the clonal selection algorithm for optimization, adapted
|
13
|
+
for both minimization and maximization of cost functions in binary,
|
14
|
+
continuous, and permutation problems.
|
15
|
+
"""
|
16
|
+
from ._ai_recognition_sys import AIRS
|
17
|
+
from ._clonalg import Clonalg
|
18
|
+
|
19
|
+
__author__ = 'João Paulo da Silva Barros'
|
20
|
+
__all__ = ['AIRS', 'Clonalg']
|
@@ -111,13 +111,13 @@ class AIRS(BaseAIRS):
|
|
111
111
|
Way to calculate the distance between the detector and the sample:
|
112
112
|
|
113
113
|
* ``'Euclidean'`` ➜ The calculation of the distance is given by the expression:
|
114
|
-
√( (x₁
|
114
|
+
√( (x₁ - x₂)² + (y₁ - y₂)² + ... + (yn - yn)²).
|
115
115
|
|
116
116
|
* ``'minkowski'`` ➜ The calculation of the distance is given by the expression:
|
117
|
-
( |X₁
|
117
|
+
( |X₁ - Y₁|p + |X₂ - Y₂|p + ... + |Xn - Yn|p) ¹/ₚ.
|
118
118
|
|
119
119
|
* ``'manhattan'`` ➜ The calculation of the distance is given by the expression:
|
120
|
-
( |x₁
|
120
|
+
( |x₁ - x₂| + |y₁ - y₂| + ... + |yn - yn|).
|
121
121
|
|
122
122
|
seed : int
|
123
123
|
Seed for the random generation of detector values. Defaults to None.
|
@@ -139,7 +139,7 @@ class AIRS(BaseAIRS):
|
|
139
139
|
|
140
140
|
References
|
141
141
|
----------
|
142
|
-
.. [1] Brabazon, A., O
|
142
|
+
.. [1] Brabazon, A., O'Neill, M., & McGarraghy, S. (2015). Natural Computing Algorithms. In
|
143
143
|
Natural Computing Series. Springer Berlin Heidelberg.
|
144
144
|
https://doi.org/10.1007/978-3-662-43631-8
|
145
145
|
|
@@ -195,6 +195,7 @@ class AIRS(BaseAIRS):
|
|
195
195
|
self.affinity_threshold = 0.0
|
196
196
|
self.classes = []
|
197
197
|
self._bounds: Optional[npt.NDArray[np.float64]] = None
|
198
|
+
self._n_features: Optional[int] = None
|
198
199
|
|
199
200
|
@property
|
200
201
|
def cells_memory(self) -> Optional[Dict[str, list[Cell]]]:
|
@@ -235,6 +236,7 @@ class AIRS(BaseAIRS):
|
|
235
236
|
self._bounds = np.vstack([np.min(X, axis=0), np.max(X, axis=0)])
|
236
237
|
|
237
238
|
self.classes = np.unique(y)
|
239
|
+
self._n_features = X.shape[1]
|
238
240
|
sample_index = self._slice_index_list_by_class(y)
|
239
241
|
progress = tqdm(
|
240
242
|
total=len(y),
|
@@ -330,11 +332,11 @@ class AIRS(BaseAIRS):
|
|
330
332
|
An ndarray of the form ``C`` [``N samples``], containing the predicted classes for
|
331
333
|
``X``. or ``None``: If there are no detectors for the prediction.
|
332
334
|
"""
|
333
|
-
if self._all_class_cell_vectors is None:
|
335
|
+
if self._all_class_cell_vectors is None or self._n_features is None:
|
334
336
|
return None
|
335
337
|
|
336
338
|
super()._check_and_raise_exceptions_predict(
|
337
|
-
X,
|
339
|
+
X, self._n_features, self._feature_type
|
338
340
|
)
|
339
341
|
|
340
342
|
c: list = []
|
@@ -380,7 +382,7 @@ class AIRS(BaseAIRS):
|
|
380
382
|
|
381
383
|
References
|
382
384
|
----------
|
383
|
-
.. [1] Brabazon, A., O
|
385
|
+
.. [1] Brabazon, A., O'Neill, M., & McGarraghy, S. (2015).
|
384
386
|
Natural Computing Algorithms. Natural Computing Series.
|
385
387
|
Springer Berlin Heidelberg. https://doi.org/10.1007/978-3-662-43631-8
|
386
388
|
"""
|
@@ -441,7 +443,7 @@ class AIRS(BaseAIRS):
|
|
441
443
|
distances = pdist(antigens_list, metric="hamming")
|
442
444
|
else:
|
443
445
|
metric_kwargs = {'p': self.p} if self.metric == 'minkowski' else {}
|
444
|
-
distances = pdist(antigens_list, metric=self.metric, **metric_kwargs)
|
446
|
+
distances = pdist(antigens_list, metric=self.metric, **metric_kwargs) # type: ignore
|
445
447
|
|
446
448
|
n = antigens_list.shape[0]
|
447
449
|
sum_affinity = np.sum(1.0 - (distances / (1.0 + distances)))
|
@@ -53,8 +53,8 @@ class Cell:
|
|
53
53
|
if feature_type == "binary-features":
|
54
54
|
return clone_and_mutate_binary(self.vector, n)
|
55
55
|
if feature_type == "ranged-features" and bounds is not None:
|
56
|
-
clone_and_mutate_ranged(self.vector, n, bounds)
|
57
|
-
return clone_and_mutate_continuous(self.vector, n)
|
56
|
+
clone_and_mutate_ranged(self.vector, n, bounds, np.float64(1.0))
|
57
|
+
return clone_and_mutate_continuous(self.vector, n, np.float64(1.0))
|
58
58
|
|
59
59
|
def __eq__(self, other):
|
60
60
|
"""Check if two cells are equal."""
|