aisp 0.1.32__py3-none-any.whl → 0.1.34__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 CHANGED
@@ -1,5 +1,18 @@
1
+ """Module (NSA) Negative Selection Algorithm
2
+
3
+ NSAs simulate the maturation process of T-cells in the immune system, where these \
4
+ cells learn to distinguish between self and non-self. Only T-cells capable \
5
+ of recognizing non-self elements are preserved.
6
+
7
+ ----
8
+
9
+ Os NSAs simulam o processo de maturação das células-T no sistema imunológico, onde \
10
+ essas células aprendem a distinguir entre o próprio e não-próprio.
11
+ Apenas as células-T capazes de reconhecer elementos não-próprios são preservadas.
12
+
13
+ """
1
14
  from ._negative_selection import BNSA, RNSA
2
15
 
3
16
  __author__ = "João Paulo da Silva Barros"
4
17
  __all__ = ["RNSA", "BNSA"]
5
- __version__ = "0.1.31"
18
+ __version__ = "0.1.34"
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
- def _check_and_raise_exceptions_fit(self, X: npt.NDArray = None, y: npt.NDArray = None,
97
- _class_: Literal["RNSA", "BNSA"] = "RNSA",
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,44 +156,7 @@ 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
- def _score(self, X: npt.NDArray, y: list) -> float:
159
+ def score(self, X: npt.NDArray, y: list) -> float:
190
160
  """
191
161
  Score function calculates forecast accuracy.
192
162
 
@@ -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 np.sum(y == y_pred) / len(y)
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
@@ -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):
@@ -88,10 +89,18 @@ class RNSA(Base):
88
89
  * classes (``npt.NDArray``): lista com as classes de saída.
89
90
  """
90
91
 
91
- def __init__(self, N: int = 100, r: float = 0.05, r_s: float = 0.0001, k: int = 1,
92
- metric: Literal["manhattan", "minkowski", "euclidean"] = "euclidean", max_discards: int = 1000,
93
- seed: int = None, algorithm: Literal["default-NSA", "V-detector"] = "default-NSA",
94
- **kwargs: Dict[str, Union[bool, str, float]]):
92
+ def __init__(
93
+ self,
94
+ N: int = 100,
95
+ r: float = 0.05,
96
+ r_s: float = 0.0001,
97
+ k: int = 1,
98
+ metric: Literal["manhattan", "minkowski", "euclidean"] = "euclidean",
99
+ max_discards: int = 1000,
100
+ seed: int = None,
101
+ algorithm: Literal["default-NSA", "V-detector"] = "default-NSA",
102
+ **kwargs: Dict[str, Union[bool, str, float]],
103
+ ):
95
104
  """
96
105
  Negative Selection class constructor (``RNSA``).
97
106
 
@@ -236,7 +245,7 @@ class RNSA(Base):
236
245
  self.r_s: float = 0
237
246
 
238
247
  if algorithm == "V-detector":
239
- self._Detector = namedtuple("Detector", "position radius")
248
+ self._Detector = namedtuple("Detector", ["position", "radius"])
240
249
  self._algorithm: str = algorithm
241
250
  else:
242
251
  self._Detector = namedtuple("Detector", "position")
@@ -247,12 +256,12 @@ class RNSA(Base):
247
256
  else:
248
257
  self.max_discards: int = 1000
249
258
 
250
- # Obtém as variáveis do kwargs.
259
+ # Retrieves the variables from kwargs.
251
260
  self.p: float = kwargs.get("p", 2)
252
261
  self._cell_bounds: bool = kwargs.get("cell_bounds", False)
253
262
  self.non_self_label: str = kwargs.get("non_self_label", "non-self")
254
263
 
255
- # Inicializa as demais variáveis da classe como None
264
+ # Initializes the other class variables as None.
256
265
  self.detectors: Union[dict, None] = None
257
266
  self.classes: npt.NDArray = None
258
267
 
@@ -286,36 +295,37 @@ class RNSA(Base):
286
295
  ---
287
296
  (``self``): Retorna a própria instância.
288
297
  """
298
+ progress = None
289
299
  super()._check_and_raise_exceptions_fit(X, y)
290
300
 
291
- # Identificando as classes possíveis, dentro do array de saídas ``y``.
301
+ # Identifying the possible classes within the output array `y`.
292
302
  self.classes = np.unique(y)
293
- # Dict que armazenará os detectores com as classes como key.
303
+ # Dictionary that will store detectors with classes as keys.
294
304
  list_detectors_by_class = dict()
295
- # Separa as classes para o treinamento.
305
+ # Separates the classes for training.
296
306
  sample_index = self.__slice_index_list_by_class(y)
297
- # Barra de progresso para a geração de todos os detectores.
307
+ # Progress bar for generating all detectors.
298
308
  if verbose:
299
309
  progress = tqdm(total=int(self.N * (len(self.classes))),
300
- bar_format="{desc} ┇{bar}┇ {n}/{total} detectors", postfix="\n",)
310
+ bar_format="{desc} ┇{bar}┇ {n}/{total} detectors", postfix="\n", )
301
311
  for _class_ in self.classes:
302
- # Inicia o conjunto vazio que conterá os detectores válidos.
312
+ # Initializes the empty set that will contain the valid detectors.
303
313
  valid_detectors_set = []
304
314
  discard_count = 0
305
- # Informando em qual classe o algoritmo está para a barra de progresso.
315
+ # Indicating which class the algorithm is currently processing for the progress bar.
306
316
  if verbose:
307
317
  progress.set_description_str(
308
318
  f"Generating the detectors for the {_class_} class:"
309
319
  )
310
320
  while len(valid_detectors_set) < self.N:
311
- # Gera um vetor candidato a detector aleatoriamente com valores entre 0 e 1.
321
+ # Generates a candidate detector vector randomly with values between 0 and 1.
312
322
  vector_x = np.random.random_sample(size=X.shape[1])
313
- # Verifica a validade do detector para o não-próprio com relação às amostras da classe.
323
+ # Checks the validity of the detector for non-self with respect to the class samples.
314
324
  valid_detector = self.__checks_valid_detector(
315
325
  X=X, vector_x=vector_x, samples_index_class=sample_index[_class_]
316
326
  )
317
327
 
318
- # Se o detector for válido, adicione a lista dos válidos.
328
+ # If the detector is valid, add it to the list of valid detectors.
319
329
  if self._algorithm == "V-detector" and valid_detector is not False:
320
330
  discard_count = 0
321
331
  valid_detectors_set.append(
@@ -338,15 +348,15 @@ class RNSA(Base):
338
348
  "consider reducing its value."
339
349
  )
340
350
 
341
- # Adicionar detectores, com as classes como chave na dict.
351
+ # Add detectors, with classes as keys in the dictionary.
342
352
  list_detectors_by_class[_class_] = valid_detectors_set
343
- # Informar a finalização da geração dos detectores para as classes.
353
+ # Notify completion of detector generation for the classes.
344
354
  if verbose:
345
355
  progress.set_description(
346
356
  f'\033[92m✔ Non-self detectors for classes ({", ".join(map(str, self.classes))}) '
347
357
  f'successfully generated\033[0m'
348
358
  )
349
- # Armazena os detectores encontrados no atributo, para os detectores da classe.
359
+ # Saves the found detectors in the attribute for the non-self detectors of the trained model.
350
360
  self.detectors = list_detectors_by_class
351
361
  return self
352
362
 
@@ -382,7 +392,7 @@ class RNSA(Base):
382
392
  contendo as classes previstas para ``X``.
383
393
  * ``None``: Se não existir detectores para a previsão.
384
394
  """
385
- # se não houver detectores retorna None.
395
+ # If there are no detectors, returns None.
386
396
  if self.detectors is None:
387
397
  return None
388
398
  elif not isinstance(X, (np.ndarray, list)):
@@ -394,9 +404,9 @@ class RNSA(Base):
394
404
  )
395
405
  )
396
406
 
397
- # Inicia um array vazio.
407
+ # Initializes an empty array that will store the predictions.
398
408
  C = np.empty(shape=0)
399
- # Para cada linha de amostra em X.
409
+ # For each sample row in X.
400
410
  for line in X:
401
411
  class_found: bool
402
412
  _class_ = self.__compare_sample_to_detectors(line)
@@ -406,11 +416,11 @@ class RNSA(Base):
406
416
  C = np.append(C, [_class_])
407
417
  class_found = True
408
418
 
409
- # Se possuir apenas uma classe e não classificar a amostra define a saída como não-própria.
419
+ # If there is only one class and the sample is not classified, set the output as non-self.
410
420
  if not class_found and len(self.classes) == 1:
411
421
  C = np.append(C, [self.non_self_label])
412
- # Se não identificar a classe com os detectores, coloca a classe com a maior distância
413
- # da média dos seus detectores.
422
+ # If the class is not identified with the detectors, assign the class with the greatest distance
423
+ # from the mean of its detectors.
414
424
  elif not class_found:
415
425
  average_distance: dict = {}
416
426
  for _class_ in self.classes:
@@ -424,8 +434,44 @@ class RNSA(Base):
424
434
  C = np.append(C, [max(average_distance, key=average_distance.get)])
425
435
  return C
426
436
 
427
- def __checks_valid_detector(self, X: npt.NDArray = None, vector_x: npt.NDArray = None,
428
- samples_index_class: npt.NDArray = None):
437
+ def __slice_index_list_by_class(self, y: npt.NDArray) -> dict:
438
+ """
439
+ The function ``__slice_index_list_by_class(...)``, separates the indices of the lines according \
440
+ to the output class, to loop through the sample array, only in positions where the output is \
441
+ the class being trained.
442
+
443
+ Parameters:
444
+ ---
445
+ * y (npt.NDArray): Receives a ``y``[``N sample``] array with the output classes of the \
446
+ ``X`` sample array.
447
+
448
+ returns:
449
+ ---
450
+ * dict: A dictionary with the list of array positions(``y``), with the classes as key.
451
+
452
+ ---
453
+
454
+ A função ``__slice_index_list_by_class(...)``, separa os índices das linhas conforme a classe \
455
+ de saída, para percorrer o array de amostra, apenas nas posições que a saída for a classe que \
456
+ está sendo treinada.
457
+
458
+ Parameters:
459
+ ---
460
+ * y (npt.NDArray): Recebe um array ``y``[``N amostra``] com as classes de saída do array \
461
+ de amostra ``X``.
462
+
463
+ Returns:
464
+ ---
465
+ * dict: Um dicionário com a lista de posições do array(``y``), com as classes como chave.
466
+ """
467
+ return slice_index_list_by_class(self.classes, y)
468
+
469
+ def __checks_valid_detector(
470
+ self,
471
+ X: npt.NDArray = None,
472
+ vector_x: npt.NDArray = None,
473
+ samples_index_class: npt.NDArray = None,
474
+ ):
429
475
  """
430
476
  Function to check if the detector has a valid non-proper ``r`` radius for the class.
431
477
 
@@ -456,26 +502,25 @@ class RNSA(Base):
456
502
  * Validade (``bool``): Retorna se o detector é válido ou não.
457
503
 
458
504
  """
459
- # Se um ou mais array de entrada possuir zero dados, retorna falso.
505
+ # If any of the input arrays have zero size, returns false.
460
506
  if (np.size(samples_index_class) == 0 or np.size(X) == 0 or np.size(vector_x) == 0):
461
507
  return False
462
- # se self.k > 1 utiliza os k vizinhos mais próximos (knn), se não verifica o detector sem
463
- # considerar os knn.
508
+ # If self.k > 1, uses the k nearest neighbors (kNN); otherwise, checks the detector
509
+ # without considering kNN.
464
510
  if self.k > 1:
465
- # Iniciar a lista dos knn vazia.
466
511
  knn_list = np.empty(shape=0)
467
512
  for i in samples_index_class:
468
- # Calcula a distância entre os dois vetores e adiciona a lista dos knn, se a
469
- # distância for menor que a maior da lista.
513
+ # Calculates the distance between the two vectors and adds it to the kNN list if the
514
+ # distance is smaller than the largest distance in the list.
470
515
  knn_list = self.__compare_KnearestNeighbors_List(
471
516
  knn_list, self.__distance(X[i], vector_x)
472
517
  )
473
- # Se a média das distâncias na lista dos knn, for menor que o raio, retorna verdadeiro.
518
+ # If the average of the distances in the kNN list is less than the radius, returns true.
474
519
  distance_mean = np.mean(knn_list)
475
520
  if self._algorithm == "V-detector":
476
521
  return self.__detector_is_valid_to_Vdetector(distance_mean, vector_x)
477
522
  elif distance_mean > (self.r + self.r_s):
478
- return True # Detector é valido!
523
+ return True
479
524
  else:
480
525
  distance: Union[float, None] = None
481
526
  for i in samples_index_class:
@@ -486,16 +531,16 @@ class RNSA(Base):
486
531
  elif distance > new_distance:
487
532
  distance = new_distance
488
533
  else:
489
- # Calcula a distância entre os vetores, se menor ou igual ao raio + raio da
490
- # amostra define a validade do detector como falso.
534
+ # Calculates the distance between the vectors; if it is less than or equal to the radius
535
+ # plus the sample's radius, sets the validity of the detector to false.
491
536
  if (self.r + self.r_s) >= self.__distance(X[i], vector_x):
492
537
  return False # Detector não é valido!
493
538
 
494
539
  if self._algorithm == "V-detector":
495
540
  return self.__detector_is_valid_to_Vdetector(distance, vector_x)
496
- return True # Detector é valido!
541
+ return True
497
542
 
498
- return False # Detector não é valido!
543
+ return False # Detector is not valid!
499
544
 
500
545
  def __compare_KnearestNeighbors_List(self, knn: npt.NDArray, distance: float) -> npt.NDArray:
501
546
  """
@@ -527,12 +572,12 @@ class RNSA(Base):
527
572
  ---
528
573
  npt.NDArray: Lista de vizinhos mais próximos atualizada e ordenada.
529
574
  """
530
- # Se a quantidade de distâncias em knn, for menor que k, adiciona a distância.
575
+ # If the number of distances in kNN is less than k, adds the distance.
531
576
  if len(knn) < self.k:
532
577
  knn = np.append(knn, distance)
533
578
  knn.sort()
534
579
  else:
535
- # Se não, adicione a distância, se a nova distancia for menor que a maior distância da lista.
580
+ # Otherwise, add the distance if the new distance is smaller than the largest distance in the list.
536
581
  if knn[self.k - 1] > distance:
537
582
  knn[self.k - 1] = distance
538
583
  knn.sort()
@@ -576,16 +621,14 @@ class RNSA(Base):
576
621
  a nenhuma classe.
577
622
  """
578
623
 
579
- # Lista para armazenar as classes e a distância média entre os detectores e a amostra.
624
+ # List to store the classes and the average distance between the detectors and the sample.
580
625
  possible_classes = []
581
626
  for _class_ in self.classes:
582
- # Variável para identificar, se a classe foi encontrada com os detectores.
627
+ # Variable to indicate if the class was found with the detectors.
583
628
  class_found: bool = True
584
- sum_distance = 0 # Variável para fazer o somatório das distâncias.
629
+ sum_distance = 0
585
630
  for detector in self.detectors[_class_]:
586
- # Calcula a distância entre a amostra e os detectores.
587
631
  distance = self.__distance(detector.position, line)
588
- # Soma as distâncias para calcular a média.
589
632
  sum_distance += distance
590
633
  if self._algorithm == "V-detector":
591
634
  if distance <= detector.radius:
@@ -595,17 +638,16 @@ class RNSA(Base):
595
638
  class_found = False
596
639
  break
597
640
 
598
- # Se a amostra passar por todos os detectores de uma classe, adiciona a classe como
599
- # possível previsão.
641
+ # If the sample passes through all the detectors of a class, adds the class as a possible prediction.
600
642
  if class_found:
601
643
  possible_classes.append([_class_, sum_distance / self.N])
602
- # Se classificar como pertencentes a apenas uma classe, retorna a classe.
644
+ # If classified as belonging to only one class, returns the class.
603
645
  if len(possible_classes) == 1:
604
646
  return possible_classes[0][0]
605
- # Se, pertencer a mais de uma classe, retorna a classe com a distância média mais distante.
647
+ # If belonging to more than one class, returns the class with the greatest average distance.
606
648
  elif len(possible_classes) > 1:
607
649
  return max(possible_classes, key=lambda x: x[1])[0]
608
- else: # Se não, retorna None
650
+ else:
609
651
  return None
610
652
 
611
653
  def __distance(self, u: npt.NDArray, v: npt.NDArray):
@@ -673,93 +715,15 @@ class RNSA(Base):
673
715
  """
674
716
  new_detector_r = float(distance - self.r_s)
675
717
  if self.r >= new_detector_r:
676
- return False # Detector não é valido!
718
+ return False
677
719
  else:
678
- # se _cell_bounds igual a True, considera o detector esta dentro do limite do plano.
720
+ # If _cell_bounds is True, considers the detector to be within the plane bounds.
679
721
  if self._cell_bounds:
680
722
  for p in vector_x:
681
723
  if (p - new_detector_r) < 0 or (p + new_detector_r) > 1:
682
724
  return False
683
725
  return True, new_detector_r
684
726
 
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
- def score(self, X: npt.NDArray, y: list) -> float:
718
- """
719
- Score function calculates forecast accuracy.
720
-
721
- Details:
722
- ---
723
- This function performs the prediction of X and checks how many elements are equal between \
724
- vector y and y_predicted. This function was added for compatibility with some scikit-learn \
725
- functions.
726
-
727
- Parameters:
728
- -----------
729
-
730
- * X (np.ndarray): Feature set with shape (n_samples, n_features).
731
- * y (np.ndarray): True values with shape (n_samples,).
732
-
733
- Returns:
734
- -------
735
-
736
- accuracy: float
737
- The accuracy of the model.
738
-
739
- ---
740
-
741
- Função score calcular a acurácia da previsão.
742
-
743
- Details:
744
- ---
745
- Esta função realiza a previsão de X e verifica quantos elementos são iguais entre o vetor \
746
- y e y_previsto. Essa função foi adicionada para oferecer compatibilidade com algumas \
747
- funções do scikit-learn.
748
-
749
- Parameters:
750
- ---
751
-
752
- * X (np.ndarray): Conjunto de características com shape (n_samples, n_features).
753
- * y (np.ndarray): Valores verdadeiros com shape (n_samples,).
754
-
755
- returns:
756
- ---
757
-
758
- * accuracy (float): A acurácia do modelo.
759
-
760
- """
761
- return super()._score(X, y)
762
-
763
727
  def get_params(self, deep: bool = True) -> dict:
764
728
  return {
765
729
  "N": self.N,
@@ -818,9 +782,16 @@ class BNSA(Base):
818
782
 
819
783
  """
820
784
 
821
- def __init__(self, N: int = 100, aff_thresh: float = 0.1, max_discards: int = 1000, seed: int = None,
822
- no_label_sample_selection: Literal["max_average_difference", "max_nearest_difference"] =
823
- "max_average_difference"):
785
+ def __init__(
786
+ self,
787
+ N: int = 100,
788
+ aff_thresh: float = 0.1,
789
+ max_discards: int = 1000,
790
+ seed: int = None,
791
+ no_label_sample_selection: Literal[
792
+ "max_average_difference", "max_nearest_difference"
793
+ ] = "max_average_difference",
794
+ ):
824
795
  """
825
796
  Constructor of the Negative Selection class (``BNSA``).
826
797
 
@@ -931,41 +902,41 @@ class BNSA(Base):
931
902
  """
932
903
  super()._check_and_raise_exceptions_fit(X, y, "BNSA")
933
904
 
934
- # Converte todo o array X para boolean
905
+ # Converts the entire array X to boolean
935
906
  if X.dtype != bool:
936
907
  X = X.astype(bool)
937
908
 
938
- # Identificando as classes possíveis, dentro do array de saídas ``y``.
909
+ # Identifying the possible classes within the output array `y`.
939
910
  self.classes = np.unique(y)
940
- # Dict que armazenará os detectores com as classes como key.
911
+ # Dictionary that will store detectors with classes as keys.
941
912
  list_detectors_by_class = dict()
942
- # Separa as classes para o treinamento.
913
+ # Separates the classes for training.
943
914
  sample_index: dict = self.__slice_index_list_by_class(y)
944
- # Barra de progresso para a geração de todos os detectores.
915
+ # Progress bar for generating all detectors.
945
916
  if verbose:
946
917
  progress = tqdm(total=int(self.N * (len(self.classes))),
947
918
  bar_format='{desc} ┇{bar}┇ {n}/{total} detectors', postfix='\n')
948
919
 
949
920
  for _class_ in self.classes:
950
- # Inicia o conjunto vazio que conterá os detectores válidos.
921
+ # Initializes the empty set that will contain the valid detectors.
951
922
  valid_detectors_set: list = []
952
923
  discard_count: int = 0
953
- # Informando em qual classe o algoritmo está para a barra de progresso.
924
+ # Updating the progress bar with the current class the algorithm is processing.
954
925
  if verbose:
955
926
  progress.set_description_str(
956
927
  f"Generating the detectors for the {_class_} class:")
957
928
  while len(valid_detectors_set) < self.N:
958
929
 
959
930
  is_valid_detector: bool = True
960
- # Gera um vetor candidato a detector aleatoriamente com valores 0 e 1.
931
+ # Generates a candidate detector vector randomly with values 0 and 1.
961
932
  vector_x = np.random.choice([False, True], size=X.shape[1])
962
- # Calcula a distância entre o candidato e as amostras da classe.
963
- distances = cdist(np.expand_dims(vector_x, axis=0),
933
+ # Calculates the distance between the candidate and the class samples.
934
+ distances = cdist(np.expand_dims(vector_x, axis=0),
964
935
  X[sample_index[_class_]], metric='hamming')
965
- # Verifica se alguma das distâncias está abaixo ou igual ao limiar
936
+ # Checks if any of the distances is below or equal to the threshold.
966
937
  is_valid_detector = not np.any(distances <= self.aff_thresh)
967
938
 
968
- # Se o detector for válido, adicione a lista dos válidos.
939
+ # If the detector is valid, add it to the list of valid detectors.
969
940
  if is_valid_detector:
970
941
  discard_count = 0
971
942
  valid_detectors_set.append(vector_x)
@@ -981,15 +952,15 @@ class BNSA(Base):
981
952
  "radius and consider reducing its value."
982
953
  )
983
954
 
984
- # Adicionar detectores, com as classes como chave na dict.
955
+ # Add detectors to the dictionary with classes as keys.
985
956
  list_detectors_by_class[_class_] = valid_detectors_set
986
957
 
987
- # Informar a finalização da geração dos detectores para as classes.
958
+ # Notify the completion of detector generation for the classes.
988
959
  if verbose:
989
960
  progress.set_description(
990
961
  f'\033[92m✔ Non-self detectors for classes ({", ".join(map(str, self.classes))}) '
991
962
  f'successfully generated\033[0m')
992
- # Armazena os detectores encontrados no atributo, para os detectores da classe.
963
+ # Saves the found detectors in the attribute for the class detectors.
993
964
  self.detectors = list_detectors_by_class
994
965
  return self
995
966
 
@@ -1025,7 +996,7 @@ class BNSA(Base):
1025
996
  contendo as classes previstas para ``X``.
1026
997
  * ``None``: Se não existir detectores para a previsão.
1027
998
  """
1028
- # se não houver detectores retorna None.
999
+ # If there are no detectors, returns None.
1029
1000
  if self.detectors is None:
1030
1001
  return None
1031
1002
  elif not isinstance(X, (np.ndarray, list)):
@@ -1036,45 +1007,42 @@ class BNSA(Base):
1036
1007
  len(self.detectors[self.classes[0]][0])
1037
1008
  )
1038
1009
  )
1039
- # Verifica se a matriz X contém apenas amostras binárias. Caso contrário, lança uma exceção.
1010
+ # Checks if matrix X contains only binary samples. Otherwise, raises an exception.
1040
1011
  if not np.isin(X, [0, 1]).all():
1041
1012
  raise ValueError(
1042
1013
  "The array X contains values that are not composed only of 0 and 1."
1043
1014
  )
1044
1015
 
1045
- # Converte todo o array X para boolean
1016
+ # Converts the entire array X to boolean.
1046
1017
  if X.dtype != bool:
1047
1018
  X = X.astype(bool)
1048
1019
 
1049
- # Inicia um array vazio.
1020
+ # Initializes an empty array that will store the predictions.
1050
1021
  C = np.empty(shape=0)
1051
- # Para cada linha de amostra em X.
1022
+ # For each sample row in X.
1052
1023
  for line in X:
1053
1024
  class_found: bool = True
1054
- # Lista para armazenar as possíveis classes às quais a amostra se adequou ao self na
1055
- # comparação com os detectores non-self.
1025
+ # List to store the possible classes to which the sample matches with self
1026
+ # when compared to the non-self detectors.
1056
1027
  possible_classes: list = []
1057
1028
  for _class_ in self.classes:
1058
- # Lista para armazenar as taxas de similaridade entre a amostra e os detectores.
1059
1029
  similarity_sum: float = 0
1060
-
1061
- # Calcula a distância de Hamming entre a linha e todos os detectores
1030
+ # Calculates the Hamming distance between the row and all detectors.
1062
1031
  distances = cdist(np.expand_dims(line, axis=0),
1063
1032
  self.detectors[_class_], metric='hamming')
1064
1033
 
1065
- # Verificar se alguma distância está abaixo ou igual ao limiar
1034
+ # Check if any distance is below or equal to the threshold.
1066
1035
  if np.any(distances <= self.aff_thresh):
1067
1036
  class_found = False
1068
1037
  else:
1069
- # Somar todas as distâncias
1070
1038
  similarity_sum = np.sum(distances)
1071
1039
 
1072
- # Se a amostra passar por todos os detectores de uma classe, adiciona a classe como
1073
- # possível previsão e sua media de similaridade.
1040
+ # If the sample passes through all detectors of a class, adds the class as a possible prediction
1041
+ # and its average similarity.
1074
1042
  if class_found:
1075
1043
  possible_classes.append([_class_, similarity_sum / self.N])
1076
1044
 
1077
- # Se, pertencer a uma ou mais classes, adiciona a classe com a distância média mais distante.
1045
+ # If belonging to one or more classes, adds the class with the greatest average distance.
1078
1046
  if len(possible_classes) > 0:
1079
1047
  C = np.append(
1080
1048
  C, [max(possible_classes, key=lambda x: x[1])[0]])
@@ -1082,24 +1050,24 @@ class BNSA(Base):
1082
1050
  else:
1083
1051
  class_found = False
1084
1052
 
1085
- # Se possuir apenas uma classe e não classificar a amostra define a saída como não-própria.
1053
+ # If there is only one class and the sample is not classified, sets the output as non-self.
1086
1054
  if not class_found and len(self.classes) == 1:
1087
1055
  C = np.append(C, ["non-self"])
1088
- # Se a classe não puder ser identificada pelos detectores
1056
+ # If the class cannot be identified by the detectors
1089
1057
  elif not class_found:
1090
1058
  class_differences: dict = {}
1091
1059
  for _class_ in self.classes:
1092
- # Atribua-a o rotulo a classe com à maior distância em relação ao detector mais próximo.
1060
+ # Assign the label to the class with the greatest distance from the nearest detector.
1093
1061
  if self.no_label_sample_selection == 'nearest_difference':
1094
- difference_min: float = cdist( np.expand_dims(line, axis=0),
1095
- self.detectors[_class_], metric='hamming'
1096
- ).min()
1062
+ difference_min: float = cdist(np.expand_dims(line, axis=0),
1063
+ self.detectors[_class_], metric='hamming'
1064
+ ).min()
1097
1065
  class_differences[_class_] = difference_min
1098
- # Ou com base na maior distância com relação à média da distancias dos detectores
1066
+ # Or based on the greatest distance from the average distances of the detectors.
1099
1067
  else:
1100
- difference_sum: float = cdist( np.expand_dims(line, axis=0),
1101
- self.detectors[_class_], metric='hamming'
1102
- ).sum()
1068
+ difference_sum: float = cdist(np.expand_dims(line, axis=0),
1069
+ self.detectors[_class_], metric='hamming'
1070
+ ).sum()
1103
1071
  class_differences[_class_] = difference_sum / self.N
1104
1072
 
1105
1073
  C = np.append(C, [max(class_differences, key=class_differences.get)])
@@ -1136,56 +1104,7 @@ class BNSA(Base):
1136
1104
  ---
1137
1105
  * dict: Um dicionário com a lista de posições do array(``y``), com as classes como chave.
1138
1106
  """
1139
- return super()._slice_index_list_by_class(y)
1140
-
1141
- def score(self, X: npt.NDArray, y: list) -> float:
1142
- """
1143
- Score function calculates forecast accuracy.
1144
-
1145
- Details:
1146
- ---
1147
- This function performs the prediction of X and checks how many elements are equal between vector \
1148
- y and y_predicted. This function was added for compatibility with some scikit-learn functions.
1149
-
1150
- Parameters:
1151
- -----------
1152
-
1153
- X: np.ndarray
1154
- Feature set with shape (n_samples, n_features).
1155
- y: np.ndarray
1156
- True values with shape (n_samples,).
1157
-
1158
- Returns:
1159
- -------
1160
-
1161
- accuracy: float
1162
- The accuracy of the model.
1163
-
1164
- ---
1165
-
1166
- Função score calcular a acurácia da previsão.
1167
-
1168
- Details:
1169
- ---
1170
- Esta função realiza a previsão de X e verifica quantos elementos são iguais entre o vetor y \
1171
- e y_previsto.
1172
- Essa função foi adicionada para oferecer compatibilidade com algumas funções do scikit-learn.
1173
-
1174
- Parameters:
1175
- ---
1176
-
1177
- * X : np.ndarray
1178
- Conjunto de características com shape (n_samples, n_features).
1179
- * y : np.ndarray
1180
- Valores verdadeiros com shape (n_samples,).
1181
-
1182
- returns:
1183
- ---
1184
-
1185
- accuracy : float
1186
- A acurácia do modelo.
1187
- """
1188
- return super()._score(X, y)
1107
+ return slice_index_list_by_class(self.classes, y)
1189
1108
 
1190
1109
  def get_params(self, deep: bool = True) -> dict:
1191
1110
  return {
aisp/utils/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from ._multiclass import slice_index_list_by_class
2
+
3
+ __author__ = "João Paulo da Silva Barros"
4
+ __all__ = ["slice_index_list_by_class"]
5
+ __version__ = "0.1.33"
@@ -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,15 +1,15 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: aisp
3
- Version: 0.1.32
3
+ Version: 0.1.34
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>
7
- License: LGPL-3.0 license
7
+ License-Expression: LGPL-3.0-only
8
8
  Project-URL: Homepage, https://ais-package.github.io/
9
9
  Project-URL: Documentation, https://ais-package.github.io/docs/intro
10
10
  Project-URL: Source Code, https://github.com/AIS-Package/aisp
11
11
  Project-URL: Tracker, https://github.com/AIS-Package/aisp/issues
12
- Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
12
+ Keywords: Artificial Immune Systems,classification,Natural computing,machine learning,artificial intelligence
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python
15
15
  Classifier: Programming Language :: Python :: 3
@@ -20,9 +20,10 @@ Classifier: Programming Language :: Python :: 3.11
20
20
  Requires-Python: >=3.8.10
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: numpy >=1.22.4
24
- Requires-Dist: scipy >=1.8.1
25
- Requires-Dist: tqdm >=4.64.1
23
+ Requires-Dist: numpy>=1.22.4
24
+ Requires-Dist: scipy>=1.8.1
25
+ Requires-Dist: tqdm>=4.64.1
26
+ Dynamic: license-file
26
27
 
27
28
  <div align = center>
28
29
 
@@ -63,7 +64,6 @@ Requires-Dist: tqdm >=4.64.1
63
64
  > 2. [Installation.](#installation)
64
65
  > 1. [Dependencies](#dependencies)
65
66
  > 2. [User installation](#user-installation)
66
- > 3. [How to import the Techniques](#how-to-import-the-techniques)
67
67
  > 3. [Examples.](#examples)
68
68
 
69
69
  ---
@@ -81,8 +81,8 @@ Artificial Immune Systems (AIS) are inspired by the vertebrate immune system, cr
81
81
  ##### Algorithms implemented:
82
82
 
83
83
  > - [x] [**Negative Selection.**](https://ais-package.github.io/docs/aisp-techniques/Negative%20Selection/)
84
+ > - [ ] *Clonal Selection Algorithms.*
84
85
  > - [ ] *Dendritic Cells.*
85
- > - [ ] *Clonalg.*
86
86
  > - [ ] *Immune Network Theory.*
87
87
 
88
88
  </section>
@@ -119,17 +119,7 @@ pip install aisp
119
119
  ```
120
120
 
121
121
  </section>
122
- <section id='how-to-import-the-techniques'>
123
122
 
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
123
  </section>
134
124
  <section id='examples'>
135
125
 
@@ -173,7 +163,6 @@ Below are some examples that use a database for classification with the [Jupyter
173
163
  > 2. [Instalação.](#instalação)
174
164
  > 1. [Dependências](#dependências)
175
165
  > 2. [Instalação do usuário](#instalação-do-usuário)
176
- > 3. [Como importar as Tecnicas](#como-importar-as-tecnicas)
177
166
  > 3. [Exemplos.](#exemplos)
178
167
 
179
168
  ---
@@ -190,8 +179,8 @@ Os sistemas imunológicos artificiais (SIA) inspiram-se no sistema imunológico
190
179
  ##### Algoritmos implementados:
191
180
 
192
181
  > - [x] [**Seleção Negativa.**](https://ais-package.github.io/docs/aisp-techniques/Negative%20Selection/)
182
+ > - [ ] *Algoritmos de Seleção Clonal.*
193
183
  > - [ ] *Células Dendríticas.*
194
- > - [ ] *Clonalg.*
195
184
  > - [ ] *Teoria da Rede Imune.*
196
185
 
197
186
  </section>
@@ -229,17 +218,6 @@ pip install aisp
229
218
 
230
219
  </section>
231
220
 
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
221
  </section>
244
222
  <section id='exemplos'>
245
223
 
@@ -0,0 +1,11 @@
1
+ aisp/NSA/__init__.py,sha256=jtJqB7TetYljN7ztwaP2SIXPll4lBuBXnKD6wjfb2x0,669
2
+ aisp/NSA/_base.py,sha256=PPQ9DygFG6ONqmcX02PUeSggBWc6j2LDsBQKfBnjO5Y,10860
3
+ aisp/NSA/_negative_selection.py,sha256=EXXGkCgw2bsbsfVgvXeZ2tZU3XnHnVcqEsHLiwwFhLA,52251
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.34.dist-info/licenses/LICENSE,sha256=fTqV5eBpeAZO0_jit8j4Ref9ikBSlHJ8xwj5TLg7gFk,7817
8
+ aisp-0.1.34.dist-info/METADATA,sha256=IYR9cqK3kK3Hybt0_aGrHvA7jypOXPRm9WH-hxkS6_w,7615
9
+ aisp-0.1.34.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
10
+ aisp-0.1.34.dist-info/top_level.txt,sha256=Q5aJi_rAVT5UNS1As0ZafoyS5dwNibnoyOYV7RWUB9s,5
11
+ aisp-0.1.34.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,