aisp 0.1.32__py3-none-any.whl → 0.1.33__py3-none-any.whl
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/NSA/__init__.py +1 -1
- aisp/NSA/_base.py +82 -41
- aisp/NSA/_negative_selection.py +117 -122
- aisp/utils/__init__.py +5 -0
- aisp/utils/_multiclass.py +43 -0
- aisp/utils/metrics.py +61 -0
- {aisp-0.1.32.dist-info → aisp-0.1.33.dist-info}/METADATA +3 -26
- aisp-0.1.33.dist-info/RECORD +11 -0
- {aisp-0.1.32.dist-info → aisp-0.1.33.dist-info}/WHEEL +1 -1
- aisp-0.1.32.dist-info/RECORD +0 -8
- {aisp-0.1.32.dist-info → aisp-0.1.33.dist-info}/LICENSE +0 -0
- {aisp-0.1.32.dist-info → aisp-0.1.33.dist-info}/top_level.txt +0 -0
aisp/NSA/__init__.py
CHANGED
aisp/NSA/_base.py
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
+
from abc import abstractmethod
|
2
|
+
|
1
3
|
import numpy as np
|
2
4
|
import numpy.typing as npt
|
3
|
-
from typing import Literal
|
5
|
+
from typing import Literal, Optional
|
4
6
|
from scipy.spatial.distance import euclidean, cityblock, minkowski
|
5
7
|
|
8
|
+
from ..utils.metrics import accuracy_score
|
9
|
+
|
6
10
|
|
7
11
|
class Base:
|
8
12
|
"""
|
@@ -93,8 +97,11 @@ class Base:
|
|
93
97
|
else:
|
94
98
|
return euclidean(u, v)
|
95
99
|
|
96
|
-
|
97
|
-
|
100
|
+
@staticmethod
|
101
|
+
def _check_and_raise_exceptions_fit(
|
102
|
+
X: npt.NDArray = None,
|
103
|
+
y: npt.NDArray = None,
|
104
|
+
_class_: Literal["RNSA", "BNSA"] = "RNSA",
|
98
105
|
):
|
99
106
|
"""
|
100
107
|
Function responsible for verifying fit function parameters and throwing exceptions if the \
|
@@ -149,43 +156,6 @@ class Base:
|
|
149
156
|
"The array X contains values that are not composed only of 0 and 1."
|
150
157
|
)
|
151
158
|
|
152
|
-
def _slice_index_list_by_class(self, y: npt.NDArray) -> dict:
|
153
|
-
"""
|
154
|
-
The function ``__slice_index_list_by_class(...)``, separates the indices of the lines \
|
155
|
-
according to the output class, to loop through the sample array, only in positions where \
|
156
|
-
the output is the class being trained.
|
157
|
-
|
158
|
-
Parameters:
|
159
|
-
---
|
160
|
-
* y (npt.NDArray): Receives a ``y``[``N sample``] array with the output classes of the \
|
161
|
-
``X`` sample array.
|
162
|
-
|
163
|
-
returns:
|
164
|
-
---
|
165
|
-
* dict: A dictionary with the list of array positions(``y``), with the classes as key.
|
166
|
-
|
167
|
-
---
|
168
|
-
|
169
|
-
A função ``__slice_index_list_by_class(...)``, separa os índices das linhas conforme a \
|
170
|
-
classe de saída, para percorrer o array de amostra, apenas nas posições que a saída for \
|
171
|
-
a classe que está sendo treinada.
|
172
|
-
|
173
|
-
Parameters:
|
174
|
-
---
|
175
|
-
* y (npt.NDArray): Recebe um array ``y``[``N amostra``] com as classes de saída do \
|
176
|
-
array de amostra ``X``.
|
177
|
-
|
178
|
-
Returns:
|
179
|
-
---
|
180
|
-
* dict: Um dicionário com a lista de posições do array(``y``), com as classes como chave.
|
181
|
-
"""
|
182
|
-
position_samples = dict()
|
183
|
-
for _class_ in self.classes:
|
184
|
-
# Pega as posições das amostras por classes a partir do y.
|
185
|
-
position_samples[_class_] = list(np.where(y == _class_)[0])
|
186
|
-
|
187
|
-
return position_samples
|
188
|
-
|
189
159
|
def _score(self, X: npt.NDArray, y: list) -> float:
|
190
160
|
"""
|
191
161
|
Score function calculates forecast accuracy.
|
@@ -237,4 +207,75 @@ class Base:
|
|
237
207
|
if len(y) == 0:
|
238
208
|
return 0
|
239
209
|
y_pred = self.predict(X)
|
240
|
-
return
|
210
|
+
return accuracy_score(y, y_pred)
|
211
|
+
|
212
|
+
@abstractmethod
|
213
|
+
def fit(self, X: npt.NDArray, y: npt.NDArray, verbose: bool = True):
|
214
|
+
"""
|
215
|
+
Function to train the model using the input data ``X`` and corresponding labels ``y``.
|
216
|
+
|
217
|
+
This abstract method is implemented by the class that inherits it.
|
218
|
+
|
219
|
+
Parameters:
|
220
|
+
---
|
221
|
+
* X (``npt.NDArray``): Input data used for training the model, previously normalized to the range [0, 1].
|
222
|
+
* y (``npt.NDArray``): Corresponding labels or target values for the input data.
|
223
|
+
* verbose (``bool``, optional): Flag to enable or disable detailed output during \
|
224
|
+
training. Default is ``True``.
|
225
|
+
|
226
|
+
Returns:
|
227
|
+
---
|
228
|
+
* self: Returns the instance of the class that implements this method.
|
229
|
+
|
230
|
+
---
|
231
|
+
|
232
|
+
Função para treinar o modelo usando os dados de entrada ``X`` e os classes correspondentes ``y``.
|
233
|
+
|
234
|
+
Este método abstrato é implementado pela classe que o herdar.
|
235
|
+
|
236
|
+
Parâmetros:
|
237
|
+
---
|
238
|
+
* X (``npt.NDArray``): Dados de entrada utilizados para o treinamento do modelo, previamente \
|
239
|
+
normalizados no intervalo [0, 1].
|
240
|
+
* y (``npt.NDArray``): Rótulos ou valores-alvo correspondentes aos dados de entrada.
|
241
|
+
* verbose (``bool``, opcional): Flag para ativar ou desativar a saída detalhada durante o \
|
242
|
+
treinamento. O padrão é ``True``.
|
243
|
+
|
244
|
+
Retornos:
|
245
|
+
---
|
246
|
+
* self: Retorna a instância da classe que implementa este método.
|
247
|
+
"""
|
248
|
+
pass
|
249
|
+
|
250
|
+
@abstractmethod
|
251
|
+
def predict(self, X) -> Optional[npt.NDArray]:
|
252
|
+
"""
|
253
|
+
Function to generate predictions based on the input data ``X``.
|
254
|
+
|
255
|
+
This abstract method is implemented by the class that inherits it.
|
256
|
+
|
257
|
+
Parameters:
|
258
|
+
---
|
259
|
+
* X (``npt.NDArray``): Input data for which predictions will be generated.
|
260
|
+
|
261
|
+
Returns:
|
262
|
+
---
|
263
|
+
* Predictions (``Optional[npt.NDArray]``): Predicted values for each input sample, or ``None``
|
264
|
+
if the prediction fails.
|
265
|
+
|
266
|
+
---
|
267
|
+
|
268
|
+
Função para gerar previsões com base nos dados de entrada ``X``.
|
269
|
+
|
270
|
+
Este método abstrato é implementado pela classe que o herdar.
|
271
|
+
|
272
|
+
Parâmetros:
|
273
|
+
---
|
274
|
+
* X (``npt.NDArray``): Dados de entrada para os quais as previsões serão geradas.
|
275
|
+
|
276
|
+
Retornos:
|
277
|
+
---
|
278
|
+
* Previsões (``Optional[npt.NDArray]``): Valores previstos para cada amostra de entrada,
|
279
|
+
ou ``None`` se a previsão falhar.
|
280
|
+
"""
|
281
|
+
pass
|
aisp/NSA/_negative_selection.py
CHANGED
@@ -6,6 +6,7 @@ from collections import namedtuple
|
|
6
6
|
from scipy.spatial.distance import cdist
|
7
7
|
|
8
8
|
from ._base import Base
|
9
|
+
from ..utils import slice_index_list_by_class
|
9
10
|
|
10
11
|
|
11
12
|
class RNSA(Base):
|
@@ -236,7 +237,7 @@ class RNSA(Base):
|
|
236
237
|
self.r_s: float = 0
|
237
238
|
|
238
239
|
if algorithm == "V-detector":
|
239
|
-
self._Detector = namedtuple("Detector", "position radius")
|
240
|
+
self._Detector = namedtuple("Detector", ["position", "radius"])
|
240
241
|
self._algorithm: str = algorithm
|
241
242
|
else:
|
242
243
|
self._Detector = namedtuple("Detector", "position")
|
@@ -247,12 +248,12 @@ class RNSA(Base):
|
|
247
248
|
else:
|
248
249
|
self.max_discards: int = 1000
|
249
250
|
|
250
|
-
#
|
251
|
+
# Retrieves the variables from kwargs.
|
251
252
|
self.p: float = kwargs.get("p", 2)
|
252
253
|
self._cell_bounds: bool = kwargs.get("cell_bounds", False)
|
253
254
|
self.non_self_label: str = kwargs.get("non_self_label", "non-self")
|
254
255
|
|
255
|
-
#
|
256
|
+
# Initializes the other class variables as None.
|
256
257
|
self.detectors: Union[dict, None] = None
|
257
258
|
self.classes: npt.NDArray = None
|
258
259
|
|
@@ -286,36 +287,37 @@ class RNSA(Base):
|
|
286
287
|
---
|
287
288
|
(``self``): Retorna a própria instância.
|
288
289
|
"""
|
290
|
+
progress = None
|
289
291
|
super()._check_and_raise_exceptions_fit(X, y)
|
290
292
|
|
291
|
-
#
|
293
|
+
# Identifying the possible classes within the output array `y`.
|
292
294
|
self.classes = np.unique(y)
|
293
|
-
#
|
295
|
+
# Dictionary that will store detectors with classes as keys.
|
294
296
|
list_detectors_by_class = dict()
|
295
|
-
#
|
297
|
+
# Separates the classes for training.
|
296
298
|
sample_index = self.__slice_index_list_by_class(y)
|
297
|
-
#
|
299
|
+
# Progress bar for generating all detectors.
|
298
300
|
if verbose:
|
299
301
|
progress = tqdm(total=int(self.N * (len(self.classes))),
|
300
|
-
bar_format="{desc} ┇{bar}┇ {n}/{total} detectors", postfix="\n",)
|
302
|
+
bar_format="{desc} ┇{bar}┇ {n}/{total} detectors", postfix="\n", )
|
301
303
|
for _class_ in self.classes:
|
302
|
-
#
|
304
|
+
# Initializes the empty set that will contain the valid detectors.
|
303
305
|
valid_detectors_set = []
|
304
306
|
discard_count = 0
|
305
|
-
#
|
307
|
+
# Indicating which class the algorithm is currently processing for the progress bar.
|
306
308
|
if verbose:
|
307
309
|
progress.set_description_str(
|
308
310
|
f"Generating the detectors for the {_class_} class:"
|
309
311
|
)
|
310
312
|
while len(valid_detectors_set) < self.N:
|
311
|
-
#
|
313
|
+
# Generates a candidate detector vector randomly with values between 0 and 1.
|
312
314
|
vector_x = np.random.random_sample(size=X.shape[1])
|
313
|
-
#
|
315
|
+
# Checks the validity of the detector for non-self with respect to the class samples.
|
314
316
|
valid_detector = self.__checks_valid_detector(
|
315
317
|
X=X, vector_x=vector_x, samples_index_class=sample_index[_class_]
|
316
318
|
)
|
317
319
|
|
318
|
-
#
|
320
|
+
# If the detector is valid, add it to the list of valid detectors.
|
319
321
|
if self._algorithm == "V-detector" and valid_detector is not False:
|
320
322
|
discard_count = 0
|
321
323
|
valid_detectors_set.append(
|
@@ -338,15 +340,15 @@ class RNSA(Base):
|
|
338
340
|
"consider reducing its value."
|
339
341
|
)
|
340
342
|
|
341
|
-
#
|
343
|
+
# Add detectors, with classes as keys in the dictionary.
|
342
344
|
list_detectors_by_class[_class_] = valid_detectors_set
|
343
|
-
#
|
345
|
+
# Notify completion of detector generation for the classes.
|
344
346
|
if verbose:
|
345
347
|
progress.set_description(
|
346
348
|
f'\033[92m✔ Non-self detectors for classes ({", ".join(map(str, self.classes))}) '
|
347
349
|
f'successfully generated\033[0m'
|
348
350
|
)
|
349
|
-
#
|
351
|
+
# Saves the found detectors in the attribute for the non-self detectors of the trained model.
|
350
352
|
self.detectors = list_detectors_by_class
|
351
353
|
return self
|
352
354
|
|
@@ -382,7 +384,7 @@ class RNSA(Base):
|
|
382
384
|
contendo as classes previstas para ``X``.
|
383
385
|
* ``None``: Se não existir detectores para a previsão.
|
384
386
|
"""
|
385
|
-
#
|
387
|
+
# If there are no detectors, returns None.
|
386
388
|
if self.detectors is None:
|
387
389
|
return None
|
388
390
|
elif not isinstance(X, (np.ndarray, list)):
|
@@ -394,9 +396,9 @@ class RNSA(Base):
|
|
394
396
|
)
|
395
397
|
)
|
396
398
|
|
397
|
-
#
|
399
|
+
# Initializes an empty array that will store the predictions.
|
398
400
|
C = np.empty(shape=0)
|
399
|
-
#
|
401
|
+
# For each sample row in X.
|
400
402
|
for line in X:
|
401
403
|
class_found: bool
|
402
404
|
_class_ = self.__compare_sample_to_detectors(line)
|
@@ -406,11 +408,11 @@ class RNSA(Base):
|
|
406
408
|
C = np.append(C, [_class_])
|
407
409
|
class_found = True
|
408
410
|
|
409
|
-
#
|
411
|
+
# If there is only one class and the sample is not classified, set the output as non-self.
|
410
412
|
if not class_found and len(self.classes) == 1:
|
411
413
|
C = np.append(C, [self.non_self_label])
|
412
|
-
#
|
413
|
-
#
|
414
|
+
# If the class is not identified with the detectors, assign the class with the greatest distance
|
415
|
+
# from the mean of its detectors.
|
414
416
|
elif not class_found:
|
415
417
|
average_distance: dict = {}
|
416
418
|
for _class_ in self.classes:
|
@@ -424,8 +426,40 @@ class RNSA(Base):
|
|
424
426
|
C = np.append(C, [max(average_distance, key=average_distance.get)])
|
425
427
|
return C
|
426
428
|
|
429
|
+
def __slice_index_list_by_class(self, y: npt.NDArray) -> dict:
|
430
|
+
"""
|
431
|
+
The function ``__slice_index_list_by_class(...)``, separates the indices of the lines according \
|
432
|
+
to the output class, to loop through the sample array, only in positions where the output is \
|
433
|
+
the class being trained.
|
434
|
+
|
435
|
+
Parameters:
|
436
|
+
---
|
437
|
+
* y (npt.NDArray): Receives a ``y``[``N sample``] array with the output classes of the \
|
438
|
+
``X`` sample array.
|
439
|
+
|
440
|
+
returns:
|
441
|
+
---
|
442
|
+
* dict: A dictionary with the list of array positions(``y``), with the classes as key.
|
443
|
+
|
444
|
+
---
|
445
|
+
|
446
|
+
A função ``__slice_index_list_by_class(...)``, separa os índices das linhas conforme a classe \
|
447
|
+
de saída, para percorrer o array de amostra, apenas nas posições que a saída for a classe que \
|
448
|
+
está sendo treinada.
|
449
|
+
|
450
|
+
Parameters:
|
451
|
+
---
|
452
|
+
* y (npt.NDArray): Recebe um array ``y``[``N amostra``] com as classes de saída do array \
|
453
|
+
de amostra ``X``.
|
454
|
+
|
455
|
+
Returns:
|
456
|
+
---
|
457
|
+
* dict: Um dicionário com a lista de posições do array(``y``), com as classes como chave.
|
458
|
+
"""
|
459
|
+
return slice_index_list_by_class(self.classes, y)
|
460
|
+
|
427
461
|
def __checks_valid_detector(self, X: npt.NDArray = None, vector_x: npt.NDArray = None,
|
428
|
-
|
462
|
+
samples_index_class: npt.NDArray = None):
|
429
463
|
"""
|
430
464
|
Function to check if the detector has a valid non-proper ``r`` radius for the class.
|
431
465
|
|
@@ -456,26 +490,25 @@ class RNSA(Base):
|
|
456
490
|
* Validade (``bool``): Retorna se o detector é válido ou não.
|
457
491
|
|
458
492
|
"""
|
459
|
-
#
|
493
|
+
# If any of the input arrays have zero size, returns false.
|
460
494
|
if (np.size(samples_index_class) == 0 or np.size(X) == 0 or np.size(vector_x) == 0):
|
461
495
|
return False
|
462
|
-
#
|
463
|
-
#
|
496
|
+
# If self.k > 1, uses the k nearest neighbors (kNN); otherwise, checks the detector
|
497
|
+
# without considering kNN.
|
464
498
|
if self.k > 1:
|
465
|
-
# Iniciar a lista dos knn vazia.
|
466
499
|
knn_list = np.empty(shape=0)
|
467
500
|
for i in samples_index_class:
|
468
|
-
#
|
469
|
-
#
|
501
|
+
# Calculates the distance between the two vectors and adds it to the kNN list if the
|
502
|
+
# distance is smaller than the largest distance in the list.
|
470
503
|
knn_list = self.__compare_KnearestNeighbors_List(
|
471
504
|
knn_list, self.__distance(X[i], vector_x)
|
472
505
|
)
|
473
|
-
#
|
506
|
+
# If the average of the distances in the kNN list is less than the radius, returns true.
|
474
507
|
distance_mean = np.mean(knn_list)
|
475
508
|
if self._algorithm == "V-detector":
|
476
509
|
return self.__detector_is_valid_to_Vdetector(distance_mean, vector_x)
|
477
510
|
elif distance_mean > (self.r + self.r_s):
|
478
|
-
return True
|
511
|
+
return True
|
479
512
|
else:
|
480
513
|
distance: Union[float, None] = None
|
481
514
|
for i in samples_index_class:
|
@@ -486,16 +519,16 @@ class RNSA(Base):
|
|
486
519
|
elif distance > new_distance:
|
487
520
|
distance = new_distance
|
488
521
|
else:
|
489
|
-
#
|
490
|
-
#
|
522
|
+
# Calculates the distance between the vectors; if it is less than or equal to the radius
|
523
|
+
# plus the sample's radius, sets the validity of the detector to false.
|
491
524
|
if (self.r + self.r_s) >= self.__distance(X[i], vector_x):
|
492
525
|
return False # Detector não é valido!
|
493
526
|
|
494
527
|
if self._algorithm == "V-detector":
|
495
528
|
return self.__detector_is_valid_to_Vdetector(distance, vector_x)
|
496
|
-
return True
|
529
|
+
return True
|
497
530
|
|
498
|
-
return False # Detector
|
531
|
+
return False # Detector is not valid!
|
499
532
|
|
500
533
|
def __compare_KnearestNeighbors_List(self, knn: npt.NDArray, distance: float) -> npt.NDArray:
|
501
534
|
"""
|
@@ -527,12 +560,12 @@ class RNSA(Base):
|
|
527
560
|
---
|
528
561
|
npt.NDArray: Lista de vizinhos mais próximos atualizada e ordenada.
|
529
562
|
"""
|
530
|
-
#
|
563
|
+
# If the number of distances in kNN is less than k, adds the distance.
|
531
564
|
if len(knn) < self.k:
|
532
565
|
knn = np.append(knn, distance)
|
533
566
|
knn.sort()
|
534
567
|
else:
|
535
|
-
#
|
568
|
+
# Otherwise, add the distance if the new distance is smaller than the largest distance in the list.
|
536
569
|
if knn[self.k - 1] > distance:
|
537
570
|
knn[self.k - 1] = distance
|
538
571
|
knn.sort()
|
@@ -576,16 +609,14 @@ class RNSA(Base):
|
|
576
609
|
a nenhuma classe.
|
577
610
|
"""
|
578
611
|
|
579
|
-
#
|
612
|
+
# List to store the classes and the average distance between the detectors and the sample.
|
580
613
|
possible_classes = []
|
581
614
|
for _class_ in self.classes:
|
582
|
-
#
|
615
|
+
# Variable to indicate if the class was found with the detectors.
|
583
616
|
class_found: bool = True
|
584
|
-
sum_distance = 0
|
617
|
+
sum_distance = 0
|
585
618
|
for detector in self.detectors[_class_]:
|
586
|
-
# Calcula a distância entre a amostra e os detectores.
|
587
619
|
distance = self.__distance(detector.position, line)
|
588
|
-
# Soma as distâncias para calcular a média.
|
589
620
|
sum_distance += distance
|
590
621
|
if self._algorithm == "V-detector":
|
591
622
|
if distance <= detector.radius:
|
@@ -595,17 +626,16 @@ class RNSA(Base):
|
|
595
626
|
class_found = False
|
596
627
|
break
|
597
628
|
|
598
|
-
#
|
599
|
-
# possível previsão.
|
629
|
+
# If the sample passes through all the detectors of a class, adds the class as a possible prediction.
|
600
630
|
if class_found:
|
601
631
|
possible_classes.append([_class_, sum_distance / self.N])
|
602
|
-
#
|
632
|
+
# If classified as belonging to only one class, returns the class.
|
603
633
|
if len(possible_classes) == 1:
|
604
634
|
return possible_classes[0][0]
|
605
|
-
#
|
635
|
+
# If belonging to more than one class, returns the class with the greatest average distance.
|
606
636
|
elif len(possible_classes) > 1:
|
607
637
|
return max(possible_classes, key=lambda x: x[1])[0]
|
608
|
-
else:
|
638
|
+
else:
|
609
639
|
return None
|
610
640
|
|
611
641
|
def __distance(self, u: npt.NDArray, v: npt.NDArray):
|
@@ -673,47 +703,15 @@ class RNSA(Base):
|
|
673
703
|
"""
|
674
704
|
new_detector_r = float(distance - self.r_s)
|
675
705
|
if self.r >= new_detector_r:
|
676
|
-
return False
|
706
|
+
return False
|
677
707
|
else:
|
678
|
-
#
|
708
|
+
# If _cell_bounds is True, considers the detector to be within the plane bounds.
|
679
709
|
if self._cell_bounds:
|
680
710
|
for p in vector_x:
|
681
711
|
if (p - new_detector_r) < 0 or (p + new_detector_r) > 1:
|
682
712
|
return False
|
683
713
|
return True, new_detector_r
|
684
714
|
|
685
|
-
def __slice_index_list_by_class(self, y: npt.NDArray) -> dict:
|
686
|
-
"""
|
687
|
-
The function ``__slice_index_list_by_class(...)``, separates the indices of the lines \
|
688
|
-
according to the output class, to loop through the sample array, only in positions where \
|
689
|
-
the output is the class being trained.
|
690
|
-
|
691
|
-
Parameters:
|
692
|
-
---
|
693
|
-
* y (npt.NDArray): Receives a ``y``[``N sample``] array with the output classes of the \
|
694
|
-
``X`` sample array.
|
695
|
-
|
696
|
-
returns:
|
697
|
-
---
|
698
|
-
* dict: A dictionary with the list of array positions(``y``), with the classes as key.
|
699
|
-
|
700
|
-
---
|
701
|
-
|
702
|
-
A função ``__slice_index_list_by_class(...)``, separa os índices das linhas conforme a classe \
|
703
|
-
de saída, para percorrer o array de amostra, apenas nas posições que a saída for a classe que \
|
704
|
-
está sendo treinada.
|
705
|
-
|
706
|
-
Parameters:
|
707
|
-
---
|
708
|
-
* y (npt.NDArray): Recebe um array ``y``[``N amostra``] com as classes de saída do array \
|
709
|
-
de amostra ``X``.
|
710
|
-
|
711
|
-
Returns:
|
712
|
-
---
|
713
|
-
* dict: Um dicionário com a lista de posições do array(``y``), com as classes como chave.
|
714
|
-
"""
|
715
|
-
return super()._slice_index_list_by_class(y)
|
716
|
-
|
717
715
|
def score(self, X: npt.NDArray, y: list) -> float:
|
718
716
|
"""
|
719
717
|
Score function calculates forecast accuracy.
|
@@ -931,41 +929,41 @@ class BNSA(Base):
|
|
931
929
|
"""
|
932
930
|
super()._check_and_raise_exceptions_fit(X, y, "BNSA")
|
933
931
|
|
934
|
-
#
|
932
|
+
# Converts the entire array X to boolean
|
935
933
|
if X.dtype != bool:
|
936
934
|
X = X.astype(bool)
|
937
935
|
|
938
|
-
#
|
936
|
+
# Identifying the possible classes within the output array `y`.
|
939
937
|
self.classes = np.unique(y)
|
940
|
-
#
|
938
|
+
# Dictionary that will store detectors with classes as keys.
|
941
939
|
list_detectors_by_class = dict()
|
942
|
-
#
|
940
|
+
# Separates the classes for training.
|
943
941
|
sample_index: dict = self.__slice_index_list_by_class(y)
|
944
|
-
#
|
942
|
+
# Progress bar for generating all detectors.
|
945
943
|
if verbose:
|
946
944
|
progress = tqdm(total=int(self.N * (len(self.classes))),
|
947
945
|
bar_format='{desc} ┇{bar}┇ {n}/{total} detectors', postfix='\n')
|
948
946
|
|
949
947
|
for _class_ in self.classes:
|
950
|
-
#
|
948
|
+
# Initializes the empty set that will contain the valid detectors.
|
951
949
|
valid_detectors_set: list = []
|
952
950
|
discard_count: int = 0
|
953
|
-
#
|
951
|
+
# Updating the progress bar with the current class the algorithm is processing.
|
954
952
|
if verbose:
|
955
953
|
progress.set_description_str(
|
956
954
|
f"Generating the detectors for the {_class_} class:")
|
957
955
|
while len(valid_detectors_set) < self.N:
|
958
956
|
|
959
957
|
is_valid_detector: bool = True
|
960
|
-
#
|
958
|
+
# Generates a candidate detector vector randomly with values 0 and 1.
|
961
959
|
vector_x = np.random.choice([False, True], size=X.shape[1])
|
962
|
-
#
|
963
|
-
distances = cdist(np.expand_dims(vector_x, axis=0),
|
960
|
+
# Calculates the distance between the candidate and the class samples.
|
961
|
+
distances = cdist(np.expand_dims(vector_x, axis=0),
|
964
962
|
X[sample_index[_class_]], metric='hamming')
|
965
|
-
#
|
963
|
+
# Checks if any of the distances is below or equal to the threshold.
|
966
964
|
is_valid_detector = not np.any(distances <= self.aff_thresh)
|
967
965
|
|
968
|
-
#
|
966
|
+
# If the detector is valid, add it to the list of valid detectors.
|
969
967
|
if is_valid_detector:
|
970
968
|
discard_count = 0
|
971
969
|
valid_detectors_set.append(vector_x)
|
@@ -981,15 +979,15 @@ class BNSA(Base):
|
|
981
979
|
"radius and consider reducing its value."
|
982
980
|
)
|
983
981
|
|
984
|
-
#
|
982
|
+
# Add detectors to the dictionary with classes as keys.
|
985
983
|
list_detectors_by_class[_class_] = valid_detectors_set
|
986
984
|
|
987
|
-
#
|
985
|
+
# Notify the completion of detector generation for the classes.
|
988
986
|
if verbose:
|
989
987
|
progress.set_description(
|
990
988
|
f'\033[92m✔ Non-self detectors for classes ({", ".join(map(str, self.classes))}) '
|
991
989
|
f'successfully generated\033[0m')
|
992
|
-
#
|
990
|
+
# Saves the found detectors in the attribute for the class detectors.
|
993
991
|
self.detectors = list_detectors_by_class
|
994
992
|
return self
|
995
993
|
|
@@ -1025,7 +1023,7 @@ class BNSA(Base):
|
|
1025
1023
|
contendo as classes previstas para ``X``.
|
1026
1024
|
* ``None``: Se não existir detectores para a previsão.
|
1027
1025
|
"""
|
1028
|
-
#
|
1026
|
+
# If there are no detectors, returns None.
|
1029
1027
|
if self.detectors is None:
|
1030
1028
|
return None
|
1031
1029
|
elif not isinstance(X, (np.ndarray, list)):
|
@@ -1036,45 +1034,42 @@ class BNSA(Base):
|
|
1036
1034
|
len(self.detectors[self.classes[0]][0])
|
1037
1035
|
)
|
1038
1036
|
)
|
1039
|
-
#
|
1037
|
+
# Checks if matrix X contains only binary samples. Otherwise, raises an exception.
|
1040
1038
|
if not np.isin(X, [0, 1]).all():
|
1041
1039
|
raise ValueError(
|
1042
1040
|
"The array X contains values that are not composed only of 0 and 1."
|
1043
1041
|
)
|
1044
1042
|
|
1045
|
-
#
|
1043
|
+
# Converts the entire array X to boolean.
|
1046
1044
|
if X.dtype != bool:
|
1047
1045
|
X = X.astype(bool)
|
1048
1046
|
|
1049
|
-
#
|
1047
|
+
# Initializes an empty array that will store the predictions.
|
1050
1048
|
C = np.empty(shape=0)
|
1051
|
-
#
|
1049
|
+
# For each sample row in X.
|
1052
1050
|
for line in X:
|
1053
1051
|
class_found: bool = True
|
1054
|
-
#
|
1055
|
-
#
|
1052
|
+
# List to store the possible classes to which the sample matches with self
|
1053
|
+
# when compared to the non-self detectors.
|
1056
1054
|
possible_classes: list = []
|
1057
1055
|
for _class_ in self.classes:
|
1058
|
-
# Lista para armazenar as taxas de similaridade entre a amostra e os detectores.
|
1059
1056
|
similarity_sum: float = 0
|
1060
|
-
|
1061
|
-
# Calcula a distância de Hamming entre a linha e todos os detectores
|
1057
|
+
# Calculates the Hamming distance between the row and all detectors.
|
1062
1058
|
distances = cdist(np.expand_dims(line, axis=0),
|
1063
1059
|
self.detectors[_class_], metric='hamming')
|
1064
1060
|
|
1065
|
-
#
|
1061
|
+
# Check if any distance is below or equal to the threshold.
|
1066
1062
|
if np.any(distances <= self.aff_thresh):
|
1067
1063
|
class_found = False
|
1068
1064
|
else:
|
1069
|
-
# Somar todas as distâncias
|
1070
1065
|
similarity_sum = np.sum(distances)
|
1071
1066
|
|
1072
|
-
#
|
1073
|
-
#
|
1067
|
+
# If the sample passes through all detectors of a class, adds the class as a possible prediction
|
1068
|
+
# and its average similarity.
|
1074
1069
|
if class_found:
|
1075
1070
|
possible_classes.append([_class_, similarity_sum / self.N])
|
1076
1071
|
|
1077
|
-
#
|
1072
|
+
# If belonging to one or more classes, adds the class with the greatest average distance.
|
1078
1073
|
if len(possible_classes) > 0:
|
1079
1074
|
C = np.append(
|
1080
1075
|
C, [max(possible_classes, key=lambda x: x[1])[0]])
|
@@ -1082,24 +1077,24 @@ class BNSA(Base):
|
|
1082
1077
|
else:
|
1083
1078
|
class_found = False
|
1084
1079
|
|
1085
|
-
#
|
1080
|
+
# If there is only one class and the sample is not classified, sets the output as non-self.
|
1086
1081
|
if not class_found and len(self.classes) == 1:
|
1087
1082
|
C = np.append(C, ["non-self"])
|
1088
|
-
#
|
1083
|
+
# If the class cannot be identified by the detectors
|
1089
1084
|
elif not class_found:
|
1090
1085
|
class_differences: dict = {}
|
1091
1086
|
for _class_ in self.classes:
|
1092
|
-
#
|
1087
|
+
# Assign the label to the class with the greatest distance from the nearest detector.
|
1093
1088
|
if self.no_label_sample_selection == 'nearest_difference':
|
1094
|
-
difference_min: float = cdist(
|
1095
|
-
|
1096
|
-
|
1089
|
+
difference_min: float = cdist(np.expand_dims(line, axis=0),
|
1090
|
+
self.detectors[_class_], metric='hamming'
|
1091
|
+
).min()
|
1097
1092
|
class_differences[_class_] = difference_min
|
1098
|
-
#
|
1093
|
+
# Or based on the greatest distance from the average distances of the detectors.
|
1099
1094
|
else:
|
1100
|
-
difference_sum: float = cdist(
|
1101
|
-
|
1102
|
-
|
1095
|
+
difference_sum: float = cdist(np.expand_dims(line, axis=0),
|
1096
|
+
self.detectors[_class_], metric='hamming'
|
1097
|
+
).sum()
|
1103
1098
|
class_differences[_class_] = difference_sum / self.N
|
1104
1099
|
|
1105
1100
|
C = np.append(C, [max(class_differences, key=class_differences.get)])
|
@@ -1136,7 +1131,7 @@ class BNSA(Base):
|
|
1136
1131
|
---
|
1137
1132
|
* dict: Um dicionário com a lista de posições do array(``y``), com as classes como chave.
|
1138
1133
|
"""
|
1139
|
-
return
|
1134
|
+
return slice_index_list_by_class(self.classes, y)
|
1140
1135
|
|
1141
1136
|
def score(self, X: npt.NDArray, y: list) -> float:
|
1142
1137
|
"""
|
aisp/utils/__init__.py
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
from typing import Union
|
2
|
+
import numpy as np
|
3
|
+
import numpy.typing as npt
|
4
|
+
|
5
|
+
|
6
|
+
def slice_index_list_by_class(classes: Union[npt.NDArray, list], y: npt.NDArray) -> dict:
|
7
|
+
"""
|
8
|
+
The function ``__slice_index_list_by_class(...)``, separates the indices of the lines \
|
9
|
+
according to the output class, to loop through the sample array, only in positions where \
|
10
|
+
the output is the class being trained.
|
11
|
+
|
12
|
+
Parameters:
|
13
|
+
---
|
14
|
+
* classes (``list or npt.NDArray``): list with unique classes.
|
15
|
+
* y (npt.NDArray): Receives a ``y``[``N sample``] array with the output classes of the \
|
16
|
+
``X`` sample array.
|
17
|
+
|
18
|
+
returns:
|
19
|
+
---
|
20
|
+
* dict: A dictionary with the list of array positions(``y``), with the classes as key.
|
21
|
+
|
22
|
+
---
|
23
|
+
|
24
|
+
A função ``__slice_index_list_by_class(...)``, separa os índices das linhas conforme a \
|
25
|
+
classe de saída, para percorrer o array de amostra, apenas nas posições que a saída for \
|
26
|
+
a classe que está sendo treinada.
|
27
|
+
|
28
|
+
Parameters:
|
29
|
+
---
|
30
|
+
* classes (``list or npt.NDArray``): lista com classes únicas.
|
31
|
+
* y (npt.NDArray): Recebe um array ``y``[``N amostra``] com as classes de saída do \
|
32
|
+
array de amostra ``X``.
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
---
|
36
|
+
* dict: Um dicionário com a lista de posições do array(``y``), com as classes como chave.
|
37
|
+
"""
|
38
|
+
position_samples = dict()
|
39
|
+
for _class_ in classes:
|
40
|
+
# Gets the sample positions by class from y.
|
41
|
+
position_samples[_class_] = list(np.nonzero(y == _class_)[0])
|
42
|
+
|
43
|
+
return position_samples
|
aisp/utils/metrics.py
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
from typing import Union
|
2
|
+
import numpy as np
|
3
|
+
import numpy.typing as npt
|
4
|
+
|
5
|
+
|
6
|
+
def accuracy_score(
|
7
|
+
y_true: Union[npt.NDArray, list],
|
8
|
+
y_pred: Union[npt.NDArray, list]
|
9
|
+
) -> float:
|
10
|
+
"""
|
11
|
+
Function to calculate precision accuracy based on lists of true labels and
|
12
|
+
predicted labels.
|
13
|
+
|
14
|
+
Parameters:
|
15
|
+
---
|
16
|
+
|
17
|
+
* y_true (``Union[npt.NDArray, list]``): Ground truth (correct) labels. \
|
18
|
+
Expected to be of the same length as `y_pred`.
|
19
|
+
* y_pred (``Union[npt.NDArray, list]``): Predicted labels. Expected to \
|
20
|
+
be of the same length as `y_true`.
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
---
|
24
|
+
* Accuracy (``float``): The ratio of correct predictions to the total \
|
25
|
+
number of predictions.
|
26
|
+
|
27
|
+
Raises:
|
28
|
+
---
|
29
|
+
* ValueError: If `y_true` or `y_pred` are empty or if they do not have the same length.
|
30
|
+
|
31
|
+
---
|
32
|
+
|
33
|
+
Função para calcular a acurácia de precisão com base em listas de rótulos
|
34
|
+
verdadeiros e nos rótulos previstos.
|
35
|
+
|
36
|
+
Parâmetros:
|
37
|
+
---
|
38
|
+
* y_true (``Union[npt.NDArray, list]``): Rótulos verdadeiros (corretos)..
|
39
|
+
* y_pred (``Union[npt.NDArray, list]``): Rótulos previstos.
|
40
|
+
|
41
|
+
Retornos:
|
42
|
+
---
|
43
|
+
* Precisão (``float``): A proporção de previsões corretas em relação
|
44
|
+
ao número total de previsões.
|
45
|
+
|
46
|
+
Lança:
|
47
|
+
---
|
48
|
+
* ValueError: Se `y_true` ou `y_pred` estiverem vazios ou se não
|
49
|
+
tiverem o mesmo tamanho.
|
50
|
+
"""
|
51
|
+
n = len(y_true)
|
52
|
+
if n == 0:
|
53
|
+
raise ValueError(
|
54
|
+
"Division by zero: y_true cannot be an empty list or array."
|
55
|
+
)
|
56
|
+
elif n != len(y_pred):
|
57
|
+
raise ValueError(
|
58
|
+
f"Error: The arrays must have the same size. Size of y_true: "
|
59
|
+
f"{len(y_true)}, Size of y_pred: {len(y_pred)}"
|
60
|
+
)
|
61
|
+
return np.sum(np.sum(np.array(y_true) == np.array(y_pred))) / n
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: aisp
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.33
|
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>
|
@@ -63,7 +63,6 @@ Requires-Dist: tqdm >=4.64.1
|
|
63
63
|
> 2. [Installation.](#installation)
|
64
64
|
> 1. [Dependencies](#dependencies)
|
65
65
|
> 2. [User installation](#user-installation)
|
66
|
-
> 3. [How to import the Techniques](#how-to-import-the-techniques)
|
67
66
|
> 3. [Examples.](#examples)
|
68
67
|
|
69
68
|
---
|
@@ -81,8 +80,8 @@ Artificial Immune Systems (AIS) are inspired by the vertebrate immune system, cr
|
|
81
80
|
##### Algorithms implemented:
|
82
81
|
|
83
82
|
> - [x] [**Negative Selection.**](https://ais-package.github.io/docs/aisp-techniques/Negative%20Selection/)
|
83
|
+
> - [ ] *Clonal Selection Algorithms.*
|
84
84
|
> - [ ] *Dendritic Cells.*
|
85
|
-
> - [ ] *Clonalg.*
|
86
85
|
> - [ ] *Immune Network Theory.*
|
87
86
|
|
88
87
|
</section>
|
@@ -119,17 +118,7 @@ pip install aisp
|
|
119
118
|
```
|
120
119
|
|
121
120
|
</section>
|
122
|
-
<section id='how-to-import-the-techniques'>
|
123
121
|
|
124
|
-
##### **How to import the Techniques**
|
125
|
-
|
126
|
-
``` Python
|
127
|
-
from aisp.NSA import RNSA
|
128
|
-
|
129
|
-
nsa = RNSA(N=300, r=0.05)
|
130
|
-
```
|
131
|
-
|
132
|
-
</section>
|
133
122
|
</section>
|
134
123
|
<section id='examples'>
|
135
124
|
|
@@ -173,7 +162,6 @@ Below are some examples that use a database for classification with the [Jupyter
|
|
173
162
|
> 2. [Instalação.](#instalação)
|
174
163
|
> 1. [Dependências](#dependências)
|
175
164
|
> 2. [Instalação do usuário](#instalação-do-usuário)
|
176
|
-
> 3. [Como importar as Tecnicas](#como-importar-as-tecnicas)
|
177
165
|
> 3. [Exemplos.](#exemplos)
|
178
166
|
|
179
167
|
---
|
@@ -190,8 +178,8 @@ Os sistemas imunológicos artificiais (SIA) inspiram-se no sistema imunológico
|
|
190
178
|
##### Algoritmos implementados:
|
191
179
|
|
192
180
|
> - [x] [**Seleção Negativa.**](https://ais-package.github.io/docs/aisp-techniques/Negative%20Selection/)
|
181
|
+
> - [ ] *Algoritmos de Seleção Clonal.*
|
193
182
|
> - [ ] *Células Dendríticas.*
|
194
|
-
> - [ ] *Clonalg.*
|
195
183
|
> - [ ] *Teoria da Rede Imune.*
|
196
184
|
|
197
185
|
</section>
|
@@ -229,17 +217,6 @@ pip install aisp
|
|
229
217
|
|
230
218
|
</section>
|
231
219
|
|
232
|
-
<section id='como-importar-as-tecnicas'>
|
233
|
-
|
234
|
-
##### **Como importar as Tecnicas**
|
235
|
-
|
236
|
-
``` Python
|
237
|
-
from aisp.NSA import RNSA
|
238
|
-
|
239
|
-
nsa = RNSA(N=300, r=0.05)
|
240
|
-
```
|
241
|
-
|
242
|
-
</section>
|
243
220
|
</section>
|
244
221
|
<section id='exemplos'>
|
245
222
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
aisp/NSA/__init__.py,sha256=b0KSfocc9p4s4AQatyEBDfQuVmMqZ18rCieTUpWnQ7c,143
|
2
|
+
aisp/NSA/_base.py,sha256=iqmsLoM7yBC_EPFZXvdsQjI1bidz1A87ZFyht0P--fI,10861
|
3
|
+
aisp/NSA/_negative_selection.py,sha256=AqsOJYkZlb2kP48b35FFIwU_fGedLpZw5JY9hpnzkXM,54897
|
4
|
+
aisp/utils/__init__.py,sha256=c8OCaR9IAQ21vpxWqtLplB84W_UKdWceI--yw7sBMi0,163
|
5
|
+
aisp/utils/_multiclass.py,sha256=aoxSqnemNjs5uJAk7a1GumNDa-q-LOw6TWluwQ10tR4,1597
|
6
|
+
aisp/utils/metrics.py,sha256=xp9A52-QsP0cJGIlYprrI7BrVwQN6gqCLINVJumlIDI,1908
|
7
|
+
aisp-0.1.33.dist-info/LICENSE,sha256=fTqV5eBpeAZO0_jit8j4Ref9ikBSlHJ8xwj5TLg7gFk,7817
|
8
|
+
aisp-0.1.33.dist-info/METADATA,sha256=M2czdHGPXbXXVOKUckfYMwwPpnj_Lt49dU5PRfLd_jw,7562
|
9
|
+
aisp-0.1.33.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
10
|
+
aisp-0.1.33.dist-info/top_level.txt,sha256=Q5aJi_rAVT5UNS1As0ZafoyS5dwNibnoyOYV7RWUB9s,5
|
11
|
+
aisp-0.1.33.dist-info/RECORD,,
|
aisp-0.1.32.dist-info/RECORD
DELETED
@@ -1,8 +0,0 @@
|
|
1
|
-
aisp/NSA/__init__.py,sha256=cmHx6ydTfhFAOfgCjDcKDJRfH5Sz41LHqVRlDZgVL58,141
|
2
|
-
aisp/NSA/_base.py,sha256=HjdF4ZnophMY_Ooj2ERjhVoWwqe8Q4rPQ-UxHlfIeNY,9557
|
3
|
-
aisp/NSA/_negative_selection.py,sha256=bYbkorRTY3V7_gS0L1QnYxpoy-KGsGDxwhza0RjhcKM,55330
|
4
|
-
aisp-0.1.32.dist-info/LICENSE,sha256=fTqV5eBpeAZO0_jit8j4Ref9ikBSlHJ8xwj5TLg7gFk,7817
|
5
|
-
aisp-0.1.32.dist-info/METADATA,sha256=0SxlcxhAKPH1XSkIENEp2zIxu_YKvq-yqSLu-dISNsI,7999
|
6
|
-
aisp-0.1.32.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
7
|
-
aisp-0.1.32.dist-info/top_level.txt,sha256=Q5aJi_rAVT5UNS1As0ZafoyS5dwNibnoyOYV7RWUB9s,5
|
8
|
-
aisp-0.1.32.dist-info/RECORD,,
|
File without changes
|
File without changes
|