python-som 0.0.1a2__py3-none-any.whl → 0.1.2__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.
- python_som/__init__.py +348 -258
- {python_som-0.0.1a2.dist-info → python_som-0.1.2.dist-info}/METADATA +14 -16
- python_som-0.1.2.dist-info/RECORD +5 -0
- {python_som-0.0.1a2.dist-info → python_som-0.1.2.dist-info}/WHEEL +1 -2
- python_som-0.0.1a2.dist-info/RECORD +0 -6
- python_som-0.0.1a2.dist-info/top_level.txt +0 -1
- {python_som-0.0.1a2.dist-info → python_som-0.1.2.dist-info/licenses}/LICENSE +0 -0
python_som/__init__.py
CHANGED
|
@@ -1,12 +1,47 @@
|
|
|
1
|
+
"""This module contains the implementation of the 2D self-organizing map.
|
|
2
|
+
|
|
3
|
+
Most features were implemented using NumPy, with Scikit-learn for standardization and PCA.
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
- Stepwise and batch training
|
|
7
|
+
- Random weight initialization
|
|
8
|
+
- Random sampling weight initialization
|
|
9
|
+
- Linear weight initialization (with PCA)
|
|
10
|
+
- Automatic selection of map size ratio (with PCA)
|
|
11
|
+
- Support for cyclic arrays, for toroidal or spherical maps
|
|
12
|
+
- Gaussian and Bubble neighborhood functions
|
|
13
|
+
- Support for custom decay functions
|
|
14
|
+
- Support for visualization (U-matrix, activation matrix)
|
|
15
|
+
- Support for supervised learning (label map)
|
|
16
|
+
- Support for NumPy arrays, Pandas DataFrames and regular lists of values
|
|
17
|
+
|
|
18
|
+
Reference:
|
|
19
|
+
Teuvo Kohonen,
|
|
20
|
+
Essentials of the self-organizing map,
|
|
21
|
+
Neural Networks,
|
|
22
|
+
Volume 37,
|
|
23
|
+
2013,
|
|
24
|
+
Pages 52-65,
|
|
25
|
+
ISSN 0893-6080,
|
|
26
|
+
https://doi.org/10.1016/j.neunet.2012.09.018.
|
|
27
|
+
"""
|
|
28
|
+
|
|
1
29
|
# %%
|
|
2
30
|
from collections import Counter
|
|
3
|
-
from typing import
|
|
31
|
+
from typing import Callable
|
|
4
32
|
|
|
5
33
|
import numpy as np
|
|
6
34
|
import pandas as pd
|
|
7
|
-
import sklearn
|
|
8
|
-
import sklearn.decomposition
|
|
9
|
-
import sklearn.preprocessing
|
|
35
|
+
import sklearn # type: ignore
|
|
36
|
+
import sklearn.decomposition # type: ignore
|
|
37
|
+
import sklearn.preprocessing # type: ignore
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
import tqdm
|
|
41
|
+
|
|
42
|
+
TQDM_AVAILABLE = True
|
|
43
|
+
except ImportError:
|
|
44
|
+
TQDM_AVAILABLE = False
|
|
10
45
|
|
|
11
46
|
|
|
12
47
|
# %%
|
|
@@ -59,15 +94,14 @@ def _inverse_decay(x: float, t: int, max_t: int) -> float:
|
|
|
59
94
|
return (max_t / 100) * x / ((max_t / 100) + t)
|
|
60
95
|
|
|
61
96
|
|
|
62
|
-
def _euclidean_distance(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"""
|
|
66
|
-
This function calculates the euclidean distances between the elements of the last dimension of a and b.
|
|
97
|
+
def _euclidean_distance(a: np.ndarray, b: np.ndarray) -> np.ndarray:
|
|
98
|
+
"""This function calculates the euclidean distances between the elements of the last dimension
|
|
99
|
+
of a and b.
|
|
67
100
|
|
|
68
101
|
The parameters a and b may be n-dimensional, but their shapes must be capable of broadcasting
|
|
69
102
|
|
|
70
|
-
The shape of the output complies to the first n-1 dimensions of the parameter with the highest
|
|
103
|
+
The shape of the output complies to the first n-1 dimensions of the parameter with the highest
|
|
104
|
+
dimensionality.
|
|
71
105
|
|
|
72
106
|
:param a: array-like: list or numpy array of values. a must not be a scalar value.
|
|
73
107
|
:param b: array-like: list or numpy array of values. b must not be a scalar value.
|
|
@@ -77,9 +111,11 @@ def _euclidean_distance(
|
|
|
77
111
|
|
|
78
112
|
|
|
79
113
|
class SOM:
|
|
80
|
-
"""
|
|
81
|
-
|
|
82
|
-
|
|
114
|
+
"""Implementation of the 2D self-organizing map, with support for NumPy arrays and
|
|
115
|
+
Pandas DataFrames.
|
|
116
|
+
|
|
117
|
+
Most features were implemented using NumPy, with Scikit-learn for standardization and
|
|
118
|
+
PCA operations.
|
|
83
119
|
|
|
84
120
|
Features:
|
|
85
121
|
- Stepwise and batch training
|
|
@@ -103,13 +139,12 @@ class SOM:
|
|
|
103
139
|
Pages 52-65,
|
|
104
140
|
ISSN 0893-6080,
|
|
105
141
|
https://doi.org/10.1016/j.neunet.2012.09.018.
|
|
106
|
-
|
|
107
142
|
"""
|
|
108
143
|
|
|
109
144
|
def __init__(
|
|
110
145
|
self,
|
|
111
|
-
x:
|
|
112
|
-
y:
|
|
146
|
+
x: int | None,
|
|
147
|
+
y: int | None,
|
|
113
148
|
input_len: int,
|
|
114
149
|
learning_rate: float = 0.5,
|
|
115
150
|
learning_rate_decay: Callable[[float, int, int], float] = _asymptotic_decay,
|
|
@@ -119,13 +154,13 @@ class SOM:
|
|
|
119
154
|
] = _asymptotic_decay,
|
|
120
155
|
neighborhood_function: str = "gaussian",
|
|
121
156
|
distance_function: Callable[
|
|
122
|
-
[
|
|
123
|
-
|
|
157
|
+
[np.ndarray, np.ndarray],
|
|
158
|
+
np.ndarray,
|
|
124
159
|
] = _euclidean_distance,
|
|
125
160
|
cyclic_x: bool = False,
|
|
126
161
|
cyclic_y: bool = False,
|
|
127
|
-
random_seed:
|
|
128
|
-
data:
|
|
162
|
+
random_seed: int | None = None,
|
|
163
|
+
data: np.ndarray | pd.DataFrame | list | None = None,
|
|
129
164
|
) -> None:
|
|
130
165
|
"""
|
|
131
166
|
Constructor for the self-organizing map class.
|
|
@@ -133,13 +168,13 @@ class SOM:
|
|
|
133
168
|
:param x: int or NoneType: X dimension of the self-organizing map, i.e.,
|
|
134
169
|
number of rows of the matrix of weights.
|
|
135
170
|
x should be larger than 0.
|
|
136
|
-
If x is None and 'data' is provided in kwargs, its value will be automatically
|
|
137
|
-
Either x or y should be different than None.
|
|
171
|
+
If x is None and 'data' is provided in kwargs, its value will be automatically
|
|
172
|
+
selected using PCA of 'data'. Either x or y should be different than None.
|
|
138
173
|
:param y: int or NoneType: Y dimension of the self-organizing map, i.e.,
|
|
139
174
|
number of columns of the matrix of weights.
|
|
140
175
|
y should be larger than 0.
|
|
141
|
-
If y is None and 'data' is provided in kwargs, its value will be automatically
|
|
142
|
-
Either x or y should be different than None.
|
|
176
|
+
If y is None and 'data' is provided in kwargs, its value will be automatically
|
|
177
|
+
selected using PCA of 'data'. Either x or y should be different than None.
|
|
143
178
|
:param input_len: int: Number of features of the training dataset, i.e.,
|
|
144
179
|
number of elements of each node of the network.
|
|
145
180
|
:param learning_rate: float: Initial learning rate for the training process.
|
|
@@ -147,23 +182,24 @@ class SOM:
|
|
|
147
182
|
Defaults to 0.5.
|
|
148
183
|
Note: The value of the learning_rate is irrelevant for the 'batch' training mode.
|
|
149
184
|
:param learning_rate_decay: function: Decay function for the learning_rate variable.
|
|
150
|
-
May be a predefined one from this package, or a custom function, with the same
|
|
151
|
-
Defaults to _asymptotic_decay.
|
|
152
|
-
:param neighborhood_radius: float: Initial neighborhood radius for the training process.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
185
|
+
May be a predefined one from this package, or a custom function, with the same
|
|
186
|
+
parameters and return type. Defaults to _asymptotic_decay.
|
|
187
|
+
:param neighborhood_radius: float: Initial neighborhood radius for the training process.
|
|
188
|
+
Defaults to 1.
|
|
189
|
+
:param neighborhood_radius_decay: function: Decay function for the neighborhood_radius
|
|
190
|
+
variable. May be a predefined one from this package, or a custom function, with the
|
|
191
|
+
same parameters and return type. Defaults to _asymptotic_decay
|
|
156
192
|
:param neighborhood_function: str: Neighborhood function name for the training process.
|
|
157
193
|
May be either 'gaussian' or 'bubble'.
|
|
158
|
-
:param distance_function: function: Function for calculating distances/dissimilarities
|
|
159
|
-
network.
|
|
160
|
-
May be a predefined one from this package, or a custom function, with the same
|
|
161
|
-
Defaults to _euclidean_distance.
|
|
162
|
-
:param cyclic_x: bool: Boolean value activate/deactivate cyclic arrays in the x direction,
|
|
163
|
-
between the first and last rows of the weight matrix.
|
|
194
|
+
:param distance_function: function: Function for calculating distances/dissimilarities
|
|
195
|
+
between models of the network.
|
|
196
|
+
May be a predefined one from this package, or a custom function, with the same
|
|
197
|
+
parameters and return type. Defaults to _euclidean_distance.
|
|
198
|
+
:param cyclic_x: bool: Boolean value activate/deactivate cyclic arrays in the x direction,
|
|
199
|
+
i.e, between the first and last rows of the weight matrix.
|
|
164
200
|
Defaults to False.
|
|
165
|
-
:param cyclic_y: bool: Boolean value activate/deactivate cyclic arrays in the y direction,
|
|
166
|
-
between the first and last columns of the weight matrix.
|
|
201
|
+
:param cyclic_y: bool: Boolean value activate/deactivate cyclic arrays in the y direction,
|
|
202
|
+
i.e, between the first and last columns of the weight matrix.
|
|
167
203
|
Defaults to False.
|
|
168
204
|
:param random_seed: int or None: Seed for NumPy random value generator. Defaults to None.
|
|
169
205
|
:param data: array-like: dataset for performing PCA.
|
|
@@ -174,11 +210,12 @@ class SOM:
|
|
|
174
210
|
raise ValueError("At least one of the dimensions (x, y) must be specified")
|
|
175
211
|
if x is None or y is None:
|
|
176
212
|
# If a dataset was given through **kwargs, select missing dimension with PCA
|
|
177
|
-
# The ratio of the (x, y) sizes will comply roughly with the ratio of
|
|
178
|
-
|
|
213
|
+
# The ratio of the (x, y) sizes will comply roughly with the ratio of
|
|
214
|
+
# the two largest principal components
|
|
215
|
+
if data is None:
|
|
179
216
|
raise ValueError(
|
|
180
|
-
"If one of the dimensions is not specified,
|
|
181
|
-
"initialization."
|
|
217
|
+
"If one of the dimensions is not specified,"
|
|
218
|
+
"a dataset must be provided for automatic size initialization."
|
|
182
219
|
)
|
|
183
220
|
# Convert data to numpy array
|
|
184
221
|
if isinstance(data, pd.DataFrame):
|
|
@@ -201,25 +238,23 @@ class SOM:
|
|
|
201
238
|
# Initializing private variables
|
|
202
239
|
self._shape = (np.uint(x), np.uint(y))
|
|
203
240
|
self._input_len = np.uint(input_len)
|
|
204
|
-
self._learning_rate =
|
|
241
|
+
self._learning_rate = float(learning_rate)
|
|
205
242
|
self._learning_rate_decay = learning_rate_decay
|
|
206
|
-
self._neighborhood_radius =
|
|
243
|
+
self._neighborhood_radius = float(neighborhood_radius)
|
|
207
244
|
self._neighborhood_radius_decay = neighborhood_radius_decay
|
|
208
245
|
self._neighborhood_function = {
|
|
209
246
|
"gaussian": self._gaussian,
|
|
210
247
|
"bubble": self._bubble,
|
|
211
248
|
}[neighborhood_function]
|
|
212
249
|
self._distance_function = distance_function
|
|
213
|
-
self._cyclic = (
|
|
250
|
+
self._cyclic = (bool(cyclic_x), bool(cyclic_y))
|
|
214
251
|
self._neigx, self._neigy = np.arange(self._shape[0]), np.arange(self._shape[1])
|
|
215
252
|
|
|
216
253
|
# Seed numpy random generator
|
|
217
254
|
if random_seed is None:
|
|
218
|
-
self._random_seed = np.random.randint(
|
|
219
|
-
np.random.randint(np.iinfo(np.int32).max)
|
|
220
|
-
)
|
|
255
|
+
self._random_seed = np.random.randint(np.iinfo(np.int32).max)
|
|
221
256
|
else:
|
|
222
|
-
self._random_seed =
|
|
257
|
+
self._random_seed = int(random_seed)
|
|
223
258
|
np.random.seed(self._random_seed)
|
|
224
259
|
|
|
225
260
|
# Random weight initialization
|
|
@@ -227,7 +262,7 @@ class SOM:
|
|
|
227
262
|
size=(self._shape[0], self._shape[1], self._input_len)
|
|
228
263
|
)
|
|
229
264
|
|
|
230
|
-
def get_shape(self) ->
|
|
265
|
+
def get_shape(self) -> tuple[np.uint, np.uint]:
|
|
231
266
|
"""
|
|
232
267
|
Gets the shape of the network.
|
|
233
268
|
|
|
@@ -249,17 +284,17 @@ class SOM:
|
|
|
249
284
|
|
|
250
285
|
:param learning_rate: float: New value for learning_rate of an instance of the SOM.
|
|
251
286
|
"""
|
|
252
|
-
self._learning_rate =
|
|
287
|
+
self._learning_rate = float(learning_rate)
|
|
253
288
|
|
|
254
289
|
def set_neighborhood_radius(self, neighborhood_radius: float) -> None:
|
|
255
290
|
"""
|
|
256
291
|
Sets the neighborhood_radius member of the SOM.
|
|
257
292
|
|
|
258
|
-
:param neighborhood_radius: float: New value for neighborhood_radius of
|
|
293
|
+
:param neighborhood_radius: float: New value for neighborhood_radius of a SOM instance.
|
|
259
294
|
"""
|
|
260
|
-
self._neighborhood_radius =
|
|
295
|
+
self._neighborhood_radius = float(neighborhood_radius)
|
|
261
296
|
|
|
262
|
-
def activate(self, x:
|
|
297
|
+
def activate(self, x: np.ndarray) -> np.ndarray:
|
|
263
298
|
"""
|
|
264
299
|
Calculates distances between an instance x and the weights of the network.
|
|
265
300
|
|
|
@@ -268,9 +303,7 @@ class SOM:
|
|
|
268
303
|
"""
|
|
269
304
|
return self._distance_function(x, self._weights)
|
|
270
305
|
|
|
271
|
-
def winner(
|
|
272
|
-
self, x: Union[np.ndarray, pd.DataFrame, list]
|
|
273
|
-
) -> Union[Iterable, Tuple[int, int]]:
|
|
306
|
+
def winner(self, x: np.ndarray) -> tuple[int, ...]:
|
|
274
307
|
"""
|
|
275
308
|
Calculates the best-matching unit of the network for an instance x
|
|
276
309
|
|
|
@@ -278,21 +311,22 @@ class SOM:
|
|
|
278
311
|
:return: (int, int): Index of the best-matching unit of x.
|
|
279
312
|
"""
|
|
280
313
|
activation_map = self.activate(x)
|
|
281
|
-
|
|
314
|
+
min_index = tuple(
|
|
315
|
+
map(int, np.unravel_index(activation_map.argmin(), activation_map.shape))
|
|
316
|
+
)
|
|
317
|
+
return min_index
|
|
282
318
|
|
|
283
|
-
def quantization(self, data:
|
|
319
|
+
def quantization(self, data: np.ndarray | pd.DataFrame | list) -> np.ndarray:
|
|
284
320
|
"""
|
|
285
321
|
Calculates distances from each instance of 'data' to each of the weights of the network.
|
|
286
322
|
|
|
287
323
|
:param data: array-like: Dataset to be compared with the weights of the network.
|
|
324
|
+
Expected shape is (n_samples, n_features).
|
|
288
325
|
:return: np.ndarray: array of lists of distances from each instance of the dataset
|
|
289
326
|
to each weight of the network.
|
|
290
327
|
"""
|
|
291
328
|
# Convert data to numpy array
|
|
292
|
-
|
|
293
|
-
data_array = data.to_numpy()
|
|
294
|
-
else:
|
|
295
|
-
data_array = np.array(data)
|
|
329
|
+
data_array = self._data_to_numpy(data)
|
|
296
330
|
return np.array(
|
|
297
331
|
[
|
|
298
332
|
(self._distance_function(i, self._weights[self.winner(i)]))
|
|
@@ -300,9 +334,8 @@ class SOM:
|
|
|
300
334
|
]
|
|
301
335
|
)
|
|
302
336
|
|
|
303
|
-
def quantization_error(self, data:
|
|
304
|
-
"""
|
|
305
|
-
Calculates average distance of the weights of the network to their assigned instances from data.
|
|
337
|
+
def quantization_error(self, data: np.ndarray | pd.DataFrame | list) -> float:
|
|
338
|
+
"""Calculates average distance of the weights of the network to their assigned instances.
|
|
306
339
|
This error is a quality measure for the training process.
|
|
307
340
|
|
|
308
341
|
:param data: array-like: Dataset to be compared with the weights of the network.
|
|
@@ -311,47 +344,52 @@ class SOM:
|
|
|
311
344
|
quantization = self.quantization(data)
|
|
312
345
|
return quantization.mean()
|
|
313
346
|
|
|
314
|
-
def distance_matrix(self) -> np.ndarray:
|
|
347
|
+
def distance_matrix(self, normalize: bool = False) -> np.ndarray:
|
|
315
348
|
"""
|
|
316
|
-
Calculates U-matrix of the current state of the network,
|
|
317
|
-
|
|
349
|
+
Calculates U-matrix of the current state of the network,
|
|
350
|
+
i.e., the matrix of distances between each node and its neighbors.
|
|
351
|
+
Has support for cyclic arrays
|
|
318
352
|
|
|
353
|
+
:param normalize: bool: Activate to normalize the U-matrix between 0 and 1.
|
|
354
|
+
Defaults to False.
|
|
319
355
|
:return: np.ndarray: U-matrix of the current state of the network.
|
|
320
356
|
"""
|
|
321
357
|
um = np.zeros(shape=self._shape)
|
|
322
358
|
it = np.nditer(um, flags=["multi_index"])
|
|
359
|
+
distances = np.zeros(self._shape + self._shape)
|
|
360
|
+
for i in range(self._shape[0]):
|
|
361
|
+
for j in range(self._shape[1]):
|
|
362
|
+
distances[i, j] = self._distance_function(
|
|
363
|
+
self._weights[i, j], self._weights
|
|
364
|
+
)
|
|
365
|
+
|
|
323
366
|
while not it.finished:
|
|
324
367
|
update_matrix = self._bubble(it.multi_index, 1)
|
|
325
|
-
um[it.multi_index] = np.sum(
|
|
326
|
-
update_matrix
|
|
327
|
-
* self._distance_function(self._weights[it.multi_index], self._weights)
|
|
328
|
-
)
|
|
368
|
+
um[it.multi_index] = np.sum(update_matrix * distances[it.multi_index])
|
|
329
369
|
it.iternext()
|
|
330
|
-
|
|
370
|
+
if normalize:
|
|
371
|
+
# Normalize U-matrix between 0 and 1
|
|
372
|
+
um = (um - np.min(um)) / (np.max(um) - np.min(um))
|
|
331
373
|
return um
|
|
332
374
|
|
|
333
|
-
def activation_matrix(
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
have been assigned to it, in the current state.
|
|
375
|
+
def activation_matrix(self, data: np.ndarray | pd.DataFrame | list) -> np.ndarray:
|
|
376
|
+
"""Calculates the activation matrix of the network for a dataset.
|
|
377
|
+
|
|
378
|
+
I.e., for each node, the count of instances that have been assigned to in,
|
|
379
|
+
in the current state.
|
|
339
380
|
|
|
340
381
|
:param data: array-like: Dataset to be compared with the weights of the network.
|
|
341
382
|
:return: np.ndarray: Activation matrix.
|
|
342
383
|
"""
|
|
343
384
|
# Convert data to numpy array
|
|
344
|
-
|
|
345
|
-
data_array = data.to_numpy()
|
|
346
|
-
else:
|
|
347
|
-
data_array = np.array(data)
|
|
385
|
+
data_array = self._data_to_numpy(data)
|
|
348
386
|
|
|
349
387
|
activation_matrix = np.zeros(self._shape)
|
|
350
388
|
for i in data_array:
|
|
351
389
|
activation_matrix[self.winner(i)] += 1
|
|
352
390
|
return activation_matrix
|
|
353
391
|
|
|
354
|
-
def winner_map(self, data:
|
|
392
|
+
def winner_map(self, data: np.ndarray | pd.DataFrame | list) -> dict:
|
|
355
393
|
"""
|
|
356
394
|
Calculates, for each node (i, j) of the network,
|
|
357
395
|
the list of all instances from 'data' that has been assigned to it.
|
|
@@ -360,12 +398,9 @@ class SOM:
|
|
|
360
398
|
:return: dict: Winner map.
|
|
361
399
|
"""
|
|
362
400
|
# Convert data to numpy array
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
data_array = np.array(data)
|
|
367
|
-
winner_map = {
|
|
368
|
-
(i, j): [] for i in range(self._shape[0]) for j in range(self._shape[1])
|
|
401
|
+
data_array = self._data_to_numpy(data)
|
|
402
|
+
winner_map: dict[tuple[int, ...], list] = {
|
|
403
|
+
tuple(index): [] for index in np.ndindex(self._shape)
|
|
369
404
|
}
|
|
370
405
|
for i in data_array:
|
|
371
406
|
winner_map[self.winner(i)].append(i)
|
|
@@ -373,40 +408,42 @@ class SOM:
|
|
|
373
408
|
|
|
374
409
|
def label_map(
|
|
375
410
|
self,
|
|
376
|
-
data:
|
|
377
|
-
labels:
|
|
378
|
-
) -> dict:
|
|
379
|
-
"""
|
|
380
|
-
|
|
381
|
-
respective instance from 'data' that has been assigned
|
|
411
|
+
data: np.ndarray | pd.DataFrame | list,
|
|
412
|
+
labels: np.ndarray | pd.DataFrame | list,
|
|
413
|
+
) -> dict[tuple[int, ...], Counter]:
|
|
414
|
+
"""Calculates, for each node (i, j) of the network, the frequency of each label...
|
|
415
|
+
|
|
416
|
+
from 'labels' corresponding to its respective instance from 'data' that has been assigned
|
|
417
|
+
to this node.
|
|
418
|
+
|
|
382
419
|
:param data: array-like: Dataset to be compared with the weights of the network.
|
|
383
420
|
:param labels: array-like: Labels corresponding to the indices of 'data'.
|
|
384
421
|
:return: dict: Label map.
|
|
385
422
|
"""
|
|
386
423
|
# Convert data to numpy array
|
|
387
|
-
|
|
388
|
-
data_array = data.to_numpy()
|
|
389
|
-
else:
|
|
390
|
-
data_array = np.array(data)
|
|
424
|
+
data_array = self._data_to_numpy(data)
|
|
391
425
|
# Convert labels to numpy array
|
|
392
426
|
if isinstance(labels, pd.DataFrame):
|
|
393
427
|
labels = labels.to_numpy()
|
|
394
428
|
else:
|
|
395
429
|
labels = np.array(labels)
|
|
396
430
|
|
|
397
|
-
winner_map = {
|
|
398
|
-
(
|
|
431
|
+
winner_map: dict[tuple[int, ...], list] = {
|
|
432
|
+
tuple(index): [] for index in np.ndindex(self._shape)
|
|
399
433
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
434
|
+
label_count_map: dict[tuple[int, ...], Counter] = {
|
|
435
|
+
tuple(index): Counter() for index in np.ndindex(self._shape)
|
|
436
|
+
}
|
|
437
|
+
for i, instance in enumerate(data_array):
|
|
438
|
+
winner = self.winner(instance)
|
|
439
|
+
winner_map[winner].append(labels[i])
|
|
440
|
+
label_count_map[winner].update([labels[i]])
|
|
441
|
+
return label_count_map
|
|
405
442
|
|
|
406
443
|
def train(
|
|
407
444
|
self,
|
|
408
|
-
data:
|
|
409
|
-
n_iteration:
|
|
445
|
+
data: np.ndarray | pd.DataFrame | list,
|
|
446
|
+
n_iteration: int | None = None,
|
|
410
447
|
mode: str = "random",
|
|
411
448
|
verbose: bool = False,
|
|
412
449
|
) -> float:
|
|
@@ -418,17 +455,14 @@ class SOM:
|
|
|
418
455
|
If None, defaults to 1000 * len(data) for stepwise training modes,
|
|
419
456
|
or 10 * len(data) for batch training mode.
|
|
420
457
|
:param mode: str: Training mode name. May be either 'random', 'sequential', or 'batch'.
|
|
421
|
-
For 'batch' mode, a much smaller number of iterations is needed,
|
|
422
|
-
for each individual iteration.
|
|
458
|
+
For 'batch' mode, a much smaller number of iterations is needed,
|
|
459
|
+
but a higher computation power is required for each individual iteration.
|
|
423
460
|
:param verbose: bool: Activate to print useful information to the terminal/console, e.g.,
|
|
424
461
|
the progress of the training process
|
|
425
462
|
:return: float: Quantization error after training
|
|
426
463
|
"""
|
|
427
464
|
# Convert data to numpy array for training
|
|
428
|
-
|
|
429
|
-
data_array = data.to_numpy()
|
|
430
|
-
else:
|
|
431
|
-
data_array = np.array(data)
|
|
465
|
+
data_array = self._data_to_numpy(data)
|
|
432
466
|
|
|
433
467
|
# If no number of iterations is given, select automatically
|
|
434
468
|
if n_iteration is None:
|
|
@@ -444,108 +478,13 @@ class SOM:
|
|
|
444
478
|
mode,
|
|
445
479
|
sep=" ",
|
|
446
480
|
)
|
|
447
|
-
if mode == "random":
|
|
448
|
-
# Random sampling from training dataset
|
|
449
|
-
for it, i in enumerate(
|
|
450
|
-
np.random.choice(
|
|
451
|
-
len(data_array), size=n_iteration, replace=(n_iteration > len(data))
|
|
452
|
-
)
|
|
453
|
-
):
|
|
454
|
-
# Calculating decaying alpha and sigma parameters for updating weights
|
|
455
|
-
alpha = self._learning_rate_decay(self._learning_rate, it, n_iteration)
|
|
456
|
-
sigma = self._neighborhood_radius_decay(
|
|
457
|
-
self._neighborhood_radius, it, n_iteration
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
# Finding winner node (best-matching unit)
|
|
461
|
-
winner = self.winner(data_array[i])
|
|
462
|
-
|
|
463
|
-
# Updating weights, based on current neighborhood function
|
|
464
|
-
self._weights += (
|
|
465
|
-
alpha
|
|
466
|
-
* self._neighborhood_function(winner, sigma)[..., None]
|
|
467
|
-
* (data_array[i] - self._weights)
|
|
468
|
-
)
|
|
469
481
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
print(
|
|
473
|
-
"Iteration:",
|
|
474
|
-
it,
|
|
475
|
-
"/",
|
|
476
|
-
n_iteration,
|
|
477
|
-
sep=" ",
|
|
478
|
-
end="\r",
|
|
479
|
-
flush=True,
|
|
480
|
-
)
|
|
482
|
+
if mode == "random":
|
|
483
|
+
self._train_random(data_array, n_iteration, verbose)
|
|
481
484
|
elif mode == "sequential":
|
|
482
|
-
|
|
483
|
-
for it, i in enumerate(data_array):
|
|
484
|
-
# Calculating decaying alpha and sigma parameters for updating weights
|
|
485
|
-
alpha = self._learning_rate_decay(self._learning_rate, it, n_iteration)
|
|
486
|
-
sigma = self._neighborhood_radius_decay(
|
|
487
|
-
self._neighborhood_radius, it, n_iteration
|
|
488
|
-
)
|
|
489
|
-
|
|
490
|
-
# Finding winner node (best-matching unit)
|
|
491
|
-
winner = self.winner(i)
|
|
492
|
-
|
|
493
|
-
# Updating weights, based on current neighborhood function
|
|
494
|
-
self._weights += (
|
|
495
|
-
alpha
|
|
496
|
-
* self._neighborhood_function(winner, sigma)[..., None]
|
|
497
|
-
* (i - self._weights)
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
# Print progress, if verbose is activated
|
|
501
|
-
if verbose:
|
|
502
|
-
print(
|
|
503
|
-
"Iteration:",
|
|
504
|
-
it,
|
|
505
|
-
"/",
|
|
506
|
-
n_iteration,
|
|
507
|
-
sep=" ",
|
|
508
|
-
end="\r",
|
|
509
|
-
flush=True,
|
|
510
|
-
)
|
|
485
|
+
self._train_sequential(data_array, n_iteration, verbose)
|
|
511
486
|
elif mode == "batch":
|
|
512
|
-
|
|
513
|
-
for it in range(n_iteration):
|
|
514
|
-
# Calculating decaying sigma
|
|
515
|
-
sigma = self._neighborhood_radius_decay(
|
|
516
|
-
self._neighborhood_radius, it, n_iteration
|
|
517
|
-
)
|
|
518
|
-
|
|
519
|
-
# For each node, create a list of instances associated to it
|
|
520
|
-
winner_map = self.winner_map(data_array)
|
|
521
|
-
|
|
522
|
-
# Calculate the weighted average of all instances in the neighborhood of each node
|
|
523
|
-
new_weights = np.zeros(self._weights.shape)
|
|
524
|
-
for i in winner_map.keys():
|
|
525
|
-
neig = self._neighborhood_function(i, sigma)
|
|
526
|
-
upper, bottom = np.zeros(self._input_len), 0.0
|
|
527
|
-
for j in winner_map.keys():
|
|
528
|
-
upper += neig[j] * np.sum(winner_map[j], axis=0)
|
|
529
|
-
bottom += neig[j] * len(winner_map[j])
|
|
530
|
-
|
|
531
|
-
# Only update if there is any instance associated with the winner node or its neighbors
|
|
532
|
-
if bottom != 0:
|
|
533
|
-
new_weights[i] = upper / bottom
|
|
534
|
-
|
|
535
|
-
# Update all nodes concurrently
|
|
536
|
-
self._weights = new_weights
|
|
537
|
-
|
|
538
|
-
# Print progress, if verbose is activated
|
|
539
|
-
if verbose:
|
|
540
|
-
print(
|
|
541
|
-
"Iteration:",
|
|
542
|
-
it,
|
|
543
|
-
"/",
|
|
544
|
-
n_iteration,
|
|
545
|
-
sep=" ",
|
|
546
|
-
end="\r",
|
|
547
|
-
flush=True,
|
|
548
|
-
)
|
|
487
|
+
self._train_batch(data_array, n_iteration, verbose)
|
|
549
488
|
else:
|
|
550
489
|
# Invalid training mode value
|
|
551
490
|
raise ValueError(
|
|
@@ -559,42 +498,186 @@ class SOM:
|
|
|
559
498
|
print("Quantization error:", q_error, sep=" ")
|
|
560
499
|
return q_error
|
|
561
500
|
|
|
501
|
+
def _train_random(
|
|
502
|
+
self, data_array: np.ndarray, n_iteration: int, verbose: bool
|
|
503
|
+
) -> None:
|
|
504
|
+
if TQDM_AVAILABLE and verbose:
|
|
505
|
+
iterator: enumerate | tqdm.tqdm = tqdm.tqdm(
|
|
506
|
+
np.random.choice(
|
|
507
|
+
len(data_array),
|
|
508
|
+
size=n_iteration,
|
|
509
|
+
replace=(n_iteration > len(data_array)),
|
|
510
|
+
),
|
|
511
|
+
total=n_iteration,
|
|
512
|
+
desc="Training",
|
|
513
|
+
)
|
|
514
|
+
else:
|
|
515
|
+
iterator = enumerate(
|
|
516
|
+
np.random.choice(
|
|
517
|
+
len(data_array),
|
|
518
|
+
size=n_iteration,
|
|
519
|
+
replace=(n_iteration > len(data_array)),
|
|
520
|
+
)
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
for it, i in iterator: # type: ignore
|
|
524
|
+
# Calculating decaying alpha and sigma parameters for updating weights
|
|
525
|
+
alpha = self._learning_rate_decay(self._learning_rate, it, n_iteration)
|
|
526
|
+
sigma = self._neighborhood_radius_decay(
|
|
527
|
+
self._neighborhood_radius, it, n_iteration
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
# Finding winner node (best-matching unit)
|
|
531
|
+
winner = self.winner(data_array[i])
|
|
532
|
+
|
|
533
|
+
# Updating weights, based on current neighborhood function
|
|
534
|
+
self._weights += (
|
|
535
|
+
alpha
|
|
536
|
+
* self._neighborhood_function(winner, sigma)[..., None]
|
|
537
|
+
* (data_array[i] - self._weights)
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
# Print progress, if verbose is activated and tqdm is not available
|
|
541
|
+
if verbose and not TQDM_AVAILABLE:
|
|
542
|
+
print(
|
|
543
|
+
"Iteration:",
|
|
544
|
+
it,
|
|
545
|
+
"/",
|
|
546
|
+
n_iteration,
|
|
547
|
+
sep=" ",
|
|
548
|
+
end="\r",
|
|
549
|
+
flush=True,
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
def _train_sequential(
|
|
553
|
+
self, data_array: np.ndarray, n_iteration: int, verbose: bool
|
|
554
|
+
) -> None:
|
|
555
|
+
if TQDM_AVAILABLE and verbose:
|
|
556
|
+
iterator: enumerate | tqdm.tqdm = tqdm.tqdm(
|
|
557
|
+
enumerate(data_array),
|
|
558
|
+
total=n_iteration,
|
|
559
|
+
desc="Training",
|
|
560
|
+
)
|
|
561
|
+
else:
|
|
562
|
+
iterator = enumerate(data_array)
|
|
563
|
+
|
|
564
|
+
for it, i in iterator: # type: ignore
|
|
565
|
+
# Calculating decaying alpha and sigma parameters for updating weights
|
|
566
|
+
alpha = self._learning_rate_decay(self._learning_rate, it, n_iteration)
|
|
567
|
+
sigma = self._neighborhood_radius_decay(
|
|
568
|
+
self._neighborhood_radius, it, n_iteration
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# Finding winner node (best-matching unit)
|
|
572
|
+
winner = self.winner(i)
|
|
573
|
+
|
|
574
|
+
# Updating weights, based on current neighborhood function
|
|
575
|
+
self._weights += (
|
|
576
|
+
alpha
|
|
577
|
+
* self._neighborhood_function(winner, sigma)[..., None]
|
|
578
|
+
* (i - self._weights)
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
# Print progress, if verbose is activated and tqdm is not available
|
|
582
|
+
if verbose and not TQDM_AVAILABLE:
|
|
583
|
+
print(
|
|
584
|
+
"Iteration:",
|
|
585
|
+
it,
|
|
586
|
+
"/",
|
|
587
|
+
n_iteration,
|
|
588
|
+
sep=" ",
|
|
589
|
+
end="\r",
|
|
590
|
+
flush=True,
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
def _train_batch(
|
|
594
|
+
self, data_array: np.ndarray, n_iteration: int, verbose: bool
|
|
595
|
+
) -> None:
|
|
596
|
+
if TQDM_AVAILABLE and verbose:
|
|
597
|
+
iterator: range | tqdm.tqdm = tqdm.tqdm(
|
|
598
|
+
range(n_iteration), total=n_iteration, desc="Training"
|
|
599
|
+
)
|
|
600
|
+
else:
|
|
601
|
+
iterator = range(n_iteration)
|
|
602
|
+
|
|
603
|
+
for it in iterator:
|
|
604
|
+
# Calculating decaying sigma
|
|
605
|
+
sigma = self._neighborhood_radius_decay(
|
|
606
|
+
self._neighborhood_radius, it, n_iteration
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
# For each node, create a list of instances associated to it
|
|
610
|
+
winner_map = self.winner_map(data_array)
|
|
611
|
+
|
|
612
|
+
# Calculate the weighted average of all instances in the neighborhood of each node
|
|
613
|
+
new_weights = np.zeros(self._weights.shape)
|
|
614
|
+
for i in winner_map.keys():
|
|
615
|
+
neig = self._neighborhood_function(i, sigma)
|
|
616
|
+
upper, bottom = np.zeros(self._input_len), 0.0
|
|
617
|
+
for j in winner_map.keys():
|
|
618
|
+
upper += neig[j] * np.sum(winner_map[j], axis=0)
|
|
619
|
+
bottom += neig[j] * len(winner_map[j])
|
|
620
|
+
|
|
621
|
+
# Only update if there is any instance associated with the winner node
|
|
622
|
+
# or its neighbors
|
|
623
|
+
if bottom != 0:
|
|
624
|
+
new_weights[i] = upper / bottom
|
|
625
|
+
|
|
626
|
+
# Update all nodes concurrently
|
|
627
|
+
self._weights = new_weights
|
|
628
|
+
|
|
629
|
+
# Print progress, if verbose is activated and tqdm is not available
|
|
630
|
+
if verbose and not TQDM_AVAILABLE:
|
|
631
|
+
print(
|
|
632
|
+
"Iteration:",
|
|
633
|
+
it,
|
|
634
|
+
"/",
|
|
635
|
+
n_iteration,
|
|
636
|
+
sep=" ",
|
|
637
|
+
end="\r",
|
|
638
|
+
flush=True,
|
|
639
|
+
)
|
|
640
|
+
|
|
562
641
|
def weight_initialization(
|
|
563
642
|
self,
|
|
564
643
|
mode: str = "random",
|
|
565
|
-
**kwargs:
|
|
644
|
+
**kwargs: np.ndarray | pd.DataFrame | list | str | int
|
|
566
645
|
) -> None:
|
|
567
|
-
"""
|
|
568
|
-
|
|
646
|
+
"""Function for weight initialization of the self-organizing map.
|
|
647
|
+
|
|
648
|
+
Calls other methods for each initialization mode.
|
|
569
649
|
|
|
570
650
|
:param mode: str: Initialization mode. May be either 'random', 'linear', or 'sample'.
|
|
571
651
|
Note: Each initialization method may require multiple additional arguments in kwargs.
|
|
572
652
|
:param kwargs:
|
|
573
|
-
For 'random' initialization mode, 'sample_mode': str may be provided to determine
|
|
574
|
-
'sample_mode' may be either 'standard_normal' (default) or 'uniform'
|
|
575
|
-
For 'random' and 'sample' modes, 'random_seed': int may be provided for the random
|
|
576
|
-
For 'sample' and 'linear' modes, 'data': array-like must be provided
|
|
653
|
+
For 'random' initialization mode, 'sample_mode': str may be provided to determine
|
|
654
|
+
the sampling mode. 'sample_mode' may be either 'standard_normal' (default) or 'uniform'
|
|
655
|
+
For 'random' and 'sample' modes, 'random_seed': int may be provided for the random
|
|
656
|
+
value generator. For 'sample' and 'linear' modes, 'data': array-like must be provided
|
|
657
|
+
for sampling/PCA.
|
|
577
658
|
"""
|
|
578
|
-
modes = {
|
|
659
|
+
modes: dict[str, Callable[..., None]] = {
|
|
579
660
|
"random": self._weight_initialization_random,
|
|
580
661
|
"linear": self._weight_initialization_linear,
|
|
581
662
|
"sample": self._weight_initialization_sample,
|
|
582
663
|
}
|
|
583
664
|
try:
|
|
584
665
|
modes[mode](**kwargs)
|
|
585
|
-
except KeyError:
|
|
666
|
+
except KeyError as exc:
|
|
586
667
|
raise ValueError(
|
|
587
668
|
"Invalid value for 'mode' parameter. Value should be in "
|
|
588
669
|
+ str(modes.keys())
|
|
589
|
-
)
|
|
670
|
+
) from exc
|
|
590
671
|
|
|
591
672
|
def _weight_initialization_random(
|
|
592
|
-
self, sample_mode: str = "standard_normal", random_seed:
|
|
673
|
+
self, sample_mode: str = "standard_normal", random_seed: int | None = None
|
|
593
674
|
) -> None:
|
|
594
|
-
"""
|
|
595
|
-
Random initialization method. Assigns weights from a random distribution defined by 'sample_mode'.
|
|
675
|
+
"""Random initialization method.
|
|
596
676
|
|
|
597
|
-
|
|
677
|
+
Assigns weights from a random distribution defined by 'sample_mode'.
|
|
678
|
+
|
|
679
|
+
:param sample_mode: str: Distribution for random sampling.
|
|
680
|
+
May be either 'uniform' or 'standard_normal'.
|
|
598
681
|
Defaults to 'standard_normal'.
|
|
599
682
|
:param random_seed: int or None: Seed for NumPy random value generator. Defaults to None.
|
|
600
683
|
"""
|
|
@@ -605,37 +688,35 @@ class SOM:
|
|
|
605
688
|
|
|
606
689
|
# Seed numpy random generator
|
|
607
690
|
if random_seed is None:
|
|
608
|
-
random_seed = np.random.randint(np.
|
|
691
|
+
random_seed = np.random.randint(np.iinfo(np.int32).max)
|
|
609
692
|
else:
|
|
610
|
-
random_seed =
|
|
693
|
+
random_seed = int(random_seed)
|
|
611
694
|
np.random.seed(random_seed)
|
|
612
695
|
|
|
613
696
|
# Initialize weights randomly
|
|
614
697
|
try:
|
|
615
698
|
self._weights = sample_modes[sample_mode](size=self._weights.shape)
|
|
616
|
-
except KeyError:
|
|
699
|
+
except KeyError as exc:
|
|
617
700
|
raise ValueError(
|
|
618
701
|
"Invalid value for 'sample_mode' parameter. Value should be in "
|
|
619
702
|
+ str(sample_modes.keys())
|
|
620
|
-
)
|
|
703
|
+
) from exc
|
|
621
704
|
|
|
622
705
|
def _weight_initialization_linear(
|
|
623
|
-
self, data:
|
|
706
|
+
self, data: np.ndarray | pd.DataFrame | list
|
|
624
707
|
) -> None:
|
|
625
|
-
"""
|
|
626
|
-
|
|
708
|
+
"""Linear initialization method.
|
|
709
|
+
|
|
710
|
+
Assigns weights spanning the hyperplane formed by the two first principal
|
|
627
711
|
components of 'data'.
|
|
628
712
|
|
|
629
|
-
This is the recommended initialization method, as it may lead to a faster convergence.
|
|
630
|
-
initialization modes, this method is deterministic based on the input dataset.
|
|
713
|
+
This is the recommended initialization method, as it may lead to a faster convergence.
|
|
714
|
+
Unlike other initialization modes, this method is deterministic based on the input dataset.
|
|
631
715
|
|
|
632
716
|
:param data: array-like: Dataset for weight initialization w/ PCA.
|
|
633
717
|
"""
|
|
634
718
|
# Convert data to numpy array for training
|
|
635
|
-
|
|
636
|
-
data_array = data.to_numpy()
|
|
637
|
-
else:
|
|
638
|
-
data_array = np.array(data)
|
|
719
|
+
data_array = self._data_to_numpy(data)
|
|
639
720
|
|
|
640
721
|
# Perform PCA w/ sklearn
|
|
641
722
|
data_array = sklearn.preprocessing.StandardScaler().fit_transform(data_array)
|
|
@@ -650,36 +731,34 @@ class SOM:
|
|
|
650
731
|
|
|
651
732
|
def _weight_initialization_sample(
|
|
652
733
|
self,
|
|
653
|
-
data:
|
|
654
|
-
random_seed:
|
|
734
|
+
data: np.ndarray | pd.DataFrame | list,
|
|
735
|
+
random_seed: int | None = None,
|
|
655
736
|
) -> None:
|
|
656
|
-
"""
|
|
657
|
-
Initialization method. Assigns weights to random samples from an input dataset.
|
|
737
|
+
"""Initialization method. Assigns weights to random samples from an input dataset.
|
|
658
738
|
|
|
659
739
|
:param data: Dataset for weight initialization/sampling.
|
|
660
740
|
:param random_seed: int or None: Seed for NumPy random value generator. Defaults to None.
|
|
661
741
|
"""
|
|
662
742
|
# Seed numpy random generator
|
|
663
743
|
if random_seed is None:
|
|
664
|
-
random_seed = np.random.randint(np.
|
|
744
|
+
random_seed = np.random.randint(np.iinfo(np.int32).max)
|
|
665
745
|
else:
|
|
666
|
-
random_seed =
|
|
746
|
+
random_seed = int(random_seed)
|
|
667
747
|
np.random.seed(random_seed)
|
|
668
748
|
|
|
669
749
|
# Convert data to numpy array for training
|
|
670
|
-
|
|
671
|
-
data_array = data.to_numpy()
|
|
672
|
-
else:
|
|
673
|
-
data_array = np.array(data)
|
|
750
|
+
data_array = self._data_to_numpy(data)
|
|
674
751
|
|
|
675
752
|
# Assign weights to random samples from dataset
|
|
676
|
-
sample_size = self._shape[0] * self._shape[1]
|
|
753
|
+
sample_size: np.uint = self._shape[0] * self._shape[1]
|
|
677
754
|
sample = np.random.choice(
|
|
678
|
-
len(data_array),
|
|
755
|
+
len(data_array),
|
|
756
|
+
size=int(sample_size),
|
|
757
|
+
replace=bool(sample_size > len(data_array)),
|
|
679
758
|
)
|
|
680
759
|
self._weights = data_array[sample].reshape(self._weights.shape)
|
|
681
760
|
|
|
682
|
-
def _gaussian(self, c:
|
|
761
|
+
def _gaussian(self, c: tuple[int, ...], sigma: float) -> np.ndarray:
|
|
683
762
|
"""
|
|
684
763
|
Gaussian neighborhood function, centered in c. Has support for cyclic arrays.
|
|
685
764
|
|
|
@@ -702,12 +781,12 @@ class SOM:
|
|
|
702
781
|
ay = np.exp(-np.power(dy, 2) / d)
|
|
703
782
|
return np.outer(ax, ay)
|
|
704
783
|
|
|
705
|
-
def _bubble(self, c:
|
|
784
|
+
def _bubble(self, c: tuple[int, ...], sigma: float) -> np.ndarray:
|
|
706
785
|
"""
|
|
707
786
|
Bubble neighborhood function, centered in c. Has support for cyclic arrays.
|
|
708
787
|
|
|
709
|
-
The neighbors of c are the nodes in the region of sigma positions in the vertical
|
|
710
|
-
around c.
|
|
788
|
+
The neighbors of c are the nodes in the region of sigma positions in the vertical
|
|
789
|
+
and horizontal directions around c.
|
|
711
790
|
|
|
712
791
|
:param c: (int, int): Center coordinates for gaussian function.
|
|
713
792
|
:param sigma: float: Spread variable for gaussian function.
|
|
@@ -733,3 +812,14 @@ class SOM:
|
|
|
733
812
|
ay[: int((c[1] + sigma) % self._shape[1] + 1)] = True
|
|
734
813
|
|
|
735
814
|
return np.outer(ax, ay).astype(int)
|
|
815
|
+
|
|
816
|
+
def _data_to_numpy(self, data: np.ndarray | pd.DataFrame | list) -> np.ndarray:
|
|
817
|
+
"""
|
|
818
|
+
Converts data to numpy array.
|
|
819
|
+
|
|
820
|
+
:param data: array-like: Dataset for training.
|
|
821
|
+
:return: np.ndarray: Numpy array of the dataset.
|
|
822
|
+
"""
|
|
823
|
+
if isinstance(data, pd.DataFrame):
|
|
824
|
+
return data.to_numpy()
|
|
825
|
+
return np.array(data)
|
|
@@ -1,28 +1,26 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-som
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Python implementation of the Self-Organizing Map
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Author-email: msouza.andre@hotmail.com
|
|
8
|
-
License: MIT
|
|
9
|
-
|
|
10
|
-
Project-URL: Source, https://github.com/andremsouza/python-som
|
|
11
|
-
Keywords: som kohonen-map self-organizing-map machine-learning
|
|
5
|
+
Project-URL: Homepage, https://github.com/andremsouza/python-som
|
|
6
|
+
Project-URL: Issues, https://github.com/andremsouza/python-som/issues
|
|
7
|
+
Author-email: André Moreira Souza <msouza.andre@hotmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
12
10
|
Classifier: Development Status :: 3 - Alpha
|
|
13
11
|
Classifier: Intended Audience :: Developers
|
|
14
12
|
Classifier: Intended Audience :: Science/Research
|
|
15
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
-
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
19
15
|
Classifier: Programming Language :: Python :: 3
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.9
|
|
23
20
|
Requires-Dist: numpy
|
|
24
21
|
Requires-Dist: pandas
|
|
25
22
|
Requires-Dist: scikit-learn
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
26
24
|
|
|
27
25
|
# python-som
|
|
28
26
|
|
|
@@ -144,4 +142,4 @@ Volume 37,
|
|
|
144
142
|
2013,
|
|
145
143
|
Pages 52-65,
|
|
146
144
|
ISSN 0893-6080,
|
|
147
|
-
https://doi.org/10.1016/j.neunet.2012.09.018.
|
|
145
|
+
https://doi.org/10.1016/j.neunet.2012.09.018.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
python_som/__init__.py,sha256=MMEdaZxLE5MQsrmjYWhwQcC3Nj47QRLlPTtRiOJiSa8,33022
|
|
2
|
+
python_som-0.1.2.dist-info/METADATA,sha256=qg5ea52OLyI6ovd-7q8Ph_2ecDOXdzEGbYYkFPRTwUY,5028
|
|
3
|
+
python_som-0.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
4
|
+
python_som-0.1.2.dist-info/licenses/LICENSE,sha256=dMZQho8iIicWbAJQk1QaAXXvvJlaWSQ3a-9NkWxu_lc,1077
|
|
5
|
+
python_som-0.1.2.dist-info/RECORD,,
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
python_som/__init__.py,sha256=bKgFPV8FzkL2CQ09V86kywGyfXr-HvOD9ZT4VnWm-ck,30520
|
|
2
|
-
python_som-0.0.1a2.dist-info/LICENSE,sha256=dMZQho8iIicWbAJQk1QaAXXvvJlaWSQ3a-9NkWxu_lc,1077
|
|
3
|
-
python_som-0.0.1a2.dist-info/METADATA,sha256=4Ck4KJNCqIxRbjrgfLF1kX-efdQdWoZfegcxzRIF9-E,5096
|
|
4
|
-
python_som-0.0.1a2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
|
5
|
-
python_som-0.0.1a2.dist-info/top_level.txt,sha256=__rpeARBlUvNEMA9BWjqS7nbMeb_3JUR_QQndHHo5Mk,11
|
|
6
|
-
python_som-0.0.1a2.dist-info/RECORD,,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
python_som
|
|
File without changes
|