python-som 0.1.3__py3-none-any.whl → 0.2.0__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 +141 -22
- {python_som-0.1.3.dist-info → python_som-0.2.0.dist-info}/METADATA +31 -4
- python_som-0.2.0.dist-info/RECORD +5 -0
- {python_som-0.1.3.dist-info → python_som-0.2.0.dist-info}/WHEEL +1 -1
- python_som-0.1.3.dist-info/RECORD +0 -5
- {python_som-0.1.3.dist-info → python_som-0.2.0.dist-info}/licenses/LICENSE +0 -0
python_som/__init__.py
CHANGED
|
@@ -9,7 +9,7 @@ Features:
|
|
|
9
9
|
- Linear weight initialization (with PCA)
|
|
10
10
|
- Automatic selection of map size ratio (with PCA)
|
|
11
11
|
- Support for cyclic arrays, for toroidal or spherical maps
|
|
12
|
-
- Gaussian and
|
|
12
|
+
- Gaussian, Bubble and Mexican Hat neighborhood functions
|
|
13
13
|
- Support for custom decay functions
|
|
14
14
|
- Support for visualization (U-matrix, activation matrix)
|
|
15
15
|
- Support for supervised learning (label map)
|
|
@@ -124,7 +124,7 @@ class SOM:
|
|
|
124
124
|
- Linear weight initialization (with PCA)
|
|
125
125
|
- Automatic selection of map size ratio (with PCA)
|
|
126
126
|
- Support for cyclic arrays, for toroidal or spherical maps
|
|
127
|
-
- Gaussian and
|
|
127
|
+
- Gaussian, Bubble and Mexican hat neighborhood functions
|
|
128
128
|
- Support for custom decay functions
|
|
129
129
|
- Support for visualization (U-matrix, activation matrix)
|
|
130
130
|
- Support for supervised learning (label map)
|
|
@@ -190,7 +190,7 @@ class SOM:
|
|
|
190
190
|
variable. May be a predefined one from this package, or a custom function, with the
|
|
191
191
|
same parameters and return type. Defaults to _asymptotic_decay
|
|
192
192
|
:param neighborhood_function: str: Neighborhood function name for the training process.
|
|
193
|
-
May be either 'gaussian' or '
|
|
193
|
+
May be either 'gaussian', 'bubble' or 'mexicanhat'.
|
|
194
194
|
:param distance_function: function: Function for calculating distances/dissimilarities
|
|
195
195
|
between models of the network.
|
|
196
196
|
May be a predefined one from this package, or a custom function, with the same
|
|
@@ -242,10 +242,19 @@ class SOM:
|
|
|
242
242
|
self._learning_rate_decay = learning_rate_decay
|
|
243
243
|
self._neighborhood_radius = float(neighborhood_radius)
|
|
244
244
|
self._neighborhood_radius_decay = neighborhood_radius_decay
|
|
245
|
-
|
|
245
|
+
neighborhood_functions = {
|
|
246
246
|
"gaussian": self._gaussian,
|
|
247
247
|
"bubble": self._bubble,
|
|
248
|
-
|
|
248
|
+
"mexicanhat": self._mexicanhat,
|
|
249
|
+
}
|
|
250
|
+
try:
|
|
251
|
+
self._neighborhood_function = neighborhood_functions[neighborhood_function]
|
|
252
|
+
except KeyError as exc:
|
|
253
|
+
raise ValueError(
|
|
254
|
+
"Invalid value for 'neighborhood_function' parameter. Value should be in "
|
|
255
|
+
+ str(list(neighborhood_functions.keys()))
|
|
256
|
+
) from exc
|
|
257
|
+
self._neighborhood_function_name = neighborhood_function
|
|
249
258
|
self._distance_function = distance_function
|
|
250
259
|
self._cyclic = (bool(cyclic_x), bool(cyclic_y))
|
|
251
260
|
self._neigx, self._neigy = np.arange(self._shape[0]), np.arange(self._shape[1])
|
|
@@ -484,6 +493,18 @@ class SOM:
|
|
|
484
493
|
elif mode == "sequential":
|
|
485
494
|
self._train_sequential(data_array, n_iteration, verbose)
|
|
486
495
|
elif mode == "batch":
|
|
496
|
+
if self._neighborhood_function_name == "mexicanhat":
|
|
497
|
+
# The batch update of Kohonen (2013), Eq. (8), is a weighted mean whose denominator
|
|
498
|
+
# is sum_j n_j * h_ji. That is only well defined for a non-negative neighborhood
|
|
499
|
+
# function. The mexican hat is signed by construction, so the denominator can vanish
|
|
500
|
+
# or turn negative, which makes the update blow up or invert the sign of the
|
|
501
|
+
# correction. Use a stepwise mode instead.
|
|
502
|
+
raise ValueError(
|
|
503
|
+
"The 'mexicanhat' neighborhood function cannot be used with the 'batch'"
|
|
504
|
+
" training mode: the weighted mean of Kohonen (2013), Eq. (8), is undefined"
|
|
505
|
+
" for a neighborhood function that takes negative values, as its denominator"
|
|
506
|
+
" is not sign-definite. Use mode='random' or mode='sequential' instead."
|
|
507
|
+
)
|
|
487
508
|
self._train_batch(data_array, n_iteration, verbose)
|
|
488
509
|
else:
|
|
489
510
|
# Invalid training mode value
|
|
@@ -641,7 +662,7 @@ class SOM:
|
|
|
641
662
|
def weight_initialization(
|
|
642
663
|
self,
|
|
643
664
|
mode: str = "random",
|
|
644
|
-
**kwargs: np.ndarray | pd.DataFrame | list | str | int
|
|
665
|
+
**kwargs: np.ndarray | pd.DataFrame | list | str | int,
|
|
645
666
|
) -> None:
|
|
646
667
|
"""Function for weight initialization of the self-organizing map.
|
|
647
668
|
|
|
@@ -758,28 +779,70 @@ class SOM:
|
|
|
758
779
|
)
|
|
759
780
|
self._weights = data_array[sample].reshape(self._weights.shape)
|
|
760
781
|
|
|
782
|
+
@staticmethod
|
|
783
|
+
def _axis_offsets(
|
|
784
|
+
coords: np.ndarray, center: int, length: float, cyclic: bool
|
|
785
|
+
) -> np.ndarray:
|
|
786
|
+
"""
|
|
787
|
+
Signed offsets from 'center' to each coordinate along one axis of the grid.
|
|
788
|
+
|
|
789
|
+
When the axis is cyclic, the minimum-image convention is applied, i.e., each offset is
|
|
790
|
+
folded into the interval [-length/2, length/2), so that the shortest way around the torus
|
|
791
|
+
is used. Both tails must be folded: an offset of -9 on an axis of length 10 represents a
|
|
792
|
+
distance of 1, not 9.
|
|
793
|
+
|
|
794
|
+
:param coords: np.ndarray: Coordinates along the axis, i.e., np.arange(length).
|
|
795
|
+
:param center: int: Coordinate of the center (winner node) along the axis.
|
|
796
|
+
:param length: float: Number of nodes along the axis.
|
|
797
|
+
:param cyclic: bool: Whether the axis wraps around.
|
|
798
|
+
:return: np.ndarray: Signed offsets from center to each coordinate.
|
|
799
|
+
"""
|
|
800
|
+
d = coords - center
|
|
801
|
+
if cyclic:
|
|
802
|
+
d = (d + length / 2) % length - length / 2
|
|
803
|
+
return d
|
|
804
|
+
|
|
805
|
+
def _sqdist(self, c: tuple[int, ...], sigma: float) -> np.ndarray:
|
|
806
|
+
"""
|
|
807
|
+
Squared geometric distance from the center c to every node of the grid.
|
|
808
|
+
|
|
809
|
+
This is the sqdist(c, i) term of Kohonen (2013), Eq. (5). Every neighborhood function must
|
|
810
|
+
be a function of this scalar quantity: the neighborhood describes lateral interaction over
|
|
811
|
+
a metric space, so it depends on the distance between two nodes and not on the individual
|
|
812
|
+
axis offsets. Only the gaussian is separable into a product of per-axis terms, and that is
|
|
813
|
+
a property of the exponential rather than of neighborhood functions in general.
|
|
814
|
+
|
|
815
|
+
:param c: (int, int): Center coordinates.
|
|
816
|
+
:param sigma: float: Spread variable, validated here for all neighborhood functions.
|
|
817
|
+
:return: np.ndarray: Squared distances from c to each node, with the shape of the network.
|
|
818
|
+
:raises ValueError: If sigma is not strictly positive.
|
|
819
|
+
"""
|
|
820
|
+
if not np.isfinite(sigma) or sigma <= 0:
|
|
821
|
+
raise ValueError(
|
|
822
|
+
"The neighborhood radius 'sigma' must be a finite positive number, got "
|
|
823
|
+
+ str(sigma)
|
|
824
|
+
)
|
|
825
|
+
dx = self._axis_offsets(
|
|
826
|
+
self._neigx, c[0], float(self._shape[0]), self._cyclic[0]
|
|
827
|
+
)
|
|
828
|
+
dy = self._axis_offsets(
|
|
829
|
+
self._neigy, c[1], float(self._shape[1]), self._cyclic[1]
|
|
830
|
+
)
|
|
831
|
+
return np.add.outer(np.power(dx, 2), np.power(dy, 2))
|
|
832
|
+
|
|
761
833
|
def _gaussian(self, c: tuple[int, ...], sigma: float) -> np.ndarray:
|
|
762
834
|
"""
|
|
763
835
|
Gaussian neighborhood function, centered in c. Has support for cyclic arrays.
|
|
764
836
|
|
|
837
|
+
Implements h_ci = exp(-sqdist(c, i) / (2 * sigma^2)), i.e., Eq. (5) of Kohonen (2013)
|
|
838
|
+
with the learning rate factored out, so that h_cc = 1.
|
|
839
|
+
|
|
765
840
|
:param c: (int, int): Center coordinates for gaussian function.
|
|
766
|
-
:param sigma: float: Spread variable for gaussian function.
|
|
841
|
+
:param sigma: float: Spread variable for gaussian function. Must be positive.
|
|
767
842
|
:return: np.ndarray: Gaussian, centered in c, over all the weights of the network.
|
|
843
|
+
:raises ValueError: If sigma is not strictly positive.
|
|
768
844
|
"""
|
|
769
|
-
|
|
770
|
-
d = 2 * sigma * sigma
|
|
771
|
-
# Calculate vertical and horizontal distances
|
|
772
|
-
dx = self._neigx - c[0]
|
|
773
|
-
dy = self._neigy - c[1]
|
|
774
|
-
# If using cyclic arrays, perform fold back distance
|
|
775
|
-
if self._cyclic[0]:
|
|
776
|
-
dx[dx > self._shape[0] / 2] -= self._shape[0]
|
|
777
|
-
if self._cyclic[1]:
|
|
778
|
-
dy[dy > self._shape[1] / 2] -= self._shape[1]
|
|
779
|
-
# Calculate gaussian centered in c
|
|
780
|
-
ax = np.exp(-np.power(dx, 2) / d)
|
|
781
|
-
ay = np.exp(-np.power(dy, 2) / d)
|
|
782
|
-
return np.outer(ax, ay)
|
|
845
|
+
return np.exp(-self._sqdist(c, sigma) / (2 * sigma * sigma))
|
|
783
846
|
|
|
784
847
|
def _bubble(self, c: tuple[int, ...], sigma: float) -> np.ndarray:
|
|
785
848
|
"""
|
|
@@ -788,10 +851,26 @@ class SOM:
|
|
|
788
851
|
The neighbors of c are the nodes in the region of sigma positions in the vertical
|
|
789
852
|
and horizontal directions around c.
|
|
790
853
|
|
|
854
|
+
This is the truncated inner, excitatory lobe of the mexican hat lateral-interaction
|
|
855
|
+
function; cf. Vrieze (1995), p. 85: "In Kohonen networks usually only the inner stimulation
|
|
856
|
+
area is used, i.e., when a neuron i fires, a positive feedback takes place for all neurons
|
|
857
|
+
i', whose distance to i is smaller than some given number rho."
|
|
858
|
+
|
|
859
|
+
Unlike the other neighborhood functions, a radius of zero is admissible here: it selects
|
|
860
|
+
the winner alone, which is well defined, whereas for the gaussian and the mexican hat a
|
|
861
|
+
zero radius is a division by zero.
|
|
862
|
+
|
|
791
863
|
:param c: (int, int): Center coordinates for gaussian function.
|
|
792
|
-
:param sigma: float: Spread variable for gaussian function.
|
|
864
|
+
:param sigma: float: Spread variable for gaussian function. Must be finite and
|
|
865
|
+
non-negative.
|
|
793
866
|
:return: np.ndarray: Neighborhood matrix, centered in c.
|
|
867
|
+
:raises ValueError: If sigma is negative or not finite.
|
|
794
868
|
"""
|
|
869
|
+
if not np.isfinite(sigma) or sigma < 0:
|
|
870
|
+
raise ValueError(
|
|
871
|
+
"The neighborhood radius 'sigma' must be a finite non-negative number, got "
|
|
872
|
+
+ str(sigma)
|
|
873
|
+
)
|
|
795
874
|
# Convert sigma to integer
|
|
796
875
|
sigma = int(np.around(sigma))
|
|
797
876
|
|
|
@@ -813,6 +892,46 @@ class SOM:
|
|
|
813
892
|
|
|
814
893
|
return np.outer(ax, ay).astype(int)
|
|
815
894
|
|
|
895
|
+
def _mexicanhat(self, c: tuple[int, ...], sigma: float) -> np.ndarray:
|
|
896
|
+
"""
|
|
897
|
+
Mexican Hat neighborhood function, centered in c. Has support for cyclic arrays.
|
|
898
|
+
|
|
899
|
+
Also known as the Ricker wavelet, or the Laplacian of Gaussian. This is the biologically
|
|
900
|
+
motivated lateral-interaction function: nodes near the winner are excited, nodes beyond a
|
|
901
|
+
certain distance are inhibited, and the inhibition vanishes as the distance grows further
|
|
902
|
+
(Vrieze, 1995, Fig. 3).
|
|
903
|
+
|
|
904
|
+
Implements the isotropic 2-D form as a function of u = sqdist(c, i) / (2 * sigma^2):
|
|
905
|
+
|
|
906
|
+
h_ci = (1 - u) * exp(-u)
|
|
907
|
+
|
|
908
|
+
normalized so that h_cc = 1. It crosses zero at a radius of sqrt(2) * sigma, reaches its
|
|
909
|
+
minimum of -exp(-2) ~= -0.135 at a radius of 2 * sigma, and decays to zero thereafter.
|
|
910
|
+
|
|
911
|
+
Note that this is *not* the outer product of two 1-D Ricker wavelets. Unlike the gaussian,
|
|
912
|
+
the Ricker wavelet is not multiplicatively separable, and such a product is not a function
|
|
913
|
+
of the distance between two nodes: it would be positive in the diagonal quadrants, where
|
|
914
|
+
both 1-D factors are negative, placing a spurious excitatory lobe exactly where the
|
|
915
|
+
function must inhibit. The neighborhood function must depend on the grid distance alone,
|
|
916
|
+
cf. sqdist(c, i) in Kohonen (2013), Eq. (5), and the "lateral distance" abscissa of
|
|
917
|
+
Vrieze (1995), Fig. 3.
|
|
918
|
+
|
|
919
|
+
The '_bubble' neighborhood function is the truncated inner (excitatory) lobe of this same
|
|
920
|
+
lateral-interaction function; cf. Vrieze (1995), p. 85.
|
|
921
|
+
|
|
922
|
+
:param c: (int, int): Center coordinates for mexican hat function.
|
|
923
|
+
:param sigma: float: Spread variable for mexican hat function. Must be positive.
|
|
924
|
+
:return: np.ndarray: Mexican Hat, centered in c, over all the weights of the network.
|
|
925
|
+
:raises ValueError: If sigma is not strictly positive.
|
|
926
|
+
|
|
927
|
+
References:
|
|
928
|
+
O. J. Vrieze, Kohonen network, in: Artificial Neural Networks: An Introduction to ANN
|
|
929
|
+
Theory and Practice, Lecture Notes in Computer Science, vol. 931, Springer, 1995,
|
|
930
|
+
pp. 83-100, https://doi.org/10.1007/BFb0027024.
|
|
931
|
+
"""
|
|
932
|
+
u = self._sqdist(c, sigma) / (2 * sigma * sigma)
|
|
933
|
+
return (1.0 - u) * np.exp(-u)
|
|
934
|
+
|
|
816
935
|
def _data_to_numpy(self, data: np.ndarray | pd.DataFrame | list) -> np.ndarray:
|
|
817
936
|
"""
|
|
818
937
|
Converts data to numpy array.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-som
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Python implementation of the Self-Organizing Map
|
|
5
5
|
Project-URL: Homepage, https://github.com/andremsouza/python-som
|
|
6
6
|
Project-URL: Issues, https://github.com/andremsouza/python-som/issues
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
16
16
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
-
Requires-Python: >=3.
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
20
|
Requires-Dist: numpy
|
|
21
21
|
Requires-Dist: pandas
|
|
22
22
|
Requires-Dist: scikit-learn
|
|
@@ -37,7 +37,7 @@ Most features were implemented using NumPy, with Scikit-learn for standardizatio
|
|
|
37
37
|
* Linear weight initialization (with PCA)
|
|
38
38
|
* Automatic selection of map size ratio (with PCA)
|
|
39
39
|
* Support for cyclic arrays, for toroidal or spherical maps
|
|
40
|
-
* Gaussian and
|
|
40
|
+
* Gaussian, Bubble and Mexican hat neighborhood functions
|
|
41
41
|
* Support for custom decay functions
|
|
42
42
|
* Support for visualization (U-matrix, activation matrix)
|
|
43
43
|
* Support for supervised learning (label map)
|
|
@@ -134,6 +134,22 @@ The following are lists of public methods and functions currently available in t
|
|
|
134
134
|
* SOM.train
|
|
135
135
|
* SOM.weight_initialization
|
|
136
136
|
|
|
137
|
+
## Neighborhood functions
|
|
138
|
+
The `neighborhood_function` parameter selects how the winner's correction is spread over the grid.
|
|
139
|
+
All three are functions of the distance between two nodes in the grid, `sqdist(c, i)` in Eq. (5) of
|
|
140
|
+
Kohonen (2013):
|
|
141
|
+
|
|
142
|
+
| Name | Shape | Notes |
|
|
143
|
+
| --- | --- | --- |
|
|
144
|
+
| `'gaussian'` | `exp(-r² / 2σ²)` | Strictly positive and monotonically decreasing. The default. |
|
|
145
|
+
| `'bubble'` | `1` for `r ≤ σ`, else `0` | The truncated inner lobe of the mexican hat. Vrieze (1995) notes this flat choice is "just as effective and sometimes even better". |
|
|
146
|
+
| `'mexicanhat'` | `(1 - u)·exp(-u)`, `u = r² / 2σ²` | Excitatory near the winner, inhibitory beyond it. Crosses zero at `r = √2·σ`, reaches its minimum of `-e⁻² ≈ -0.135` at `r = 2σ`. |
|
|
147
|
+
|
|
148
|
+
The mexican hat takes negative values, so it **cannot be used with `mode='batch'`**: the batch update
|
|
149
|
+
of Kohonen (2013), Eq. (8), is a weighted mean whose denominator `Σⱼ nⱼ·hⱼᵢ` is not sign-definite for
|
|
150
|
+
a signed neighborhood function. Use `mode='random'` or `mode='sequential'` instead; passing
|
|
151
|
+
`mode='batch'` raises a `ValueError`.
|
|
152
|
+
|
|
137
153
|
## References
|
|
138
154
|
This implemetation was based on the following paper, by Professor Teuvo Kohonen:
|
|
139
155
|
|
|
@@ -144,4 +160,15 @@ Volume 37,
|
|
|
144
160
|
2013,
|
|
145
161
|
Pages 52-65,
|
|
146
162
|
ISSN 0893-6080,
|
|
147
|
-
https://doi.org/10.1016/j.neunet.2012.09.018.
|
|
163
|
+
https://doi.org/10.1016/j.neunet.2012.09.018.
|
|
164
|
+
|
|
165
|
+
The mexican hat neighborhood function follows the lateral-interaction formulation in:
|
|
166
|
+
|
|
167
|
+
O. J. Vrieze,
|
|
168
|
+
Kohonen network,
|
|
169
|
+
in: Artificial Neural Networks: An Introduction to ANN Theory and Practice,
|
|
170
|
+
Lecture Notes in Computer Science, Volume 931,
|
|
171
|
+
Springer, Berlin, Heidelberg,
|
|
172
|
+
1995,
|
|
173
|
+
Pages 83-100,
|
|
174
|
+
https://doi.org/10.1007/BFb0027024.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
python_som/__init__.py,sha256=xG3Dq-6Wipv57lTsHzZWUC-UmYmtF5MP3BV5yC3_jkI,39881
|
|
2
|
+
python_som-0.2.0.dist-info/METADATA,sha256=CkwnAxayiH7G3lf1nbt6KMpvzl3d9lrqdxLKGS5M8BU,6542
|
|
3
|
+
python_som-0.2.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
4
|
+
python_som-0.2.0.dist-info/licenses/LICENSE,sha256=dMZQho8iIicWbAJQk1QaAXXvvJlaWSQ3a-9NkWxu_lc,1077
|
|
5
|
+
python_som-0.2.0.dist-info/RECORD,,
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
python_som/__init__.py,sha256=MMEdaZxLE5MQsrmjYWhwQcC3Nj47QRLlPTtRiOJiSa8,33022
|
|
2
|
-
python_som-0.1.3.dist-info/METADATA,sha256=ClgvuD9gQ1hoHuagaRpbM55vG6QLWm4jXDSBNkFpaUs,5084
|
|
3
|
-
python_som-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
4
|
-
python_som-0.1.3.dist-info/licenses/LICENSE,sha256=dMZQho8iIicWbAJQk1QaAXXvvJlaWSQ3a-9NkWxu_lc,1077
|
|
5
|
-
python_som-0.1.3.dist-info/RECORD,,
|
|
File without changes
|