3 @brief Phase oscillatory network for patten recognition based on modified Kuramoto model.
4 @details Implementation based on paper @cite article::nnet::syncpr::1.
6 @authors Andrei Novikov (pyclustering@yandex.ru)
8 @copyright BSD-3-Clause
19 import pyclustering.core.syncpr_wrapper
as wrapper
21 from pyclustering.core.wrapper
import ccore_library
25 import matplotlib.pyplot
as plt
26 import matplotlib.animation
as animation
31 @brief Represents output dynamic of syncpr (Sync for Pattern Recognition).
37 @brief Constructor of syncpr dynamic.
39 @param[in] phase (list): Dynamic of oscillators on each step of simulation. If ccore pointer is specified than it can be ignored.
40 @param[in] time (list): Simulation time.
41 @param[in] ccore (ctypes.pointer): Pointer to CCORE sync_dynamic instance in memory.
44 super().
__init__(phase, time, ccore);
49 @brief Visualizer of output dynamic of syncpr network (Sync for Pattern Recognition).
54 def show_pattern(syncpr_output_dynamic, image_height, image_width):
56 @brief Displays evolution of phase oscillators as set of patterns where the last one means final result of recognition.
58 @param[in] syncpr_output_dynamic (syncpr_dynamic): Output dynamic of a syncpr network.
59 @param[in] image_height (uint): Height of the pattern (image_height * image_width should be equal to number of oscillators).
60 @param[in] image_width (uint): Width of the pattern.
63 number_pictures = len(syncpr_output_dynamic);
64 iteration_math_step = 1.0;
65 if (number_pictures > 50):
66 iteration_math_step = number_pictures / 50.0;
69 number_cols = int(numpy.ceil(number_pictures ** 0.5));
70 number_rows = int(numpy.ceil(number_pictures / number_cols));
73 double_indexer =
True;
74 if ( (number_cols == 1)
or (number_rows == 1) ):
76 double_indexer =
False;
78 (_, axarr) = plt.subplots(number_rows, number_cols);
80 if (number_pictures > 1):
81 plt.setp([ax
for ax
in axarr], visible =
False);
83 iteration_display = 0.0;
84 for iteration
in range(len(syncpr_output_dynamic)):
85 if (iteration >= iteration_display):
86 iteration_display += iteration_math_step;
89 if (number_pictures > 1):
90 ax_handle = axarr[real_index];
92 syncpr_visualizer.__show_pattern(ax_handle, syncpr_output_dynamic, image_height, image_width, iteration);
94 if (double_indexer
is True):
95 real_index = real_index[0], real_index[1] + 1;
96 if (real_index[1] >= number_cols):
97 real_index = real_index[0] + 1, 0;
107 @brief Shows animation of pattern recognition process that has been preformed by the oscillatory network.
109 @param[in] syncpr_output_dynamic (syncpr_dynamic): Output dynamic of a syncpr network.
110 @param[in] image_height (uint): Height of the pattern (image_height * image_width should be equal to number of oscillators).
111 @param[in] image_width (uint): Width of the pattern.
112 @param[in] animation_velocity (uint): Interval between frames in milliseconds.
113 @param[in] title (string): Title of the animation that is displayed on a figure if it is specified.
114 @param[in] save_movie (string): If it is specified then animation will be stored to file that is specified in this parameter.
117 figure = plt.figure();
120 return frame_generation(0);
122 def frame_generation(index_dynamic):
125 if (title
is not None):
126 figure.suptitle(title, fontsize = 26, fontweight =
'bold')
128 ax1 = figure.add_subplot(121, projection=
'polar');
129 ax2 = figure.add_subplot(122);
131 dynamic = syncpr_output_dynamic.output[index_dynamic];
133 artist1, = ax1.plot(dynamic, [1.0] * len(dynamic), marker =
'o', color =
'blue', ls =
'');
134 artist2 = syncpr_visualizer.__show_pattern(ax2, syncpr_output_dynamic, image_height, image_width, index_dynamic);
136 return [ artist1, artist2 ];
138 cluster_animation = animation.FuncAnimation(figure, frame_generation, len(syncpr_output_dynamic), interval = animation_velocity, init_func = init_frame, repeat_delay = 5000);
140 if (save_movie
is not None):
144 cluster_animation.save(save_movie, writer =
'ffmpeg', fps = 15, bitrate = 1500);
150 def __show_pattern(ax_handle, syncpr_output_dynamic, image_height, image_width, iteration):
152 @brief Draws pattern on specified ax.
154 @param[in] ax_handle (Axis): Axis where pattern should be drawn.
155 @param[in] syncpr_output_dynamic (syncpr_dynamic): Output dynamic of a syncpr network.
156 @param[in] image_height (uint): Height of the pattern (image_height * image_width should be equal to number of oscillators).
157 @param[in] image_width (uint): Width of the pattern.
158 @param[in] iteration (uint): Simulation iteration that should be used for extracting pattern.
160 @return (matplotlib.artist) Artist (pattern) that is rendered in the canvas.
164 current_dynamic = syncpr_output_dynamic.output[iteration];
165 stage_picture = [(255, 255, 255)] * (image_height * image_width);
166 for index_phase
in range(len(current_dynamic)):
167 phase = current_dynamic[index_phase];
169 pixel_color = math.floor( phase * (255 / (2 * math.pi)) );
170 stage_picture[index_phase] = (pixel_color, pixel_color, pixel_color);
172 stage = numpy.array(stage_picture, numpy.uint8);
173 stage = numpy.reshape(stage, (image_height, image_width) + ((3),));
175 image_cluster = Image.fromarray(stage);
177 artist = ax_handle.imshow(image_cluster, interpolation =
'none');
178 plt.setp(ax_handle, visible =
True);
180 ax_handle.xaxis.set_ticklabels([]);
181 ax_handle.yaxis.set_ticklabels([]);
182 ax_handle.xaxis.set_ticks_position(
'none');
183 ax_handle.yaxis.set_ticks_position(
'none');
190 @brief Model of phase oscillatory network for pattern recognition that is based on the Kuramoto model.
191 @details The model uses second-order and third-order modes of the Fourier components.
193 CCORE option can be used to use the pyclustering core - C/C++ shared library for processing that significantly increases performance.
197 # Network size should be equal to size of pattern for learning.
198 net = syncpr(size_network, 0.3, 0.3);
200 # Train network using list of patterns (input images).
201 net.train(image_samples);
203 # Recognize image using 10 steps during 10 seconds of simulation.
204 sync_output_dynamic = net.simulate(10, 10, pattern, solve_type.RK4, True);
206 # Display output dynamic.
207 syncpr_visualizer.show_output_dynamic(sync_output_dynamic);
209 # Display evolution of recognition of the pattern.
210 syncpr_visualizer.show_pattern(sync_output_dynamic, image_height, image_width);
216 def __init__(self, num_osc, increase_strength1, increase_strength2, ccore = True):
218 @brief Constructor of oscillatory network for pattern recognition based on Kuramoto model.
220 @param[in] num_osc (uint): Number of oscillators in the network.
221 @param[in] increase_strength1 (double): Parameter for increasing strength of the second term of the Fourier component.
222 @param[in] increase_strength2 (double): Parameter for increasing strength of the third term of the Fourier component.
223 @param[in] ccore (bool): If True simulation is performed by CCORE library (C++ implementation of pyclustering).
227 if ( (ccore
is True)
and ccore_library.workable() ):
233 self.
_coupling = [ [0.0
for i
in range(num_osc)]
for j
in range(num_osc) ];
235 super().
__init__(num_osc, 1, 0, conn_type.ALL_TO_ALL, conn_represent.MATRIX, initial_type.RANDOM_GAUSSIAN, ccore)
240 @brief Default destructor of syncpr.
251 @brief Returns size of the network.
263 @brief Trains syncpr network using Hebbian rule for adjusting strength of connections between oscillators during training.
265 @param[in] samples (list): list of patterns where each pattern is represented by list of features that are equal to [-1; 1].
270 for pattern
in samples:
277 number_samples = len(samples);
279 for i
in range(length):
280 for j
in range(i + 1, len(self), 1):
283 for p
in range(number_samples):
284 value1 = samples[p][i];
285 value2 = samples[p][j];
293 def simulate(self, steps, time, pattern, solution = solve_type.RK4, collect_dynamic = True):
295 @brief Performs static simulation of syncpr oscillatory network.
296 @details In other words network performs pattern recognition during simulation.
298 @param[in] steps (uint): Number steps of simulations during simulation.
299 @param[in] time (double): Time of simulation.
300 @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
301 @param[in] solution (solve_type): Type of solver that should be used for simulation.
302 @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
304 @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
305 otherwise returns only last values (last step of simulation) of dynamic.
307 @see simulate_dynamic()
308 @see simulate_static()
312 return self.
simulate_static(steps, time, pattern, solution, collect_dynamic);
315 def simulate_dynamic(self, pattern, order = 0.998, solution = solve_type.RK4, collect_dynamic = False, step = 0.1, int_step = 0.01, threshold_changes = 0.0000001):
317 @brief Performs dynamic simulation of the network until stop condition is not reached.
318 @details In other words network performs pattern recognition during simulation.
319 Stop condition is defined by input argument 'order' that represents memory order, but
320 process of simulation can be stopped if convergance rate is low whose threshold is defined
321 by the argument 'threshold_changes'.
323 @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
324 @param[in] order (double): Order of process synchronization, distributed 0..1.
325 @param[in] solution (solve_type): Type of solution.
326 @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
327 @param[in] step (double): Time step of one iteration of simulation.
328 @param[in] int_step (double): Integration step, should be less than step.
329 @param[in] threshold_changes (double): Additional stop condition that helps prevent infinite simulation, defines limit of changes of oscillators between current and previous steps.
331 @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
332 otherwise returns only last values (last step of simulation) of dynamic.
335 @see simulate_static()
342 ccore_instance_dynamic = wrapper.syncpr_simulate_dynamic(self.
_ccore_network_pointer, pattern, order, solution, collect_dynamic, step);
345 for i
in range(0, len(pattern), 1):
346 if (pattern[i] > 0.0):
349 self.
_phases[i] = math.pi / 2.0;
361 if (collect_dynamic ==
True):
362 dyn_phase.append(self.
_phases);
366 while (current_order < order):
371 time_counter += step;
374 if (collect_dynamic ==
True):
375 dyn_phase.append(self.
_phases);
376 dyn_time.append(time_counter);
379 previous_order = current_order;
383 if (abs(current_order - previous_order) < threshold_changes):
386 if (collect_dynamic !=
True):
387 dyn_phase.append(self.
_phases);
388 dyn_time.append(time_counter);
391 return output_sync_dynamic;
394 def simulate_static(self, steps, time, pattern, solution = solve_type.FAST, collect_dynamic = False):
396 @brief Performs static simulation of syncpr oscillatory network.
397 @details In other words network performs pattern recognition during simulation.
399 @param[in] steps (uint): Number steps of simulations during simulation.
400 @param[in] time (double): Time of simulation.
401 @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
402 @param[in] solution (solve_type): Type of solution.
403 @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
405 @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
406 otherwise returns only last values (last step of simulation) of dynamic.
409 @see simulate_dynamic()
416 ccore_instance_dynamic = wrapper.syncpr_simulate_static(self.
_ccore_network_pointer, steps, time, pattern, solution, collect_dynamic);
419 for i
in range(0, len(pattern), 1):
420 if (pattern[i] > 0.0):
423 self.
_phases[i] = math.pi / 2.0;
425 return super().
simulate_static(steps, time, solution, collect_dynamic);
430 @brief Calculates function of the memorized pattern.
431 @details Throws exception if length of pattern is not equal to size of the network or if it consists feature with value that are not equal to [-1; 1].
433 @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
435 @return (double) Order of memory for the specified pattern.
448 def __calculate_memory_order(self, pattern):
450 @brief Calculates function of the memorized pattern without any pattern validation.
452 @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
454 @return (double) Order of memory for the specified pattern.
459 for index
in range(len(self)):
460 memory_order += pattern[index] * cmath.exp( 1j * self.
_phases[index] );
462 memory_order /= len(self);
463 return abs(memory_order);
466 def _phase_kuramoto(self, teta, t, argv):
468 @brief Returns result of phase calculation for specified oscillator in the network.
470 @param[in] teta (double): Phase of the oscillator that is differentiated.
471 @param[in] t (double): Current time of simulation.
472 @param[in] argv (tuple): Index of the oscillator in the list.
474 @return (double) New phase for specified oscillator (don't assign it here).
485 phase_delta = self.
_phases[k] - teta;
487 phase += self.
_coupling[index][k] * math.sin(phase_delta);
492 term += (term1 - term2);
494 return ( phase + term / len(self) );
497 def __validate_pattern(self, pattern):
499 @brief Validates pattern.
500 @details Throws exception if length of pattern is not equal to size of the network or if it consists feature with value that are not equal to [-1; 1].
502 @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
505 if (len(pattern) != len(self)):
506 raise NameError(
'syncpr: length of the pattern (' + len(pattern) +
') should be equal to size of the network');
508 for feature
in pattern:
509 if ( (feature != -1.0)
and (feature != 1.0) ):
510 raise NameError(
'syncpr: patten feature (' + feature +
') should be distributed in [-1; 1]');