Completed
Push — master ( 433ce3...eb7094 )
by Andrei
01:25
created

syncsom   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 223
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 223
rs 10
c 1
b 0
f 0
wmc 22

10 Methods

Rating   Name   Duplication   Size   Complexity  
A show_som_layer() 0 7 1
A __create_sync_layer() 0 17 4
A sync_layer() 0 7 1
B get_som_clusters() 0 26 3
A __init__() 0 22 1
B get_clusters() 0 30 3
A som_layer() 0 7 1
B process() 0 30 3
A show_sync_layer() 0 7 1
A __has_object_connection() 0 20 4
1
"""!
2
3
@brief Cluster analysis algorithm: SYNC-SOM
4
@details Based on article description:
5
         - A.Novikov, E.Benderskaya. SYNC-SOM Double-layer Oscillatory Network for Cluster Analysis. 2014.
6
7
@authors Andrei Novikov ([email protected])
8
@date 2014-2016
9
@copyright GNU Public License
10
11
@cond GNU_PUBLIC_LICENSE
12
    PyClustering is free software: you can redistribute it and/or modify
13
    it under the terms of the GNU General Public License as published by
14
    the Free Software Foundation, either version 3 of the License, or
15
    (at your option) any later version.
16
    
17
    PyClustering is distributed in the hope that it will be useful,
18
    but WITHOUT ANY WARRANTY; without even the implied warranty of
19
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
    GNU General Public License for more details.
21
    
22
    You should have received a copy of the GNU General Public License
23
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
@endcond
25
26
"""
27
28
from pyclustering.nnet.som import som, type_conn;
29
from pyclustering.nnet import initial_type;
30
31
from pyclustering.cluster.syncnet import syncnet;
32
33
from pyclustering.utils import euclidean_distance_sqrt;
34
35
36
class syncsom:
37
    """!
38
    @brief Class represents clustering algorithm SYNC-SOM. SYNC-SOM is bio-inspired algorithm that is based on oscillatory network 
39
           that uses self-organized feature map as the first layer.
40
           
41
    Example:
42
    @code
43
        # read sample for clustering
44
        sample = read_sample(file);
45
        
46
        # create oscillatory network for cluster analysis where the first layer has 
47
        # size 10x10 and connectivity radius for objects 1.0.
48
        network = syncsom(sample, 10, 10, 1.0);
49
        
50
        # simulate network (perform cluster analysis) and collect output dynamic
51
        (dyn_time, dyn_phase) = network.process(True, 0.998);
52
        
53
        # obtain encoded clusters
54
        encoded_clusters = network.get_som_clusters();
55
        
56
        # obtain real clusters
57
        clusters = network.get_clusters();
58
        
59
        # show the first layer of the network
60
        network.show_som_layer();
61
        
62
        # show the second layer of the network
63
        network.show_sync_layer();
64
    @endcode
65
    
66
    """
67
68
    @property
69
    def som_layer(self):
70
        """!
71
        @brief The first layer of the oscillatory network - self-organized feature map.
72
        
73
        """
74
        return self._som;
75
76
77
    @property
78
    def sync_layer(self):
79
        """!
80
        @brief The second layer of the oscillatory network based on Kuramoto model.
81
        
82
        """
83
        return self._sync;
84
85
86
    def __init__(self, data, rows, cols, radius):
87
        """!
88
        @brief Constructor of the double layer oscillatory network SYNC-SOM.
89
        
90
        @param[in] data (list): Input data that is presented as list of points (objects), each point should be represented by list or tuple.
91
        @param[in] rows (uint): Rows of neurons (number of neurons in column) in the input layer (self-organized feature map).
92
        @param[in] cols (uint): Columns of neurons (number of neurons in row) in the input later (self-organized feature map).
93
        @param[in] radius (double): Connectivity radius between objects that defines connection between oscillators in the second layer.
94
        
95
        """
96
        
97
        self._data = data;
98
        self._radius = radius * radius;
99
        
100
        self._som = som(rows, cols, conn_type = type_conn.grid_four);   # The first (input) later - SOM layer.
101
        self._som_osc_table = list();
102
        
103
        self._sync = None;       # The second (output) layer - Sync layer.
104
        self._struct = None;     # Structure of connections between oscillators in the second layer - Sync layer.
105
        
106
        # For convenience
107
        self._analyser = None;
108
109
110
    def process(self, collect_dynamic = False, order = 0.999):
111
        """!
112
        @brief Performs simulation of the oscillatory network.
113
        
114
        @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
115
        @param[in] order (double): Order of process synchronization that should be considered as end of clustering, destributed 0..1.
116
        
117
        @return (tuple) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
118
                otherwise returns only last values (last step of simulation) of dynamic.
119
        
120
        @see get_som_clusters()
121
        @see get_clusters()
122
        """
123
        
124
        # train self-organization map.
125
        self._som.train(self._data, 100);
126
        
127
        # prepare to build list.
128
        weights = list();
129
        self._som_osc_table.clear();        # must be cleared, if it's used before.
130
        for i in range(self._som.size):
131
            if (self._som.awards[i] > 0):
132
                weights.append(self._som.weights[i]);
133
                self._som_osc_table.append(i);
134
        
135
        # create oscillatory neural network.
136
        self._sync = self.__create_sync_layer(weights);
137
        self._analyser = self._sync.process(order, collect_dynamic = collect_dynamic);
138
        
139
        return (self._analyser.time, self._analyser.output);
140
141
142
    def __create_sync_layer(self, weights):
143
        """!
144
        @brief Creates second layer of the network.
145
        
146
        @param[in] weights (list): List of weights of SOM neurons.
147
        
148
        @return (syncnet) Second layer of the network.
149
        
150
        """
151
        sync_layer = syncnet(weights, 0.0, initial_phases = initial_type.RANDOM_GAUSSIAN);
152
        
153
        for oscillator_index1 in range(0, len(sync_layer)):
154
            for oscillator_index2 in range(oscillator_index1 + 1, len(sync_layer)):
155
                if (self.__has_object_connection(oscillator_index1, oscillator_index2)):
156
                    sync_layer.set_connection(oscillator_index1, oscillator_index2);
157
        
158
        return sync_layer;
159
160
161
    def __has_object_connection(self, oscillator_index1, oscillator_index2):
162
        """!
163
        @brief Searches for pair of objects that are encoded by specified neurons and that are connected in line with connectivity radius.
164
        
165
        @param[in] oscillator_index1 (uint): Index of the first oscillator in the second layer.
166
        @param[in] oscillator_index2 (uint): Index of the second oscillator in the second layer.
167
        
168
        @return (bool) True - if there is pair of connected objects encoded by specified oscillators.
169
        
170
        """
171
        som_neuron_index1 = self._som_osc_table[oscillator_index1];
172
        som_neuron_index2 = self._som_osc_table[oscillator_index2];
173
        
174
        for index_object1 in self._som.capture_objects[som_neuron_index1]:
175
            for index_object2 in self._som.capture_objects[som_neuron_index2]:
176
                distance = euclidean_distance_sqrt(self._data[index_object1], self._data[index_object2]);
177
                if (distance <= self._radius):
178
                    return True;
179
        
180
        return False;
181
182
183
    def get_som_clusters(self, eps = 0.1):
0 ignored issues
show
Unused Code introduced by
The argument eps seems to be unused.
Loading history...
184
        """!
185
        @brief Returns clusters with SOM neurons that encode input features in line with result of synchronization in the second (Sync) layer.
186
        
187
        @param[in] eps (double): Maximum error for allocation of synchronous ensemble oscillators.
188
        
189
        @return (list) List of clusters that are represented by lists of indexes of neurons that encode input data.
190
        
191
        @see process()
192
        @see get_clusters()
193
        
194
        """
195
        
196
        sync_clusters = self._analyser.allocate_clusters();
197
        
198
        # Decode it to indexes of SOM neurons
199
        som_clusters = list();
200
        for oscillators in sync_clusters:
201
            cluster = list();
202
            for index_oscillator in oscillators:
203
                index_neuron = self._som_osc_table[index_oscillator];
204
                cluster.append(index_neuron);
205
                
206
            som_clusters.append(cluster);
207
            
208
        return som_clusters;
209
210
211
    def get_clusters(self, eps = 0.1):
212
        """!
213
        @brief Returns clusters in line with ensembles of synchronous oscillators where each synchronous ensemble corresponds to only one cluster.
214
        
215
        @param[in] eps (double): Maximum error for allocation of synchronous ensemble oscillators.
216
        
217
        @return (list) List of grours (lists) of indexes of synchronous oscillators that corresponds to index of objects.
218
        
219
        @see process()
220
        @see get_som_clusters()
221
        
222
        """
223
        
224
        sync_clusters = self._analyser.allocate_clusters(eps);       # it isn't indexes of SOM neurons
225
        
226
        clusters = list();
227
        total_winners = 0;
0 ignored issues
show
Unused Code introduced by
The variable total_winners seems to be unused.
Loading history...
228
        total_number_points = 0;
0 ignored issues
show
Unused Code introduced by
The variable total_number_points seems to be unused.
Loading history...
229
        for oscillators in sync_clusters:
230
            cluster = list();
231
            for index_oscillator in oscillators:
232
                index_neuron = self._som_osc_table[index_oscillator];
233
                
234
                cluster += self._som.capture_objects[index_neuron];
235
                total_number_points += len(self._som.capture_objects[index_neuron]);
236
                total_winners += 1;
237
                
238
            clusters.append(cluster);
239
        
240
        return clusters;
241
242
243
    def show_som_layer(self):
244
        """!
245
        @brief Shows visual representation of the first (SOM) layer.
246
        
247
        """
248
        
249
        self._som.show_network();
250
251
252
    def show_sync_layer(self):
253
        """!
254
        @brief Shows visual representation of the second (Sync) layer.
255
        
256
        """
257
        
258
        self._sync.show_network();
259