Skip to content

Commit 2ff3388

Browse files
authored
Merge pull request #59 from MikeKis/master
Adding MNIST example
2 parents a005011 + f94bb59 commit 2ff3388

31 files changed

Lines changed: 1807 additions & 84 deletions

File tree

.gitattributes

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,3 @@
1-
# Set the default behavior, in case people don't have core.autocrlf set.
2-
#* text=auto
3-
4-
# Explicitly declare text files you want to always be normalized and converted
5-
# to native line endings on checkout.
6-
#*.c text
7-
#*.cpp text
8-
#*.h text
9-
#*.txt text
10-
#*.md text
11-
121
# Declare files that will always have CRLF line endings on checkout.
132
#*.sln text eol=crlf
143

cmake/knp-functions.cmake

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
#[[
2-
© 2024 AO Kaspersky Lab
3-
4-
Licensed under the Apache License, Version 2.0 (the "License");
5-
you may not use this file except in compliance with the License.
6-
You may obtain a copy of the License at
7-
8-
http://www.apache.org/licenses/LICENSE-2.0
9-
10-
Unless required by applicable law or agreed to in writing, software
11-
distributed under the License is distributed on an "AS IS" BASIS,
12-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
See the License for the specific language governing permissions and
14-
limitations under the License.
15-
]]
16-
1+
#[[
2+
© 2024 AO Kaspersky Lab
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
]]
16+
1717
#
1818
# Kaspersky Neuromorphic Platform build functions. Artiom N.(cl)2023.
1919
#
@@ -66,7 +66,8 @@ function(knp_set_target_parameters target visibility)
6666

6767
target_compile_definitions("${target}" ${visibility}
6868
# $<$<CONFIG:Debug>:_FORTIFY_SOURCE=2>
69-
$<$<CONFIG:Release>:_FORTIFY_SOURCE=1>)
69+
# $<$<CONFIG:Release>:_FORTIFY_SOURCE=1>
70+
)
7071

7172
target_compile_definitions("${target}" ${visibility} $<$<COMPILE_LANG_AND_ID:C,MSVC>:NOMINMAX>)
7273
target_compile_definitions("${target}" ${visibility} $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:NOMINMAX>)

examples/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ set(CMAKE_CXX_STANDARD 17)
2424
add_subdirectory(save-load-networks)
2525
add_subdirectory(simple-network)
2626
add_subdirectory(mnist-client)
27+
add_subdirectory(mnist-learn)
2728

2829
if (KNP_INSTALL)
2930
install(DIRECTORY "."
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#[[
2+
© 2024 AO Kaspersky Lab
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
]]
16+
17+
cmake_minimum_required(VERSION 3.22)
18+
project(mnist_learn_example)
19+
20+
set(CMAKE_CXX_STANDARD 20)
21+
set(CMAKE_VERBOSE_MAKEFILE ON)
22+
23+
find_package(Boost COMPONENTS program_options REQUIRED )
24+
25+
# Need to check if the example build inside KNP tree.
26+
if (NOT TARGET KNP::BaseFramework::Core)
27+
find_package(knp-base-framework REQUIRED)
28+
endif()
29+
30+
add_executable(mnist_learn_example main.cpp data_read.cpp construct_network.cpp evaluation.cpp logging.cpp train.cpp inference.cpp wta.cpp)
31+
target_link_libraries(mnist_learn_example PRIVATE KNP::BaseFramework::Core ${Boost_LIBRARIES})

examples/mnist-learn/MNIST.zip

11 MB
Binary file not shown.
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/**
2+
* @file construct_network.cpp
3+
* @brief Functions for network construction.
4+
* @kaspersky_support A. Vartenkov
5+
* @date 03.12.2024
6+
* @license Apache 2.0
7+
* @copyright © 2024 AO Kaspersky Lab
8+
*
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
*/
21+
22+
#include "construct_network.h"
23+
24+
#include <knp/core/population.h>
25+
#include <knp/core/projection.h>
26+
#include <knp/framework/sonata/network_io.h>
27+
#include <knp/neuron-traits/all_traits.h>
28+
#include <knp/synapse-traits/all_traits.h>
29+
30+
#include "generators.h"
31+
32+
// A list of short type names to make reading easier.
33+
using BlifatPopulation = knp::core::Population<knp::neuron_traits::BLIFATNeuron>;
34+
using ResourceBlifatPopulation = knp::core::Population<knp::neuron_traits::SynapticResourceSTDPBLIFATNeuron>;
35+
using ResourceNeuron = knp::neuron_traits::SynapticResourceSTDPBLIFATNeuron;
36+
using ResourceNeuronData = knp::neuron_traits::neuron_parameters<ResourceNeuron>;
37+
38+
// Network hyperparameters. You may want to fine-tune these.
39+
constexpr float default_threshold = 8.571F;
40+
constexpr float min_synaptic_weight = -0.7;
41+
constexpr float max_synaptic_weight = 0.864249F;
42+
constexpr float base_weight_value = 0.000F;
43+
constexpr int neuron_dopamine_period = 10;
44+
constexpr int synapse_dopamine_period = 10;
45+
constexpr float l_neuron_potential_decay = 1.0 - 1.0 / 3.0;
46+
constexpr float dopamine_parameter = 0.042F;
47+
constexpr float dopamine_value = dopamine_parameter;
48+
constexpr float threshold_weight_coeff = 0.023817F;
49+
50+
//
51+
// Network geometry.
52+
//
53+
54+
// Number of neurons reserved per a single digit.
55+
constexpr int neurons_per_column = 15;
56+
57+
// Ten possible digits, one column per each.
58+
constexpr int num_possible_labels = classes_in_mnist;
59+
60+
// All columns are a part of the same population.
61+
constexpr int num_input_neurons = neurons_per_column * num_possible_labels;
62+
63+
// Number of pixels for a single MNIST image.
64+
constexpr int input_size = 28 * 28;
65+
66+
// Dense input projection from 28 * 28 image to population of 150 neurons.
67+
constexpr int input_projection_size = input_size * num_input_neurons;
68+
69+
70+
// Intermediate population neurons.
71+
template <class Neuron>
72+
struct PopulationData
73+
{
74+
size_t size_;
75+
knp::neuron_traits::neuron_parameters<Neuron> neuron_;
76+
};
77+
78+
79+
enum PopIndexes
80+
{
81+
INPUT = 0,
82+
DOPAMINE = 1,
83+
OUTPUT = 2,
84+
GATE = 3
85+
};
86+
87+
88+
// Calculate synaptic resource value given synapse weight.
89+
float resource_from_weight(float weight, float min_weight, float max_weight)
90+
{
91+
// Max weight is only possible with infinite resource, so we should select a value less than that.
92+
float eps = 1e-6;
93+
if (min_weight > max_weight) std::swap(min_weight, max_weight);
94+
if (weight < min_weight || weight >= max_weight - eps)
95+
throw std::logic_error("Weight should not be less than min_weight, more than max_weight or too close to it.");
96+
double diff = max_weight - min_weight;
97+
double over = weight - min_weight;
98+
return static_cast<float>(over * diff / (diff - over));
99+
}
100+
101+
102+
// Add populations to the network.
103+
auto add_subnetwork_populations(AnnotatedNetwork &result)
104+
{
105+
result.data_.wta_data_.push_back({});
106+
// Parameters for a default neuron.
107+
ResourceNeuronData default_neuron{{}};
108+
default_neuron.activation_threshold_ = default_threshold;
109+
ResourceNeuronData l_neuron = default_neuron;
110+
l_neuron.potential_decay_ = l_neuron_potential_decay;
111+
l_neuron.d_h_ = -dopamine_value;
112+
l_neuron.dopamine_plasticity_time_ = neuron_dopamine_period;
113+
l_neuron.synapse_sum_threshold_coefficient_ = threshold_weight_coeff;
114+
l_neuron.isi_max_ = 10;
115+
116+
struct PopulationRole
117+
{
118+
PopulationData<ResourceNeuron> pd_;
119+
bool for_inference_;
120+
bool output_;
121+
std::string name_;
122+
};
123+
auto dopamine_neuron = default_neuron;
124+
dopamine_neuron.total_blocking_period_ = 0;
125+
// Create initial neuron data for populations. There are four of them.
126+
std::vector<PopulationRole> pop_data{
127+
{{num_input_neurons, l_neuron}, true, false, "INPUT"},
128+
{{num_input_neurons, dopamine_neuron}, false, false, "DOPAMINE"},
129+
{{num_possible_labels, default_neuron}, true, true, "OUTPUT"},
130+
{{num_possible_labels, default_neuron}, false, false, "GATE"}};
131+
132+
// Creating a population. It's usually very simple as all neurons are usually the same.
133+
std::vector<knp::core::UID> population_uids;
134+
for (auto &pop_init_data : pop_data)
135+
{
136+
// A very simple neuron generator returning a default neuron.
137+
auto neuron_generator = [&pop_init_data](size_t index) { return pop_init_data.pd_.neuron_; };
138+
139+
knp::core::UID uid;
140+
result.network_.add_population(ResourceBlifatPopulation{uid, neuron_generator, pop_init_data.pd_.size_});
141+
population_uids.push_back(uid);
142+
result.data_.population_names_[uid] = pop_init_data.name_;
143+
if (pop_init_data.for_inference_) result.data_.inference_population_uids_.insert(uid);
144+
if (pop_init_data.output_) result.data_.output_uids_.push_back(uid);
145+
}
146+
147+
result.data_.wta_data_.back().first.push_back(population_uids[INPUT]);
148+
return std::make_pair(population_uids, pop_data);
149+
}
150+
151+
152+
// Create network for MNIST.
153+
AnnotatedNetwork create_example_network(int num_compound_networks)
154+
{
155+
AnnotatedNetwork result;
156+
for (int i = 0; i < num_compound_networks; ++i)
157+
{
158+
auto [population_uids, pop_data] = add_subnetwork_populations(result);
159+
160+
// Now that we added all the populations we need, we have to connect them with projections.
161+
// Creating a projection is more tricky, as all the connection logic should be described in a generator.
162+
// Create a default synapse.
163+
ResourceSynapseParams default_synapse;
164+
auto afferent_synapse = default_synapse;
165+
afferent_synapse.rule_.synaptic_resource_ =
166+
resource_from_weight(base_weight_value, min_synaptic_weight, max_synaptic_weight);
167+
afferent_synapse.rule_.dopamine_plasticity_period_ = synapse_dopamine_period;
168+
afferent_synapse.rule_.w_min_ = min_synaptic_weight;
169+
afferent_synapse.rule_.w_max_ = max_synaptic_weight;
170+
171+
// 1. Trainable input projection.
172+
ResourceDeltaProjection input_projection{
173+
knp::core::UID{false}, population_uids[INPUT], make_dense_generator(input_size, afferent_synapse),
174+
input_projection_size};
175+
result.data_.projections_from_raster_.push_back(input_projection.get_uid());
176+
input_projection.unlock_weights(); // Trainable
177+
result.network_.add_projection(input_projection);
178+
result.data_.inference_internal_projection_.insert(input_projection.get_uid());
179+
180+
default_synapse.weight_ = 9;
181+
182+
// 2. Activating projection. It sends signals from labels to dopamine population.
183+
const DeltaSynapseData default_activating_synapse{1, 1, knp::synapse_traits::OutputType::BLOCKING};
184+
DeltaProjection projection_2{
185+
knp::core::UID{false}, population_uids[DOPAMINE],
186+
make_aligned_generator(pop_data[INPUT].pd_.size_, pop_data[DOPAMINE].pd_.size_, default_activating_synapse),
187+
pop_data[INPUT].pd_.size_};
188+
result.network_.add_projection(projection_2);
189+
result.data_.wta_data_[i].second.push_back(projection_2.get_uid());
190+
191+
// 3. Dopamine projection, it goes from dopamine population to input population.
192+
const DeltaSynapseData default_dopamine_synapse{dopamine_value, 1, knp::synapse_traits::OutputType::DOPAMINE};
193+
DeltaProjection projection_3{
194+
population_uids[DOPAMINE], population_uids[INPUT],
195+
make_aligned_generator(pop_data[DOPAMINE].pd_.size_, pop_data[INPUT].pd_.size_, default_dopamine_synapse),
196+
pop_data[INPUT].pd_.size_};
197+
198+
result.network_.add_projection(projection_3);
199+
result.data_.inference_internal_projection_.insert(projection_3.get_uid());
200+
201+
// 4. Strong excitatory projection going to output neurons.
202+
DeltaProjection projection_4{
203+
knp::core::UID{false}, population_uids[OUTPUT],
204+
make_aligned_generator(pop_data[INPUT].pd_.size_, pop_data[OUTPUT].pd_.size_, default_synapse),
205+
pop_data[INPUT].pd_.size_};
206+
result.data_.wta_data_[i].second.push_back(projection_4.get_uid());
207+
208+
result.network_.add_projection(projection_4);
209+
result.data_.inference_internal_projection_.insert(projection_4.get_uid());
210+
211+
// 5. Blocking projection.
212+
const DeltaSynapseData default_blocking_synapse{-20, 1, knp::synapse_traits::OutputType::BLOCKING};
213+
DeltaProjection projection_5{
214+
population_uids[OUTPUT], population_uids[GATE],
215+
make_aligned_generator(pop_data[OUTPUT].pd_.size_, pop_data[GATE].pd_.size_, default_blocking_synapse),
216+
num_possible_labels};
217+
result.network_.add_projection(projection_5);
218+
result.data_.inference_internal_projection_.insert(projection_5.get_uid());
219+
220+
// 6. Strong excitatory projection going from ground truth classes.
221+
DeltaProjection projection_6{
222+
knp::core::UID{false}, population_uids[DOPAMINE],
223+
make_aligned_generator(num_possible_labels, pop_data[DOPAMINE].pd_.size_, default_synapse),
224+
pop_data[DOPAMINE].pd_.size_};
225+
result.network_.add_projection(projection_6);
226+
result.data_.projections_from_classes_.push_back(projection_6.get_uid());
227+
228+
// 7. Strong slow excitatory projection going from ground truth classes.
229+
auto slow_synapse = default_synapse;
230+
slow_synapse.delay_ = 10;
231+
DeltaProjection projection_7{
232+
knp::core::UID{false}, population_uids[GATE],
233+
make_aligned_generator(num_possible_labels, pop_data[GATE].pd_.size_, slow_synapse),
234+
pop_data[GATE].pd_.size_};
235+
result.network_.add_projection(projection_7);
236+
result.data_.projections_from_classes_.push_back(projection_7.get_uid());
237+
238+
// 8. Strong inhibitory projection from ground truth input.
239+
auto inhibitory_synapse = default_synapse;
240+
inhibitory_synapse.weight_ = -30;
241+
DeltaProjection projection_8{
242+
knp::core::UID{false}, population_uids[GATE],
243+
make_exclusive_generator(num_possible_labels, inhibitory_synapse),
244+
num_possible_labels * (pop_data[GATE].pd_.size_ - 1)};
245+
result.data_.projections_from_classes_.push_back(projection_8.get_uid());
246+
result.network_.add_projection(projection_8);
247+
248+
// 9. Weak excitatory projection.
249+
auto weak_excitatory_synapse = default_synapse;
250+
weak_excitatory_synapse.weight_ = 3;
251+
DeltaProjection projection_9{
252+
population_uids[GATE], population_uids[INPUT],
253+
make_aligned_generator(pop_data[GATE].pd_.size_, pop_data[INPUT].pd_.size_, weak_excitatory_synapse),
254+
pop_data[INPUT].pd_.size_};
255+
result.network_.add_projection(projection_9);
256+
result.data_.inference_internal_projection_.insert(projection_9.get_uid());
257+
}
258+
259+
// Return created network.
260+
return result;
261+
}

0 commit comments

Comments
 (0)