File: som.py

package info (click to toggle)
python-pyclustering 0.10.1.2-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 11,128 kB
  • sloc: cpp: 38,888; python: 24,311; sh: 384; makefile: 105
file content (995 lines) | stat: -rwxr-xr-x 39,276 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
"""!

@brief Neural Network: Self-Organized Feature Map
@details Implementation based on paper @cite article::nnet::som::1, @cite article::nnet::som::2.

@authors Andrei Novikov (pyclustering@yandex.ru)
@date 2014-2020
@copyright BSD-3-Clause

"""

import math
import random

import matplotlib.pyplot as plt

import pyclustering.core.som_wrapper as wrapper

from pyclustering.core.wrapper import ccore_library

from pyclustering.utils import euclidean_distance_square
from pyclustering.utils.dimension import dimension_info

from enum import IntEnum


class type_conn(IntEnum):
    """!
    @brief Enumeration of connection types for SOM.
    
    @see som
    
    """

    ## Grid type of connections when each oscillator has connections with left, upper, right, lower neighbors.
    grid_four = 0

    ## Grid type of connections when each oscillator has connections with left, upper-left, upper, upper-right, right, right-lower, lower, lower-left neighbors.
    grid_eight = 1

    ## Grid type of connections when each oscillator has connections with left, upper-left, upper-right, right, right-lower, lower-left neighbors.
    honeycomb = 2

    ## Grid type of connections when existance of each connection is defined by the SOM rule on each step of simulation.
    func_neighbor = 3


class type_init(IntEnum):
    """!
    @brief Enumeration of initialization types for SOM.
    
    @see som
    
    """

    ## Weights are randomly distributed using Gaussian distribution (0, 1).
    random = 0

    ## Weights are randomly distributed using Gaussian distribution (input data centroid, 1).
    random_centroid = 1

    ## Weights are randomly distrbiuted using Gaussian distribution (input data centroid, surface of input data).
    random_surface = 2

    ## Weights are distributed as a uniform grid that covers whole surface of the input data.
    uniform_grid = 3


class som_parameters:
    """!
    @brief Represents SOM parameters.
    
    """

    def __init__(self):
        """!
        @brief Creates SOM parameters.
        
        """

        ## Defines an initialization way for neuron weights (random, random in center of the input data, random distributed in data, ditributed in line with uniform grid).
        self.init_type = type_init.uniform_grid

        ## Initial radius. If the initial radius is not specified (equals to `None`) then it will be calculated by SOM.
        self.init_radius = None

        ## Rate of learning.
        self.init_learn_rate = 0.1

        ## Condition that defines when the learining process should be stopped. It is used when the autostop mode is on.
        self.adaptation_threshold = 0.001

        ## Seed for random state (by default is `None`, current system time is used).
        self.random_state = None


class som:
    """!
    @brief Represents self-organized feature map (SOM).
    @details The self-organizing feature map (SOM) method is a powerful tool for the visualization of
             of high-dimensional data. It converts complex, nonlinear statistical relationships between
             high-dimensional data into simple geometric relationships on a low-dimensional display.
    
    @details `ccore` option can be specified in order to control using C++ implementation of pyclustering library. By
              default C++ implementation is on. C++ implementation improves performance of the self-organized feature
              map.
    
    Example:
    @code
        import random

        from pyclustering.utils import read_sample
        from pyclustering.nnet.som import som, type_conn, type_init, som_parameters
        from pyclustering.samples.definitions import FCPS_SAMPLES

        # read sample 'Lsun' from file
        sample = read_sample(FCPS_SAMPLES.SAMPLE_LSUN)

        # create SOM parameters
        parameters = som_parameters()

        # create self-organized feature map with size 7x7
        rows = 10  # five rows
        cols = 10  # five columns
        structure = type_conn.grid_four;  # each neuron has max. four neighbors.
        network = som(rows, cols, structure, parameters)

        # train network on 'Lsun' sample during 100 epouchs.
        network.train(sample, 100)

        # simulate trained network using randomly modified point from input dataset.
        index_point = random.randint(0, len(sample) - 1)
        point = sample[index_point]  # obtain randomly point from data
        point[0] += random.random() * 0.2  # change randomly X-coordinate
        point[1] += random.random() * 0.2  # change randomly Y-coordinate
        index_winner = network.simulate(point)

        # check what are objects from input data are much close to randomly modified.
        index_similar_objects = network.capture_objects[index_winner]

        # neuron contains information of encoded objects
        print("Point '%s' is similar to objects with indexes '%s'." % (str(point), str(index_similar_objects)))
        print("Coordinates of similar objects:")
        for index in index_similar_objects: print("\tPoint:", sample[index])

        # result visualization:
        # show distance matrix (U-matrix).
        network.show_distance_matrix()

        # show density matrix (P-matrix).
        network.show_density_matrix()

        # show winner matrix.
        network.show_winner_matrix()

        # show self-organized map.
        network.show_network()
    @endcode
    
    There is a visualization of 'Target' sample that was done by the self-organized feature map:
    @image html target_som_processing.png
    
    """

    @property
    def size(self):
        """!
        @brief Return size of self-organized map that is defined by total number of neurons.

        @return (uint) Size of self-organized map (number of neurons).
        
        """

        if self.__ccore_som_pointer is not None:
            self._size = wrapper.som_get_size(self.__ccore_som_pointer)

        return self._size

    @property
    def weights(self):
        """!
        @brief Return weight of each neuron.

        @return (list) Weights of each neuron.
        
        """

        if self.__ccore_som_pointer is not None:
            self._weights = wrapper.som_get_weights(self.__ccore_som_pointer)

        return self._weights

    @property
    def awards(self):
        """!
        @brief Return amount of captured objects by each neuron after training.

        @return (list) Amount of captured objects by each neuron.

        @see train()
        
        """

        if self.__ccore_som_pointer is not None:
            self._award = wrapper.som_get_awards(self.__ccore_som_pointer)

        return self._award

    @property
    def capture_objects(self):
        """!
        @brief Returns indexes of captured objects by each neuron.
        @details For example, a network with size 2x2 has been trained on a sample with five objects. Suppose neuron #1
                  won an object with index `1`, neuron #2 won objects `0`, `3`, `4`, neuron #3 did not won anything and
                  finally neuron #4 won an object with index `2`. Thus, for this example we will have the following
                  output `[[1], [0, 3, 4], [], [2]]`.

        @return (list) Indexes of captured objects by each neuron.
        
        """

        if self.__ccore_som_pointer is not None:
            self._capture_objects = wrapper.som_get_capture_objects(self.__ccore_som_pointer)

        return self._capture_objects

    def __init__(self, rows, cols, conn_type=type_conn.grid_eight, parameters=None, ccore=True):
        """!
        @brief Constructor of self-organized map.
        
        @param[in] rows (uint): Number of neurons in the column (number of rows).
        @param[in] cols (uint): Number of neurons in the row (number of columns).
        @param[in] conn_type (type_conn): Type of connection between oscillators in the network (grid four, grid eight, honeycomb, function neighbour).
        @param[in] parameters (som_parameters): Other specific parameters.
        @param[in] ccore (bool): If True simulation is performed by CCORE library (C++ implementation of pyclustering).

        """

        # some of these parameters are required despite core implementation, for example, for network visualization.
        self._cols = cols

        self._rows = rows

        self._size = cols * rows

        self._conn_type = conn_type

        self._data = None

        self._neighbors = None

        self._local_radius = 0.0

        self._learn_rate = 0.0

        self.__ccore_som_pointer = None

        self._params = parameters or som_parameters()

        if self._params.init_radius is None:
            self._params.init_radius = self.__initialize_initial_radius(rows, cols)

        if (ccore is True) and ccore_library.workable():
            self.__ccore_som_pointer = wrapper.som_create(rows, cols, conn_type, self._params)

        else:
            # location
            self._location = self.__initialize_locations(rows, cols)

            # default weights
            self._weights = [[0.0]] * self._size

            # awards
            self._award = [0] * self._size

            # captured objects
            self._capture_objects = [[] for i in range(self._size)]

            # distances - calculate and store them only during training
            self._sqrt_distances = None

            # connections
            if conn_type != type_conn.func_neighbor:
                self._create_connections(conn_type)

    def __del__(self):
        """!
        @brief Destructor of the self-organized feature map.
        
        """

        if self.__ccore_som_pointer is not None:
            wrapper.som_destroy(self.__ccore_som_pointer)

    def __len__(self):
        """!
        @brief Returns size of the network that defines by amount of neuron in it.

        @return (uint) Size of self-organized map (amount of neurons).
        
        """

        return self._size

    def __getstate__(self):
        """
        @brief Returns state of SOM network that can be used to store network.

        """
        if self.__ccore_som_pointer is not None:
            self.__download_dump_from_ccore()
            return self.__get_dump_from_python(True)

        return self.__get_dump_from_python(False)

    def __setstate__(self, som_state):
        """
        @brief Set state of SOM network that can be used to load network.

        """
        if som_state['ccore'] is True and ccore_library.workable():
            self.__upload_dump_to_ccore(som_state['state'])
        else:
            self.__upload_dump_to_python(som_state['state'])

    def __initialize_initial_radius(self, rows, cols):
        """!
        @brief Initialize initial radius using map sizes.
        
        @param[in] rows (uint): Number of neurons in the column (number of rows).
        @param[in] cols (uint): Number of neurons in the row (number of columns).
        
        @return (list) Value of initial radius.
        
        """

        if (cols + rows) / 4.0 > 1.0:
            return 2.0

        elif (cols > 1) and (rows > 1):
            return 1.5

        else:
            return 1.0

    def __initialize_locations(self, rows, cols):
        """!
        @brief Initialize locations (coordinates in SOM grid) of each neurons in the map.
        
        @param[in] rows (uint): Number of neurons in the column (number of rows).
        @param[in] cols (uint): Number of neurons in the row (number of columns).
        
        @return (list) List of coordinates of each neuron in map.
        
        """

        location = list()
        for i in range(rows):
            for j in range(cols):
                location.append([float(i), float(j)])

        return location

    def __initialize_distances(self, size, location):
        """!
        @brief Initialize distance matrix in SOM grid.
        
        @param[in] size (uint): Amount of neurons in the network.
        @param[in] location (list): List of coordinates of each neuron in the network.
        
        @return (list) Distance matrix between neurons in the network.
        
        """
        sqrt_distances = [[[] for i in range(size)] for j in range(size)]
        for i in range(size):
            for j in range(i, size, 1):
                dist = euclidean_distance_square(location[i], location[j])
                sqrt_distances[i][j] = dist
                sqrt_distances[j][i] = dist

        return sqrt_distances

    def _create_initial_weights(self, init_type):
        """!
        @brief Creates initial weights for neurons in line with the specified initialization.
        
        @param[in] init_type (type_init): Type of initialization of initial neuron weights (random, random in center of the input data, random distributed in data, ditributed in line with uniform grid).
        
        """

        dim_info = dimension_info(self._data)

        step_x = dim_info.get_center()[0]
        if self._rows > 1:
            step_x = dim_info.get_width()[0] / (self._rows - 1)

        step_y = 0.0
        if dim_info.get_dimensions() > 1:
            step_y = dim_info.get_center()[1]
            if self._cols > 1:
                step_y = dim_info.get_width()[1] / (self._cols - 1)

        # generate weights (topological coordinates)
        random.seed(self._params.random_state)

        # Uniform grid.
        if init_type == type_init.uniform_grid:
            # Predefined weights in line with input data.
            self._weights = [[[] for i in range(dim_info.get_dimensions())] for j in range(self._size)]
            for i in range(self._size):
                location = self._location[i]
                for dim in range(dim_info.get_dimensions()):
                    if dim == 0:
                        if self._rows > 1:
                            self._weights[i][dim] = dim_info.get_minimum_coordinate()[dim] + step_x * location[dim]
                        else:
                            self._weights[i][dim] = dim_info.get_center()[dim]

                    elif dim == 1:
                        if self._cols > 1:
                            self._weights[i][dim] = dim_info.get_minimum_coordinate()[dim] + step_y * location[dim]
                        else:
                            self._weights[i][dim] = dim_info.get_center()[dim]
                    else:
                        self._weights[i][dim] = dim_info.get_center()[dim]

        elif init_type == type_init.random_surface:
            # Random weights at the full surface.
            self._weights = [
                [random.uniform(dim_info.get_minimum_coordinate()[i], dim_info.get_maximum_coordinate()[i]) for i in
                 range(dim_info.get_dimensions())] for _ in range(self._size)]

        elif init_type == type_init.random_centroid:
            # Random weights at the center of input data.
            self._weights = [[(random.random() + dim_info.get_center()[i]) for i in range(dim_info.get_dimensions())]
                             for _ in range(self._size)]

        else:
            # Random weights of input data.
            self._weights = [[random.random() for i in range(dim_info.get_dimensions())] for _ in range(self._size)]

    def _create_connections(self, conn_type):
        """!
        @brief Create connections in line with input rule (grid four, grid eight, honeycomb, function neighbour).
        
        @param[in] conn_type (type_conn): Type of connection between oscillators in the network.
        
        """

        self._neighbors = [[] for index in range(self._size)]

        for index in range(0, self._size, 1):
            upper_index = index - self._cols
            upper_left_index = index - self._cols - 1
            upper_right_index = index - self._cols + 1

            lower_index = index + self._cols
            lower_left_index = index + self._cols - 1
            lower_right_index = index + self._cols + 1

            left_index = index - 1
            right_index = index + 1

            node_row_index = math.floor(index / self._cols)
            upper_row_index = node_row_index - 1
            lower_row_index = node_row_index + 1

            if (conn_type == type_conn.grid_eight) or (conn_type == type_conn.grid_four):
                if upper_index >= 0:
                    self._neighbors[index].append(upper_index)

                if lower_index < self._size:
                    self._neighbors[index].append(lower_index)

            if (conn_type == type_conn.grid_eight) or (conn_type == type_conn.grid_four) or (
                    conn_type == type_conn.honeycomb):
                if (left_index >= 0) and (math.floor(left_index / self._cols) == node_row_index):
                    self._neighbors[index].append(left_index)

                if (right_index < self._size) and (math.floor(right_index / self._cols) == node_row_index):
                    self._neighbors[index].append(right_index)

            if conn_type == type_conn.grid_eight:
                if (upper_left_index >= 0) and (math.floor(upper_left_index / self._cols) == upper_row_index):
                    self._neighbors[index].append(upper_left_index)

                if (upper_right_index >= 0) and (math.floor(upper_right_index / self._cols) == upper_row_index):
                    self._neighbors[index].append(upper_right_index)

                if (lower_left_index < self._size) and (math.floor(lower_left_index / self._cols) == lower_row_index):
                    self._neighbors[index].append(lower_left_index)

                if (lower_right_index < self._size) and (math.floor(lower_right_index / self._cols) == lower_row_index):
                    self._neighbors[index].append(lower_right_index)

            if conn_type == type_conn.honeycomb:
                if (node_row_index % 2) == 0:
                    upper_left_index = index - self._cols
                    upper_right_index = index - self._cols + 1

                    lower_left_index = index + self._cols
                    lower_right_index = index + self._cols + 1
                else:
                    upper_left_index = index - self._cols - 1
                    upper_right_index = index - self._cols

                    lower_left_index = index + self._cols - 1
                    lower_right_index = index + self._cols

                if (upper_left_index >= 0) and (math.floor(upper_left_index / self._cols) == upper_row_index):
                    self._neighbors[index].append(upper_left_index)

                if (upper_right_index >= 0) and (math.floor(upper_right_index / self._cols) == upper_row_index):
                    self._neighbors[index].append(upper_right_index)

                if (lower_left_index < self._size) and (math.floor(lower_left_index / self._cols) == lower_row_index):
                    self._neighbors[index].append(lower_left_index)

                if (lower_right_index < self._size) and (math.floor(lower_right_index / self._cols) == lower_row_index):
                    self._neighbors[index].append(lower_right_index)

    def _competition(self, x):
        """!
        @brief Calculates neuron winner (distance, neuron index).
        
        @param[in] x (list): Input pattern from the input data set, for example it can be coordinates of point.
        
        @return (uint) Returns index of neuron that is winner.
        
        """

        index = 0
        minimum = euclidean_distance_square(self._weights[0], x)

        for i in range(1, self._size, 1):
            candidate = euclidean_distance_square(self._weights[i], x)
            if candidate < minimum:
                index = i
                minimum = candidate

        return index

    def _adaptation(self, index, x):
        """!
        @brief Change weight of neurons in line with won neuron.
        
        @param[in] index (uint): Index of neuron-winner.
        @param[in] x (list): Input pattern from the input data set.
        
        """

        dimension = len(self._weights[0])

        if self._conn_type == type_conn.func_neighbor:
            for neuron_index in range(self._size):
                distance = self._sqrt_distances[index][neuron_index]

                if distance < self._local_radius:
                    influence = math.exp(-(distance / (2.0 * self._local_radius)))

                    for i in range(dimension):
                        self._weights[neuron_index][i] = self._weights[neuron_index][
                                                             i] + self._learn_rate * influence * (
                                                                 x[i] - self._weights[neuron_index][i])

        else:
            for i in range(dimension):
                self._weights[index][i] = self._weights[index][i] + self._learn_rate * (x[i] - self._weights[index][i])

            for neighbor_index in self._neighbors[index]:
                distance = self._sqrt_distances[index][neighbor_index]
                if distance < self._local_radius:
                    influence = math.exp(-(distance / (2.0 * self._local_radius)))

                    for i in range(dimension):
                        self._weights[neighbor_index][i] = self._weights[neighbor_index][
                                                               i] + self._learn_rate * influence * (
                                                                   x[i] - self._weights[neighbor_index][i])

    def train(self, data, epochs, autostop=False):
        """!
        @brief Trains self-organized feature map (SOM).

        @param[in] data (list): Input data - list of points where each point is represented by list of features, for example coordinates.
        @param[in] epochs (uint): Number of epochs for training.
        @param[in] autostop (bool): Automatic termination of learning process when adaptation is not occurred.
        
        @return (uint) Number of learning iterations.
        
        """

        self._data = data

        if self.__ccore_som_pointer is not None:
            return wrapper.som_train(self.__ccore_som_pointer, data, epochs, autostop)

        self._sqrt_distances = self.__initialize_distances(self._size, self._location)

        for i in range(self._size):
            self._award[i] = 0
            self._capture_objects[i].clear()

        # weights
        self._create_initial_weights(self._params.init_type)

        previous_weights = None

        for epoch in range(1, epochs + 1):
            # Depression term of coupling
            self._local_radius = (self._params.init_radius * math.exp(-(epoch / epochs))) ** 2
            self._learn_rate = self._params.init_learn_rate * math.exp(-(epoch / epochs))

            # Clear statistics
            if autostop:
                for i in range(self._size):
                    self._award[i] = 0
                    self._capture_objects[i].clear()

            for i in range(len(self._data)):
                # Step 1: Competition:
                index = self._competition(self._data[i])

                # Step 2: Adaptation:   
                self._adaptation(index, self._data[i])

                # Update statistics
                if (autostop is True) or (epoch == epochs):
                    self._award[index] += 1
                    self._capture_objects[index].append(i)

            # Check requirement of stopping
            if autostop:
                if previous_weights is not None:
                    maximal_adaptation = self._get_maximal_adaptation(previous_weights)
                    if maximal_adaptation < self._params.adaptation_threshold:
                        return epoch

                previous_weights = [item[:] for item in self._weights]

        return epochs

    def simulate(self, input_pattern):
        """!
        @brief Processes input pattern (no learining) and returns index of neuron-winner.
               Using index of neuron winner catched object can be obtained using property capture_objects.
               
        @param[in] input_pattern (list): Input pattern.
        
        @return (uint) Returns index of neuron-winner.
               
        @see capture_objects
        
        """

        if self.__ccore_som_pointer is not None:
            return wrapper.som_simulate(self.__ccore_som_pointer, input_pattern)

        return self._competition(input_pattern)

    def _get_maximal_adaptation(self, previous_weights):
        """!
        @brief Calculates maximum changes of weight in line with comparison between previous weights and current weights.
        
        @param[in] previous_weights (list): Weights from the previous step of learning process.
        
        @return (double) Value that represents maximum changes of weight after adaptation process.
        
        """

        dimension = len(self._data[0])
        maximal_adaptation = 0.0

        for neuron_index in range(self._size):
            for dim in range(dimension):
                current_adaptation = previous_weights[neuron_index][dim] - self._weights[neuron_index][dim]

                if current_adaptation < 0:
                    current_adaptation = -current_adaptation

                if maximal_adaptation < current_adaptation:
                    maximal_adaptation = current_adaptation

        return maximal_adaptation

    def get_winner_number(self):
        """!
        @brief Calculates number of winner at the last step of learning process.
        
        @return (uint) Number of winner.
        
        """

        if self.__ccore_som_pointer is not None:
            self._award = wrapper.som_get_awards(self.__ccore_som_pointer)

        winner_number = 0
        for i in range(self._size):
            if self._award[i] > 0:
                winner_number += 1

        return winner_number

    def show_distance_matrix(self):
        """!
        @brief Shows gray visualization of U-matrix (distance matrix).
        
        @see get_distance_matrix()
        
        """
        distance_matrix = self.get_distance_matrix()

        plt.imshow(distance_matrix, cmap=plt.get_cmap('hot'), interpolation='kaiser')
        plt.title("U-Matrix")
        plt.colorbar()
        plt.show()

    def get_distance_matrix(self):
        """!
        @brief Calculates distance matrix (U-matrix).
        @details The U-Matrix visualizes based on the distance in input space between a weight vector and its neighbors on map.
        
        @return (list) Distance matrix (U-matrix).
        
        @see show_distance_matrix()
        @see get_density_matrix()
        
        """
        if self.__ccore_som_pointer is not None:
            self._weights = wrapper.som_get_weights(self.__ccore_som_pointer)

            if self._conn_type != type_conn.func_neighbor:
                self._neighbors = wrapper.som_get_neighbors(self.__ccore_som_pointer)

        distance_matrix = [[0.0] * self._cols for i in range(self._rows)]

        for i in range(self._rows):
            for j in range(self._cols):
                neuron_index = i * self._cols + j

                if self._conn_type == type_conn.func_neighbor:
                    self._create_connections(type_conn.grid_eight)

                for neighbor_index in self._neighbors[neuron_index]:
                    distance_matrix[i][j] += euclidean_distance_square(self._weights[neuron_index],
                                                                       self._weights[neighbor_index])

                distance_matrix[i][j] /= len(self._neighbors[neuron_index])

        return distance_matrix

    def show_density_matrix(self, surface_divider=20.0):
        """!
        @brief Show density matrix (P-matrix) using kernel density estimation.
        
        @param[in] surface_divider (double): Divider in each dimension that affect radius for density measurement.
        
        @see show_distance_matrix()
        
        """
        density_matrix = self.get_density_matrix(surface_divider)

        plt.imshow(density_matrix, cmap=plt.get_cmap('hot'), interpolation='kaiser')
        plt.title("P-Matrix")
        plt.colorbar()
        plt.show()

    def get_density_matrix(self, surface_divider=20.0):
        """!
        @brief Calculates density matrix (P-Matrix).
        
        @param[in] surface_divider (double): Divider in each dimension that affect radius for density measurement.
        
        @return (list) Density matrix (P-Matrix).
        
        @see get_distance_matrix()
        
        """

        if self.__ccore_som_pointer is not None:
            self._weights = wrapper.som_get_weights(self.__ccore_som_pointer)

        density_matrix = [[0] * self._cols for i in range(self._rows)]
        dimension = len(self._weights[0])

        dim_max = [float('-Inf')] * dimension
        dim_min = [float('Inf')] * dimension

        for weight in self._weights:
            for index_dim in range(dimension):
                if weight[index_dim] > dim_max[index_dim]:
                    dim_max[index_dim] = weight[index_dim]

                if weight[index_dim] < dim_min[index_dim]:
                    dim_min[index_dim] = weight[index_dim]

        radius = [0.0] * len(self._weights[0])
        for index_dim in range(dimension):
            radius[index_dim] = (dim_max[index_dim] - dim_min[index_dim]) / surface_divider

        ## TODO: do not use data
        for point in self._data:
            for index_neuron in range(len(self)):
                point_covered = True

                for index_dim in range(dimension):
                    if abs(point[index_dim] - self._weights[index_neuron][index_dim]) > radius[index_dim]:
                        point_covered = False
                        break

                row = int(math.floor(index_neuron / self._cols))
                col = index_neuron - row * self._cols

                if point_covered is True:
                    density_matrix[row][col] += 1

        return density_matrix

    def show_winner_matrix(self):
        """!
        @brief Show a winner matrix where each element corresponds to neuron and value represents
               amount of won objects from input data-space at the last training iteration.
        
        @see show_distance_matrix()
        
        """

        if self.__ccore_som_pointer is not None:
            self._award = wrapper.som_get_awards(self.__ccore_som_pointer)

        (fig, ax) = plt.subplots()
        winner_matrix = [[0] * self._cols for _ in range(self._rows)]

        for i in range(self._rows):
            for j in range(self._cols):
                neuron_index = i * self._cols + j

                winner_matrix[i][j] = self._award[neuron_index]
                ax.text(i, j, str(winner_matrix[i][j]), va='center', ha='center')

        ax.imshow(winner_matrix, cmap=plt.get_cmap('cool'), interpolation='none')
        ax.grid(True)

        plt.title("Winner Matrix")
        plt.show()

    def show_network(self, awards=False, belongs=False, coupling=True, dataset=True, marker_type='o'):
        """!
        @brief Shows neurons in the dimension of data.
        
        @param[in] awards (bool): If True - displays how many objects won each neuron.
        @param[in] belongs (bool): If True - marks each won object by according index of neuron-winner (only when
                    dataset is displayed too).
        @param[in] coupling (bool): If True - displays connections between neurons (except case when function neighbor
                    is used).
        @param[in] dataset (bool): If True - displays inputs data set.
        @param[in] marker_type (string): Defines marker that is used to denote neurons on the plot.
        
        """

        if self.__ccore_som_pointer is not None:
            self._size = wrapper.som_get_size(self.__ccore_som_pointer)
            self._weights = wrapper.som_get_weights(self.__ccore_som_pointer)
            self._neighbors = wrapper.som_get_neighbors(self.__ccore_som_pointer)
            self._award = wrapper.som_get_awards(self.__ccore_som_pointer)

        dimension = len(self._weights[0])

        fig = plt.figure()

        # Check for dimensions
        if (dimension == 1) or (dimension == 2):
            axes = fig.add_subplot(111)
        elif dimension == 3:
            axes = fig.gca(projection='3d')
        else:
            raise NotImplementedError('Impossible to show network in data-space that is differ from 1D, 2D or 3D.')

        if (self._data is not None) and (dataset is True):
            for x in self._data:
                if dimension == 1:
                    axes.plot(x[0], 0.0, 'b|', ms=30)

                elif dimension == 2:
                    axes.plot(x[0], x[1], 'b.')

                elif dimension == 3:
                    axes.scatter(x[0], x[1], x[2], c='b', marker='.')

        # Show neurons
        for index in range(self._size):
            color = 'g'
            if self._award[index] == 0:
                color = 'y'

            if dimension == 1:
                axes.plot(self._weights[index][0], 0.0, color + marker_type)

                if awards:
                    location = '{0}'.format(self._award[index])
                    axes.text(self._weights[index][0], 0.0, location, color='black', fontsize=10)

                if belongs and self._data is not None:
                    location = '{0}'.format(index)
                    axes.text(self._weights[index][0], 0.0, location, color='black', fontsize=12)
                    for k in range(len(self._capture_objects[index])):
                        point = self._data[self._capture_objects[index][k]]
                        axes.text(point[0], 0.0, location, color='blue', fontsize=10)

            if dimension == 2:
                axes.plot(self._weights[index][0], self._weights[index][1], color + marker_type)

                if awards:
                    location = '{0}'.format(self._award[index])
                    axes.text(self._weights[index][0], self._weights[index][1], location, color='black', fontsize=10)

                if belongs and self._data is not None:
                    location = '{0}'.format(index)
                    axes.text(self._weights[index][0], self._weights[index][1], location, color='black', fontsize=12)
                    for k in range(len(self._capture_objects[index])):
                        point = self._data[self._capture_objects[index][k]]
                        axes.text(point[0], point[1], location, color='blue', fontsize=10)

                if (self._conn_type != type_conn.func_neighbor) and (coupling is True):
                    for neighbor in self._neighbors[index]:
                        if neighbor > index:
                            axes.plot([self._weights[index][0], self._weights[neighbor][0]],
                                      [self._weights[index][1], self._weights[neighbor][1]],
                                      'g', linewidth=0.5)

            elif dimension == 3:
                axes.scatter(self._weights[index][0], self._weights[index][1], self._weights[index][2], c=color,
                             marker=marker_type)

                if (self._conn_type != type_conn.func_neighbor) and (coupling != False):
                    for neighbor in self._neighbors[index]:
                        if neighbor > index:
                            axes.plot([self._weights[index][0], self._weights[neighbor][0]],
                                      [self._weights[index][1], self._weights[neighbor][1]],
                                      [self._weights[index][2], self._weights[neighbor][2]],
                                      'g-', linewidth=0.5)

        plt.title("Network Structure")
        plt.grid()
        plt.show()

    def __get_dump_from_python(self, ccore_usage):
        return {'ccore': ccore_usage,
                'state': {'cols': self._cols,
                          'rows': self._rows,
                          'size': self._size,
                          'conn_type': self._conn_type,
                          'neighbors': self._neighbors,
                          'local_radius': self._local_radius,
                          'learn_rate': self._learn_rate,
                          'params': self._params,
                          'location': self._location,
                          'weights': self._weights,
                          'award': self._award,
                          'capture_objects': self._capture_objects}}

    def __download_dump_from_ccore(self):
        self._location = self.__initialize_locations(self._rows, self._cols)
        self._weights = wrapper.som_get_weights(self.__ccore_som_pointer)
        self._award = wrapper.som_get_awards(self.__ccore_som_pointer)
        self._capture_objects = wrapper.som_get_capture_objects(self.__ccore_som_pointer)

    def __upload_common_part(self, state_dump):
        self._cols = state_dump['cols']
        self._rows = state_dump['rows']
        self._size = state_dump['size']
        self._conn_type = state_dump['conn_type']
        self._neighbors = state_dump['neighbors']
        self._local_radius = state_dump['local_radius']
        self._learn_rate = state_dump['learn_rate']
        self._params = state_dump['params']
        self._neighbors = None

    def __upload_dump_to_python(self, state_dump):
        self.__ccore_som_pointer = None

        self.__upload_common_part(state_dump)

        self._location = state_dump['location']
        self._weights = state_dump['weights']
        self._award = state_dump['award']
        self._capture_objects = state_dump['capture_objects']

        self._location = self.__initialize_locations(self._rows, self._cols)
        self._create_connections(self._conn_type)

    def __upload_dump_to_ccore(self, state_dump):
        self.__upload_common_part(state_dump)
        self.__ccore_som_pointer = wrapper.som_create(self._rows, self._cols, self._conn_type, self._params)
        wrapper.som_load(self.__ccore_som_pointer, state_dump['weights'], state_dump['award'],
                         state_dump['capture_objects'])