|
1
|
|
|
"""!
|
|
2
|
|
|
|
|
3
|
|
|
@brief Chaotic Neural Network
|
|
4
|
|
|
@details Based on article description:
|
|
5
|
|
|
-
|
|
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
|
|
|
import math;
|
|
29
|
|
|
|
|
30
|
|
|
import random;
|
|
31
|
|
|
|
|
32
|
|
|
from enum import IntEnum;
|
|
33
|
|
|
|
|
34
|
|
|
from pyclustering.utils import euclidean_distance_sqrt, average_neighbor_distance, heaviside, draw_dynamics;
|
|
35
|
|
|
|
|
36
|
|
|
|
|
37
|
|
|
class type_conn(IntEnum):
|
|
38
|
|
|
"""!
|
|
39
|
|
|
@brief Enumeration of connection types for Chaotic Neural Network.
|
|
40
|
|
|
|
|
41
|
|
|
@see cnn_network
|
|
42
|
|
|
|
|
43
|
|
|
"""
|
|
44
|
|
|
|
|
45
|
|
|
## All oscillators have connection with each other.
|
|
46
|
|
|
ALL_TO_ALL = 0,
|
|
47
|
|
|
|
|
48
|
|
|
## Connections between oscillators are created in line with Delaunay triangulation.
|
|
49
|
|
|
TRIANGULATION_DELAUNAY = 1,
|
|
50
|
|
|
|
|
51
|
|
|
|
|
52
|
|
|
class cnn_dynamic:
|
|
53
|
|
|
def __init__(self, output = [], time = []):
|
|
|
|
|
|
|
54
|
|
|
self.output = output;
|
|
55
|
|
|
self.time = time;
|
|
56
|
|
|
|
|
57
|
|
|
|
|
58
|
|
|
def __len__(self):
|
|
59
|
|
|
return len(self.output);
|
|
60
|
|
|
|
|
61
|
|
|
|
|
62
|
|
|
def allocate_observation_matrix(self):
|
|
63
|
|
|
number_neurons = len(self.output[0]);
|
|
64
|
|
|
observation_matrix = [];
|
|
65
|
|
|
|
|
66
|
|
|
for iteration in range(len(self.output)):
|
|
67
|
|
|
obervation_column = [];
|
|
68
|
|
|
for index_neuron in range(number_neurons):
|
|
69
|
|
|
obervation_column.append(heaviside(self.output[iteration][index_neuron]));
|
|
70
|
|
|
|
|
71
|
|
|
observation_matrix.append(obervation_column);
|
|
72
|
|
|
|
|
73
|
|
|
return observation_matrix;
|
|
74
|
|
|
|
|
75
|
|
|
|
|
76
|
|
|
class cnn_visualizer:
|
|
77
|
|
|
@staticmethod
|
|
78
|
|
|
def show_output_dynamic(cnn_output_dynamic):
|
|
79
|
|
|
draw_dynamics(cnn_output_dynamic.time, cnn_output_dynamic.output, x_title = "t", y_title = "x");
|
|
80
|
|
|
|
|
81
|
|
|
@staticmethod
|
|
82
|
|
|
def show_observation_matrix(cnn_output_dynamic):
|
|
83
|
|
|
observation_matrix = cnn_output_dynamic.allocate_observation_matrix();
|
|
84
|
|
|
draw_dynamics(cnn_output_dynamic.time, observation_matrix, x_title = "t", y_title = "outputs");
|
|
85
|
|
|
|
|
86
|
|
|
|
|
87
|
|
|
class cnn_network:
|
|
88
|
|
|
def __init__(self, num_osc, conn_type = type_conn.ALL_TO_ALL, amount_neighbors = 3):
|
|
89
|
|
|
self.__num_osc = num_osc;
|
|
90
|
|
|
self.__conn_type = conn_type;
|
|
91
|
|
|
self.__amount_neighbors = amount_neighbors;
|
|
92
|
|
|
|
|
93
|
|
|
self.__average_distance = 0.0;
|
|
94
|
|
|
self.__weights = None;
|
|
95
|
|
|
self.__weights_summary = None;
|
|
96
|
|
|
|
|
97
|
|
|
self.__output = [ random.random() for _ in range(num_osc) ];
|
|
98
|
|
|
|
|
99
|
|
|
|
|
100
|
|
|
def __len__(self):
|
|
101
|
|
|
return self.__num_osc;
|
|
102
|
|
|
|
|
103
|
|
|
|
|
104
|
|
|
def simulate(self, steps, stimulus):
|
|
105
|
|
|
self.__create_weights(stimulus);
|
|
106
|
|
|
|
|
107
|
|
|
dynamic = cnn_dynamic([], []);
|
|
108
|
|
|
dynamic.output.append(self.__output);
|
|
109
|
|
|
dynamic.time.append(0);
|
|
110
|
|
|
|
|
111
|
|
|
for step in range(1, steps, 1):
|
|
112
|
|
|
self.__output = self.__calculate_states();
|
|
113
|
|
|
|
|
114
|
|
|
dynamic.output.append(self.__output);
|
|
115
|
|
|
dynamic.time.append(step);
|
|
116
|
|
|
|
|
117
|
|
|
return dynamic;
|
|
118
|
|
|
|
|
119
|
|
|
|
|
120
|
|
|
def __calculate_states(self):
|
|
121
|
|
|
output = [ 0.0 for _ in range(self.__num_osc) ];
|
|
122
|
|
|
|
|
123
|
|
|
for i in range(self.__num_osc):
|
|
124
|
|
|
output[i] = self.__neuron_evolution(i);
|
|
125
|
|
|
|
|
126
|
|
|
return output;
|
|
127
|
|
|
|
|
128
|
|
|
|
|
129
|
|
|
def __neuron_evolution(self, index):
|
|
130
|
|
|
value = 0.0;
|
|
131
|
|
|
state = 1.0 - 2.0 * self.__output[index];
|
|
132
|
|
|
|
|
133
|
|
|
for index in range(self.__num_osc):
|
|
134
|
|
|
value += self.__weights[index][index] * state;
|
|
135
|
|
|
|
|
136
|
|
|
return value / self.__weights_summary[index];
|
|
137
|
|
|
|
|
138
|
|
|
|
|
139
|
|
|
def __create_weights(self, stimulus):
|
|
140
|
|
|
self.__average_distance = average_neighbor_distance(stimulus, self.__amount_neighbors);
|
|
141
|
|
|
|
|
142
|
|
|
self.__weights = [ [ 0.0 for _ in range(len(stimulus)) ] for _ in range(len(stimulus)) ];
|
|
143
|
|
|
self.__weights_summary = [ 0.0 for _ in range(self.__num_osc) ];
|
|
144
|
|
|
|
|
145
|
|
|
if (self.__conn_type == type_conn.ALL_TO_ALL):
|
|
146
|
|
|
self.__create_weights_all_to_all(stimulus);
|
|
147
|
|
|
|
|
148
|
|
|
elif (self.__conn_type == type_conn.TRIANGULATION_DELAUNAY):
|
|
149
|
|
|
self.__create_weights_delaunay_triangulation(stimulus);
|
|
150
|
|
|
|
|
151
|
|
|
|
|
152
|
|
|
def __create_weights_all_to_all(self, stimulus):
|
|
153
|
|
|
for i in range(len(stimulus)):
|
|
154
|
|
|
for j in range(i + 1, len(stimulus)):
|
|
155
|
|
|
weight = self.__calculate_weight(stimulus[i], stimulus[j]);
|
|
156
|
|
|
|
|
157
|
|
|
self.__weights[i][j] = weight;
|
|
158
|
|
|
self.__weights[j][i] = weight;
|
|
159
|
|
|
|
|
160
|
|
|
self.__weights_summary[i] += weight;
|
|
161
|
|
|
self.__weights_summary[j] += weight;
|
|
162
|
|
|
|
|
163
|
|
|
|
|
164
|
|
|
def __create_weights_delaunay_triangulation(self, stimulus):
|
|
165
|
|
|
pass;
|
|
166
|
|
|
|
|
167
|
|
|
|
|
168
|
|
|
def __calculate_weight(self, oscillator_location1, oscillator_location2):
|
|
169
|
|
|
distance = euclidean_distance_sqrt(oscillator_location1, oscillator_location2);
|
|
170
|
|
|
return math.exp(-distance / (2.0 * self.__amount_neighbors));
|
|
171
|
|
|
|
Objects as default values are only created once in Python and not on each invocation of the function. If the default object is modified, this modification is carried over to the next invocation of the method.