Newer
Older

Michal Kravcenko
committed
/**
* DESCRIPTION OF THE FILE
*
* @author Michal Kravčenko
* @date 13.6.18 -
*/
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>

Michal Kravcenko
committed
#include "NeuralNetwork.h"
#include "../NetConnection/ConnectionWeightIdentity.h"

Michal Kravcenko
committed
NeuralNetwork::NeuralNetwork() {
this->neurons = new std::vector<Neuron*>(0);
this->connection_weights = new std::vector<double>(0);
this->connection_weights->reserve(0);
this->delete_weights = true;

Michal Kravcenko
committed
}
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
NeuralNetwork* NeuralNetwork::get_subnet(std::vector<size_t> &input_neuron_indices, std::vector<size_t> &output_neuron_indices){
NeuralNetwork *output_net = nullptr;
Neuron * active_neuron, * target_neuron;
size_t n = this->neurons->size();
bool *part_of_subnetwork = new bool[n];
std::fill(part_of_subnetwork, part_of_subnetwork + n, false);
bool *is_reachable_from_source = new bool[n];
bool *is_reachable_from_destination = new bool[n];
std::fill(is_reachable_from_source, is_reachable_from_source + n, false);
std::fill(is_reachable_from_destination, is_reachable_from_destination + n, false);
bool *visited_neurons = new bool[n];
std::fill(visited_neurons, visited_neurons + n, false);
size_t active_set_size[2];
active_set_size[0] = 0;
active_set_size[1] = 0;
size_t * active_neuron_set = new size_t[2 * n];
size_t idx1 = 0, idx2 = 1;
/* MAPPING BETWEEN NEURONS AND THEIR INDICES */
size_t idx = 0, idx_target;
for(Neuron *v: *this->neurons){
v->set_idx( idx );
idx++;
}
/* INITIAL STATE FOR THE FORWARD PASS */
for(size_t i: input_neuron_indices ){
if( i < 0 || i >= n){
//invalid index
continue;
}
active_neuron_set[idx1 * n + active_set_size[idx1]] = i;
active_set_size[idx1]++;
visited_neurons[i] = true;
}
/* FORWARD PASS */
while(active_set_size[idx1] > 0){
//we iterate through the active neurons and propagate the signal
for(int i = 0; i < active_set_size[idx1]; ++i){
idx = active_neuron_set[i];
is_reachable_from_source[ idx ] = true;
active_neuron = this->neurons->at( idx );
for(Connection* connection: *(active_neuron->get_connections_out())){
target_neuron = connection->get_neuron_out( );
idx_target = target_neuron->get_idx( );
if( visited_neurons[idx_target] ){
//this neuron was already analyzed
continue;
}
visited_neurons[idx_target] = true;
active_neuron_set[active_set_size[idx2] + n * idx2] = idx_target;
active_set_size[idx2]++;
}
}
idx1 = idx2;
idx2 = (idx1 + 1) % 2;
active_set_size[idx2] = 0;
}
/* INITIAL STATE FOR THE FORWARD PASS */
active_set_size[0] = active_set_size[1] = 0;
std::fill(visited_neurons, visited_neurons + n, false);
for(size_t i: output_neuron_indices ){
if( i < 0 || i >= n){
//invalid index
continue;
}
active_neuron_set[idx1 * n + active_set_size[idx1]] = i;
active_set_size[idx1]++;
visited_neurons[i] = true;
}
/* BACKWARD PASS */
size_t n_new_neurons = 0;
while(active_set_size[idx1] > 0){
//we iterate through the active neurons and propagate the signal
for(int i = 0; i < active_set_size[idx1]; ++i){
idx = active_neuron_set[i];
is_reachable_from_destination[ idx ] = true;
active_neuron = this->neurons->at( idx );
if(is_reachable_from_source[ idx ]){
n_new_neurons++;
}
for(Connection* connection: *(active_neuron->get_connections_in())){
target_neuron = connection->get_neuron_in( );
idx_target = target_neuron->get_idx( );
if( visited_neurons[idx_target] ){
//this neuron was already analyzed
continue;
}
visited_neurons[idx_target] = true;
active_neuron_set[active_set_size[idx2] + n * idx2] = idx_target;
active_set_size[idx2]++;
}
}
idx1 = idx2;
idx2 = (idx1 + 1) % 2;
active_set_size[idx2] = 0;
}
/* FOR CONSISTENCY REASONS */
for(size_t in: input_neuron_indices){
if( !is_reachable_from_destination[in] ){
n_new_neurons++;
}
is_reachable_from_destination[in] = true;
}
/* FOR CONSISTENCY REASONS */
for(size_t in: output_neuron_indices){
if( !is_reachable_from_source[in] ){
n_new_neurons++;
}
is_reachable_from_source[in] = true;
}
/* WE FORM THE SET OF NEURONS IN THE OUTPUT NETWORK */
if(n_new_neurons > 0){
// printf("Number of new neurons: %d\n", n_new_neurons);
output_net = new NeuralNetwork();
output_net->set_weight_array( this->connection_weights );
std::vector<size_t > local_inputs(0), local_outputs(0);
local_inputs.reserve(input_neuron_indices.size());
local_outputs.reserve(output_neuron_indices.size());
std::vector<Neuron*> local_n_arr(0);
local_n_arr.reserve( n_new_neurons );
std::vector<Neuron*> local_local_n_arr(0);
local_local_n_arr.reserve( n_new_neurons );
int * neuron_local_mapping = new int[ n ];
std::fill(neuron_local_mapping, neuron_local_mapping + n, -1);
idx = 0;
for(size_t i = 0; i < n; ++i){
if(is_reachable_from_source[i] && is_reachable_from_destination[i]){
neuron_local_mapping[i] = (int)idx;
idx++;
Neuron *new_neuron = this->neurons->at(i)->get_copy( );
output_net->add_neuron( new_neuron );
local_local_n_arr.push_back( new_neuron );
local_n_arr.push_back( this->neurons->at(i) );
}
}
for(size_t in: input_neuron_indices){
local_inputs.push_back(neuron_local_mapping[in]);
}
for(size_t in: output_neuron_indices){
local_outputs.push_back(neuron_local_mapping[in]);
}
// printf("%d\n", local_n_arr.size());
// printf("inputs: %d, outputs: %d\n", local_inputs.size(), local_outputs.size());
int local_idx_1, local_idx_2;
for(Neuron* source_neuron: local_n_arr){
//we also add the relevant edges
local_idx_1 = neuron_local_mapping[source_neuron->get_idx()];
for(Connection* connection: *(source_neuron->get_connections_out( ))){
target_neuron = connection->get_neuron_out();
local_idx_2 = neuron_local_mapping[target_neuron->get_idx()];
if(local_idx_2 >= 0){
//this edge is part of the subnetwork
Connection* new_connection = connection->get_copy( local_local_n_arr[local_idx_1], local_local_n_arr[local_idx_2] );
local_local_n_arr[local_idx_1]->add_connection_out(new_connection);
local_local_n_arr[local_idx_2]->add_connection_in(new_connection);
// printf("adding a connection between neurons %d, %d\n", local_idx_1, local_idx_2);
}
}
}
output_net->specify_input_neurons(local_inputs);
output_net->specify_output_neurons(local_outputs);
delete [] neuron_local_mapping;
}
delete [] is_reachable_from_source;
delete [] is_reachable_from_destination;
delete [] part_of_subnetwork;
delete [] visited_neurons;
delete [] active_neuron_set;
return output_net;
}

Michal Kravcenko
committed
NeuralNetwork::~NeuralNetwork() {
if(this->neurons){
delete this->neurons;
this->neurons = nullptr;
}
if(this->output_neurons){
delete this->output_neurons;
this->output_neurons = nullptr;
}
if(this->input_neurons){
delete this->input_neurons;
this->input_neurons = nullptr;
}
if(this->active_eval_set){
delete this->active_eval_set;
this->active_eval_set = nullptr;
}
if(this->connection_weights && this->delete_weights){
delete this->connection_weights;
this->connection_weights = nullptr;
}

Michal Kravcenko
committed
}
int NeuralNetwork::add_neuron(Neuron *n) {

Michal Kravcenko
committed
this->neurons->push_back(n);
this->in_out_determined = false;
n->set_idx(this->neurons->size() - 1);
return n->get_idx();
}
size_t NeuralNetwork::add_connection_simple(int n1_idx, int n2_idx) {
return add_connection_simple(n1_idx, n2_idx, -1);
size_t NeuralNetwork::add_connection_simple(int n1_idx, int n2_idx, size_t weight_idx) {
return add_connection_simple(n1_idx, n2_idx, weight_idx, 1);
size_t NeuralNetwork::add_connection_simple(int n1_idx, int n2_idx, size_t weight_idx, double weight_value) {
// TODO generate weight_value automatically from normal distribution
if(weight_idx < 0 || weight_idx >= this->connection_weights->size()){
//this weight is a new one, we add it to the system of weights
this->connection_weights->push_back(weight_value);
weight_idx = (int)this->connection_weights->size() - 1;
}
Neuron *neuron_out = this->neurons->at(n1_idx);
Neuron *neuron_in = this->neurons->at(n2_idx);
ConnectionWeightIdentity *con_weight_u1u2 = new ConnectionWeightIdentity(this->connection_weights);
con_weight_u1u2->SetParamIndex(weight_idx, 0);
Connection *u1u2 = new Connection(neuron_out, neuron_in, con_weight_u1u2);
neuron_out->add_connection_out(u1u2);
neuron_in->add_connection_in(u1u2);
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
return weight_idx;
}
void NeuralNetwork::add_connection_shared_power1(int n1_idx, int n2_idx, size_t weight_idx) {
std::function<double(double *, size_t*, size_t)> weight_function = [](double * weight_array, size_t * index_array, size_t n_params){
return weight_array[index_array[0]];
};
size_t* weight_indices = new size_t[1];
double* weight_values = new double[1];
weight_indices[0] = weight_idx;
this->add_connection_general( n1_idx, n2_idx, &weight_function, weight_indices, weight_values, 1);
delete [] weight_indices;
delete [] weight_values;
}
void NeuralNetwork::add_connection_shared_power2(int n1_idx, int n2_idx, size_t weight_idx) {
std::function<double(double *, size_t*, size_t)> weight_function = [](double * weight_array, size_t * index_array, size_t n_params){
return weight_array[index_array[0]] * weight_array[index_array[0]];
};
size_t* weight_indices = new size_t[1];
double* weight_values = new double[1];
weight_indices[0] = weight_idx;
this->add_connection_general( n1_idx, n2_idx, &weight_function, weight_indices, weight_values, 1);
delete [] weight_indices;
delete [] weight_values;
}
void NeuralNetwork::add_connection_general(int n1_idx, int n2_idx, std::function<double(double *, size_t*, size_t)> *f,
size_t* weight_indices, double* weight_values, size_t n_weights) {
ConnectionWeight *con_weight_u1u2 = new ConnectionWeight(n_weights, this->connection_weights);
//we analyze weights
size_t weight_idx = 0;
double weight_value = 0.0;
for(size_t wi = 0; wi < n_weights; ++wi){
weight_idx = weight_indices[wi];
weight_value = weight_values[wi];
if(weight_idx < 0 || weight_idx >= this->connection_weights->size()){
//this weight is a new one, we add it to the system of weights
this->connection_weights->push_back(weight_value);
weight_indices[wi] = (int)this->connection_weights->size() - 1;
}
con_weight_u1u2->SetParamIndex(weight_indices[wi], wi);
}
Neuron *neuron_out = this->neurons->at(n1_idx);
Neuron *neuron_in = this->neurons->at(n2_idx);
Connection *u1u2 = new Connection(neuron_out, neuron_in, con_weight_u1u2);
neuron_out->add_connection_out(u1u2);
neuron_in->add_connection_in(u1u2);

Michal Kravcenko
committed
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
}
void NeuralNetwork::determine_inputs_outputs() {
if(this->output_neurons){
delete this->output_neurons;
}
if(this->input_neurons){
delete this->input_neurons;
}
this->output_neurons = new std::vector<Neuron*>(0);
this->input_neurons = new std::vector<Neuron*>(0);
if(this->active_eval_set == nullptr){
this->active_eval_set = new std::vector<Neuron*>(this->neurons->size() * 2);
}
else{
this->active_eval_set->resize(this->neurons->size() * 2);
}
for(Neuron* neuron: *this->neurons){
if(neuron->get_connections_out()->empty()){
//this neuron has no outgoing connections, it is the output neuron
this->output_neurons->push_back(neuron);
}
else if(neuron->get_connections_in()->empty()){
//this neuron has no incoming connections, it is the input neuron
this->input_neurons->push_back(neuron);
}
}
this->n_inputs = this->input_neurons->size();
this->n_outputs = this->output_neurons->size();

Michal Kravcenko
committed
this->in_out_determined = true;
}
void NeuralNetwork::set_weight_array(std::vector<double> *weight_ptr) {

Michal Kravcenko
committed
if( this->connection_weights && this->delete_weights ){
delete this->connection_weights;
}
this->connection_weights = weight_ptr;
this->delete_weights = false;
this->n_weights = this->connection_weights->size();
}
void NeuralNetwork::eval_single(std::vector<double> &input, std::vector<double> &output, double * custom_weights) {
if(!this->in_out_determined && this->n_inputs * this->n_outputs <= 0){
// this->determine_inputs_outputs();
std::cerr << "Input and output neurons have not been specified\n" << std::endl;

Michal Kravcenko
committed
}
if(this->n_inputs != input.size()){
std::cerr << "Error, input size != Network input size\n" << std::endl;
exit(-1);

Michal Kravcenko
committed
}
if(this->n_outputs != output.size()){
std::cerr << "Error, output size != Network output size\n" << std::endl;
exit(-1);

Michal Kravcenko
committed
}
// std::vector<double> *weight_ptr = this->connection_weights;
// std::vector<double> *custom_weight_ptr = nullptr;
//
//
if(custom_weights != nullptr){
// custom_weight_ptr = new std::vector<double>(custom_weights, custom_weights + this->n_weights);
// this->connection_weights = custom_weight_ptr;
this->connection_weights->assign(custom_weights, custom_weights + this->n_weights);
}

Michal Kravcenko
committed
std::fill(output.begin(), output.end(), 0.0);
//reset of the potentials
for(Neuron* neuron: *this->neurons){
neuron->set_potential(0.0);
neuron->set_saturation_in(0);
neuron->set_saturation_out(0);
}
if(this->active_eval_set == nullptr){
this->active_eval_set = new std::vector<Neuron*>(this->neurons->size() * 2);
}
else{
this->active_eval_set->resize(this->neurons->size() * 2);
}

Michal Kravcenko
committed
int active_set_size[2];
active_set_size[0] = 0;
active_set_size[1] = 0;
int idx1 = 0, idx2 = 1;
active_set_size[0] = this->n_inputs;
int i = 0;
auto n = this->neurons->size();
for(Neuron* neuron: *this->input_neurons){
this->active_eval_set->at(i) = neuron;
neuron->set_potential(input[i]);

Michal Kravcenko
committed
#ifdef VERBOSE_NN_EVAL
printf("INPUT NEURON %d, POTENTIAL: %f\n", neuron, input[i]);
#endif

Michal Kravcenko
committed
++i;
}
Neuron* active_neuron;
Neuron* target_neuron;
while(active_set_size[idx1] > 0){
//we iterate through the active neurons and propagate the signal
for(i = 0; i < active_set_size[idx1]; ++i){
active_neuron = this->active_eval_set->at(i + n * idx1);
active_neuron->activate();//computes the activation function on its potential

Michal Kravcenko
committed
#ifdef VERBOSE_NN_EVAL
printf(" active neuron %d, potential: %f, state: %f\n", active_neuron, active_neuron->get_potential(), active_neuron->get_state());
#endif

Michal Kravcenko
committed
for(Connection* connection: *(active_neuron->get_connections_out())){
connection->pass_signal();
target_neuron = connection->get_neuron_out();
target_neuron->adjust_saturation_in(1);
if(target_neuron->is_saturated_in()){
this->active_eval_set->at(active_set_size[idx2] + n * idx2) = target_neuron;
active_set_size[idx2]++;
}
}
}

Michal Kravcenko
committed
#ifdef VERBOSE_NN_EVAL
printf("-----------\n");
#endif

Michal Kravcenko
committed
idx1 = idx2;
idx2 = (idx1 + 1) % 2;
active_set_size[idx2] = 0;
}
i = 0;
for(Neuron* neuron: *this->output_neurons){
output[i] = neuron->get_state();

Michal Kravcenko
committed

Michal Kravcenko
committed
#ifdef VERBOSE_NN_EVAL
printf("OUTPUT NEURON %d, VALUE: %f\n", neuron, output[i]);
#endif

Michal Kravcenko
committed

Michal Kravcenko
committed
++i;
}

Michal Kravcenko
committed

Michal Kravcenko
committed
}
void NeuralNetwork::copy_weights(double *weights) {
for(unsigned int i = 0; i < this->connection_weights->size(); ++i){
(*this->connection_weights)[i] = weights[i];
}
std::vector<double>* NeuralNetwork::get_weights_ptr(){
return this->connection_weights;
}
return;
}
boost::random::mt19937 gen;
// Init weight guess ("optimal" for logistic activation functions)
double r = 4 * sqrt(6./(this->n_weights));
boost::random::uniform_real_distribution<> dist(-r, r);
for(size_t i = 0; i < this->n_weights; i++) {
this->connection_weights->at(i) = dist(gen);
}
}
size_t NeuralNetwork::get_n_inputs() {
return this->n_inputs;
}
size_t NeuralNetwork::get_n_outputs() {
return this->n_outputs;
}
size_t NeuralNetwork::get_n_weights() {
if(!this->n_weights) {
this->n_weights = this->connection_weights->size();
}
return this->n_weights;
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
}
void NeuralNetwork::specify_input_neurons(std::vector<size_t> &input_neurons_indices) {
if( !this->input_neurons ){
this->input_neurons = new std::vector<Neuron*>();
}
this->input_neurons->reserve( input_neurons_indices.size() );
for( size_t e: input_neurons_indices ){
if( e < 0 || e >= this->neurons->size() ){
continue;
}
this->input_neurons->push_back( this->neurons->at(e) );
}
this->n_inputs = this->input_neurons->size();
}
void NeuralNetwork::specify_output_neurons(std::vector<size_t> &output_neurons_indices) {
if( !this->output_neurons ){
this->output_neurons = new std::vector<Neuron*>();
}
this->output_neurons->reserve( output_neurons_indices.size() );
for( size_t e: output_neurons_indices ){
if( e < 0 || e >= this->neurons->size() ){
continue;
}
this->output_neurons->push_back( this->neurons->at(e) );
}
this->n_outputs = this->output_neurons->size();
}
void NeuralNetwork::print_weights() {
printf("Connection weights: ");
if(this->connection_weights){
for( size_t i = 0; i < this->connection_weights->size() - 1; ++i){
printf("%f, ", this->connection_weights->at(i));
}
printf("%f", this->connection_weights->at(this->connection_weights->size() - 1));
}
printf("\n");
}
void NeuralNetwork::print_stats(){
printf("Number of neurons: %d, number of weights: %d\n", this->neurons->size(), this->n_weights);
}