Error-driven dopamine (delta-rule) training mode in KNP: network option, isolated backend
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
39079670c0
commit
64fc5161e0
8 changed files with 267 additions and 32 deletions
|
|
@ -19,7 +19,7 @@ ap.add_argument('--columns', type=int, default=20)
|
||||||
ap.add_argument('--seed', type=int, default=42)
|
ap.add_argument('--seed', type=int, default=42)
|
||||||
ap.add_argument('--jitter', type=float, default=20)
|
ap.add_argument('--jitter', type=float, default=20)
|
||||||
ap.add_argument('--quant', choices=['default', 'weights'], default='default')
|
ap.add_argument('--quant', choices=['default', 'weights'], default='default')
|
||||||
ap.add_argument('--backend', choices=['single', 'multi', 'tuned', 'fast'], default='single')
|
ap.add_argument('--backend', choices=['single', 'multi', 'tuned', 'fast', 'delta'], default='single')
|
||||||
ap.add_argument('--threads', type=int, default=2)
|
ap.add_argument('--threads', type=int, default=2)
|
||||||
a = ap.parse_args()
|
a = ap.parse_args()
|
||||||
if a.backend == 'multi':
|
if a.backend == 'multi':
|
||||||
|
|
@ -41,7 +41,8 @@ env['KNP_CPU_THREADS'] = str(a.threads)
|
||||||
backends = {'single': '/home/int/knp-build/bin/libknp-cpu-single-threaded-backend.so',
|
backends = {'single': '/home/int/knp-build/bin/libknp-cpu-single-threaded-backend.so',
|
||||||
'multi': '/home/int/knp-build/bin/libknp-cpu-multi-threaded-backend.so',
|
'multi': '/home/int/knp-build/bin/libknp-cpu-multi-threaded-backend.so',
|
||||||
'tuned': str(root / 'acceleration/libknp-cpu-tuned-backend.so'),
|
'tuned': str(root / 'acceleration/libknp-cpu-tuned-backend.so'),
|
||||||
'fast': str(root / 'fast-cpu/libknp-cpu-fast-backend.so')}
|
'fast': str(root / 'fast-cpu/libknp-cpu-fast-backend.so'),
|
||||||
|
'delta': '/home/int/run/delta-cpu/libknp-cpu-delta-backend.so'}
|
||||||
command = [str(root / 'build/cats_dogs'), '-m', 'altai',
|
command = [str(root / 'build/cats_dogs'), '-m', 'altai',
|
||||||
'--images', str(a.data_dir / (a.dataset + '.bin')),
|
'--images', str(a.data_dir / (a.dataset + '.bin')),
|
||||||
'--labels', str(a.data_dir / (a.dataset + '.target')),
|
'--labels', str(a.data_dir / (a.dataset + '.target')),
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,10 @@
|
||||||
// KNP_COL_GATE=<g1,g2,..> gate->input pulses: weight g_k with delay k (default: single 10000 pulse with delay 1).
|
// KNP_COL_GATE=<g1,g2,..> gate->input pulses: weight g_k with delay k (default: single 10000 pulse with delay 1).
|
||||||
// KNP_COL_PUNISH=<d> target->input dopamine -d*1000 to every column except the label's one (reward is 0.179376):
|
// KNP_COL_PUNISH=<d> target->input dopamine -d*1000 to every column except the label's one (reward is 0.179376):
|
||||||
// neurons of wrong columns that fired recently lose the synapses that made them fire.
|
// neurons of wrong columns that fired recently lose the synapses that made them fire.
|
||||||
|
// KNP_DELTA=<a>,<b> error-driven (delta-rule) training; needs a backend built with KNP_DELTA_RULE support
|
||||||
|
// (augment/build_delta_cpu.py). The label sends dopamine +a to its neurons on every active
|
||||||
|
// step, every input neuron's own spike sends it dopamine -b on the next step, so dopamine =
|
||||||
|
// target - output; no forcing gate, no label inhibition, no Hebbian/stability changes.
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
float env_float(const char *name, float default_value)
|
float env_float(const char *name, float default_value)
|
||||||
|
|
@ -110,6 +114,13 @@ static NetworkPopulations create_populations(NetworkConstructor &constructor)
|
||||||
input_neuron.stability_change_parameter_ = stability_change_parameter;
|
input_neuron.stability_change_parameter_ = stability_change_parameter;
|
||||||
input_neuron.resource_drain_coefficient_ = resource_drain_coefficient;
|
input_neuron.resource_drain_coefficient_ = resource_drain_coefficient;
|
||||||
input_neuron.synapse_sum_threshold_coefficient_ = synapse_sum_threshold_coefficient;
|
input_neuron.synapse_sum_threshold_coefficient_ = synapse_sum_threshold_coefficient;
|
||||||
|
if (env_float_list("KNP_DELTA", {}).size() == 2)
|
||||||
|
{
|
||||||
|
// Only the dopamine (error) rule changes synapses.
|
||||||
|
input_neuron.d_h_ = 0;
|
||||||
|
input_neuron.stability_change_parameter_ = 0;
|
||||||
|
input_neuron.synapse_sum_threshold_coefficient_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Create input population with specialized parameters for image processing.
|
// Create input population with specialized parameters for image processing.
|
||||||
const auto &input_pop = constructor.add_population(
|
const auto &input_pop = constructor.add_population(
|
||||||
|
|
@ -179,6 +190,14 @@ static void create_projections(
|
||||||
target_to_input_synapse_dopamine.output_type_ = knp::synapse_traits::OutputType::DOPAMINE;
|
target_to_input_synapse_dopamine.output_type_ = knp::synapse_traits::OutputType::DOPAMINE;
|
||||||
target_to_input_synapse_dopamine.weight_ = 0.179376 * 1000;
|
target_to_input_synapse_dopamine.weight_ = 0.179376 * 1000;
|
||||||
target_to_input_synapse_dopamine.delay_ = 3;
|
target_to_input_synapse_dopamine.delay_ = 3;
|
||||||
|
const std::vector<float> delta = env_float_list("KNP_DELTA", {});
|
||||||
|
const bool delta_mode = delta.size() == 2;
|
||||||
|
if (delta_mode)
|
||||||
|
{
|
||||||
|
// Label spikes come on every active step, in step with the image spikes.
|
||||||
|
target_to_input_synapse_dopamine.weight_ = delta[0];
|
||||||
|
target_to_input_synapse_dopamine.delay_ = 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Create dopamine projection connecting target to input population.
|
// Create dopamine projection connecting target to input population.
|
||||||
auto target_to_input_proj_dopamine = constructor.add_projection(
|
auto target_to_input_proj_dopamine = constructor.add_projection(
|
||||||
|
|
@ -216,26 +235,32 @@ static void create_projections(
|
||||||
target_to_input_synapse_excitatory.weight_ = -30 * 1000;
|
target_to_input_synapse_excitatory.weight_ = -30 * 1000;
|
||||||
target_to_input_synapse_excitatory.delay_ = 4;
|
target_to_input_synapse_excitatory.delay_ = 4;
|
||||||
|
|
||||||
// Create excitatory projection connecting target population to input population.
|
// Create excitatory projection connecting target population to input population (not in delta mode).
|
||||||
auto target_to_input_proj_excitatory = constructor.add_projection(
|
if (!delta_mode)
|
||||||
target_to_input_synapse_excitatory, knp::framework::projection::creators::all_to_all<DeltaSynapse>,
|
{
|
||||||
pops.target_pop_, pops.input_pop_, false, false);
|
auto target_to_input_proj_excitatory = constructor.add_projection(
|
||||||
|
target_to_input_synapse_excitatory, knp::framework::projection::creators::all_to_all<DeltaSynapse>,
|
||||||
// Record excitatory projection for label processing.
|
pops.target_pop_, pops.input_pop_, false, false);
|
||||||
network.data_.projections_from_classes_.push_back(target_to_input_proj_excitatory);
|
|
||||||
|
// Record excitatory projection for label processing.
|
||||||
|
network.data_.projections_from_classes_.push_back(target_to_input_proj_excitatory);
|
||||||
|
}
|
||||||
|
|
||||||
// Configure synapse for target to gate projection.
|
// Configure synapse for target to gate projection.
|
||||||
DeltaSynapseParams target_to_gate_synapse;
|
DeltaSynapseParams target_to_gate_synapse;
|
||||||
target_to_gate_synapse.output_type_ = knp::synapse_traits::OutputType::EXCITATORY;
|
target_to_gate_synapse.output_type_ = knp::synapse_traits::OutputType::EXCITATORY;
|
||||||
target_to_gate_synapse.weight_ = 10 * 1000;
|
target_to_gate_synapse.weight_ = 10 * 1000;
|
||||||
|
|
||||||
// Create projection connecting target population to gate population.
|
// Create projection connecting target population to gate population (no forcing in delta mode).
|
||||||
auto target_to_gate_proj = constructor.add_projection(
|
if (!delta_mode)
|
||||||
target_to_gate_synapse, knp::framework::projection::creators::aligned<DeltaSynapse>, pops.target_pop_,
|
{
|
||||||
pops.gate_pop_, false, false);
|
auto target_to_gate_proj = constructor.add_projection(
|
||||||
|
target_to_gate_synapse, knp::framework::projection::creators::aligned<DeltaSynapse>, pops.target_pop_,
|
||||||
// Record gate projection for label processing.
|
pops.gate_pop_, false, false);
|
||||||
network.data_.projections_from_classes_.push_back(target_to_gate_proj);
|
|
||||||
|
// Record gate projection for label processing.
|
||||||
|
network.data_.projections_from_classes_.push_back(target_to_gate_proj);
|
||||||
|
}
|
||||||
|
|
||||||
// Configure synapse for input to output projection.
|
// Configure synapse for input to output projection.
|
||||||
DeltaSynapseParams input_to_output_synapse;
|
DeltaSynapseParams input_to_output_synapse;
|
||||||
|
|
@ -276,6 +301,31 @@ static void create_projections(
|
||||||
pops.input_pop_, false, false);
|
pops.input_pop_, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delta mode: a neuron's own spike sends it negative dopamine on the next step (dopamine = target - output).
|
||||||
|
if (delta_mode)
|
||||||
|
{
|
||||||
|
DeltaSynapseParams self_dopamine;
|
||||||
|
self_dopamine.output_type_ = knp::synapse_traits::OutputType::DOPAMINE;
|
||||||
|
self_dopamine.weight_ = -delta[1];
|
||||||
|
self_dopamine.delay_ = 1;
|
||||||
|
auto self_creator = [self_dopamine](
|
||||||
|
const knp::core::UID &pre_uid, const knp::core::UID &post_uid, size_t pre_size,
|
||||||
|
size_t post_size, auto)
|
||||||
|
{
|
||||||
|
return knp::framework::projection::creators::index_based<DeltaSynapse>(
|
||||||
|
pre_uid, post_uid, pre_size, post_size,
|
||||||
|
[self_dopamine](size_t pre, size_t post) -> std::optional<DeltaSynapseParams>
|
||||||
|
{
|
||||||
|
if (pre != post) return std::nullopt;
|
||||||
|
return self_dopamine;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
auto self_proj =
|
||||||
|
constructor.add_projection(self_dopamine, self_creator, pops.input_pop_, pops.input_pop_, false, false);
|
||||||
|
// Training signal only: not part of the inference network.
|
||||||
|
network.data_.inference_internal_projection_.erase(self_proj);
|
||||||
|
}
|
||||||
|
|
||||||
// Lateral inhibition inside each column.
|
// Lateral inhibition inside each column.
|
||||||
if (const float lateral = env_float("KNP_COL_LATERAL", 0); lateral > 0)
|
if (const float lateral = env_float("KNP_COL_LATERAL", 0); lateral > 0)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// cppcheck-suppress missingInclude
|
// cppcheck-suppress missingInclude
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
#include "global_config.h"
|
#include "global_config.h"
|
||||||
#include "network_functions.h"
|
#include "network_functions.h"
|
||||||
|
|
||||||
|
|
@ -37,7 +39,10 @@ make_training_labels_spikes_generator<knp::neuron_traits::AltAILIF>(const Datase
|
||||||
knp::core::messaging::SpikeData message;
|
knp::core::messaging::SpikeData message;
|
||||||
|
|
||||||
knp::core::Step local_step = step % steps_per_image;
|
knp::core::Step local_step = step % steps_per_image;
|
||||||
if (local_step == 11) message.push_back(dataset.get_data_for_training().first[step / steps_per_image].first);
|
// KNP_DELTA (error-driven training): the label is sent on every active step, in step with the image.
|
||||||
|
static const bool every_step = std::getenv("KNP_DELTA") != nullptr;
|
||||||
|
if (every_step ? local_step < active_steps : local_step == 11)
|
||||||
|
message.push_back(dataset.get_data_for_training().first[step / steps_per_image].first);
|
||||||
return message;
|
return message;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
changes-2026-10-15.md
Normal file
14
changes-2026-10-15.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Журнал работы — 15 октября 2026
|
||||||
|
|
||||||
|
## Ночь (продолжение): выход, обучаемый дофамином по ошибке, внутри KNP
|
||||||
|
|
||||||
|
Пользователь: «начинай, связь со мной не нужна». Цель — перенести в KNP правило, проверенное в симуляторе (выход «цель − ответ» × вход).
|
||||||
|
|
||||||
|
### Код
|
||||||
|
- `examples/.../models/altai/construct_network.cpp`: режим `KNP_DELTA=<a>,<b>`. Метка подаёт дофамин +a нейронам своего класса с задержкой 1 на каждом активном шаге; собственный спайк нейрона даёт ему дофамин −b на следующем шаге (проекция input→input, исключена из сети распознавания). Нет торможения по метке (−30000) и принуждения через гейт; у нейронов выключены Хебб (d_h=0), стабильность и порог от суммы весов. Используется с `KNP_COL_SIZE=1` (один нейрон на класс).
|
||||||
|
- `.../models/altai/spike_generators.cpp`: при `KNP_DELTA` метка отправляется на шагах 0–9 (а не на 11-м).
|
||||||
|
- `readout/build_delta_cpu.py` → `/home/int/run/delta-cpu/libknp-cpu-delta-backend.so`: изолированная копия бэкенда; `KNP_DELTA_RULE=<скорость>` — у нейрона с дофамином D ≠ 0 каждый обучаемый синапс, чей входной спайк пришёл с отставанием `KNP_DELTA_LAG` шагов, меняет ресурс на скорость·D (независимо от спайка самого нейрона); `KNP_DELTA_DEBUG` печатает гистограмму отставаний. Оригинальные библиотеки не менялись.
|
||||||
|
- Отладка: при отставании 0 обновлений не было (веса не менялись); гистограмма: отставания 1/2/3 = 406 тыс./252 тыс./120 тыс., 0 — ни одного. Дофамин приходит на шаг позже входа → `KNP_DELTA_LAG=1`.
|
||||||
|
- Пробный прогон (признаки coarse, 3000 обучение, 1000 проверка, 10 нейронов): скорость 0,3 — 48,5%, 1 — 76,8%, 3 — **77,4%** (колонки 200 нейронов на том же объёме: 69,8% без наказания, до 79% с наказанием).
|
||||||
|
- `cats-dogs/source`: те же изменения (`construct_network.cpp`, `spike_generators.cpp`), пересобран `cats_dogs`; `cats-dogs/run.py --backend delta`.
|
||||||
|
- Запущены: `readout/delta_runs.sh` (MNIST coarse, 30 000, 6 вариантов) и кошки/собаки на плотных признаках `features-v3/kmeans_validation` (2048 входов, 18 000 обучение, скорости 0,3/1/3).
|
||||||
|
|
@ -41,6 +41,10 @@
|
||||||
// KNP_COL_GATE=<g1,g2,..> gate->input pulses: weight g_k with delay k (default: single 10000 pulse with delay 1).
|
// KNP_COL_GATE=<g1,g2,..> gate->input pulses: weight g_k with delay k (default: single 10000 pulse with delay 1).
|
||||||
// KNP_COL_PUNISH=<d> target->input dopamine -d*1000 to every column except the label's one (reward is 0.179376):
|
// KNP_COL_PUNISH=<d> target->input dopamine -d*1000 to every column except the label's one (reward is 0.179376):
|
||||||
// neurons of wrong columns that fired recently lose the synapses that made them fire.
|
// neurons of wrong columns that fired recently lose the synapses that made them fire.
|
||||||
|
// KNP_DELTA=<a>,<b> error-driven (delta-rule) training; needs a backend built with KNP_DELTA_RULE support
|
||||||
|
// (augment/build_delta_cpu.py). The label sends dopamine +a to its neurons on every active
|
||||||
|
// step, every input neuron's own spike sends it dopamine -b on the next step, so dopamine =
|
||||||
|
// target - output; no forcing gate, no label inhibition, no Hebbian/stability changes.
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
float env_float(const char *name, float default_value)
|
float env_float(const char *name, float default_value)
|
||||||
|
|
@ -110,6 +114,13 @@ static NetworkPopulations create_populations(NetworkConstructor &constructor)
|
||||||
input_neuron.stability_change_parameter_ = stability_change_parameter;
|
input_neuron.stability_change_parameter_ = stability_change_parameter;
|
||||||
input_neuron.resource_drain_coefficient_ = resource_drain_coefficient;
|
input_neuron.resource_drain_coefficient_ = resource_drain_coefficient;
|
||||||
input_neuron.synapse_sum_threshold_coefficient_ = synapse_sum_threshold_coefficient;
|
input_neuron.synapse_sum_threshold_coefficient_ = synapse_sum_threshold_coefficient;
|
||||||
|
if (env_float_list("KNP_DELTA", {}).size() == 2)
|
||||||
|
{
|
||||||
|
// Only the dopamine (error) rule changes synapses.
|
||||||
|
input_neuron.d_h_ = 0;
|
||||||
|
input_neuron.stability_change_parameter_ = 0;
|
||||||
|
input_neuron.synapse_sum_threshold_coefficient_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Create input population with specialized parameters for image processing.
|
// Create input population with specialized parameters for image processing.
|
||||||
const auto &input_pop = constructor.add_population(
|
const auto &input_pop = constructor.add_population(
|
||||||
|
|
@ -179,6 +190,14 @@ static void create_projections(
|
||||||
target_to_input_synapse_dopamine.output_type_ = knp::synapse_traits::OutputType::DOPAMINE;
|
target_to_input_synapse_dopamine.output_type_ = knp::synapse_traits::OutputType::DOPAMINE;
|
||||||
target_to_input_synapse_dopamine.weight_ = 0.179376 * 1000;
|
target_to_input_synapse_dopamine.weight_ = 0.179376 * 1000;
|
||||||
target_to_input_synapse_dopamine.delay_ = 3;
|
target_to_input_synapse_dopamine.delay_ = 3;
|
||||||
|
const std::vector<float> delta = env_float_list("KNP_DELTA", {});
|
||||||
|
const bool delta_mode = delta.size() == 2;
|
||||||
|
if (delta_mode)
|
||||||
|
{
|
||||||
|
// Label spikes come on every active step, in step with the image spikes.
|
||||||
|
target_to_input_synapse_dopamine.weight_ = delta[0];
|
||||||
|
target_to_input_synapse_dopamine.delay_ = 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Create dopamine projection connecting target to input population.
|
// Create dopamine projection connecting target to input population.
|
||||||
auto target_to_input_proj_dopamine = constructor.add_projection(
|
auto target_to_input_proj_dopamine = constructor.add_projection(
|
||||||
|
|
@ -216,26 +235,32 @@ static void create_projections(
|
||||||
target_to_input_synapse_excitatory.weight_ = -30 * 1000;
|
target_to_input_synapse_excitatory.weight_ = -30 * 1000;
|
||||||
target_to_input_synapse_excitatory.delay_ = 4;
|
target_to_input_synapse_excitatory.delay_ = 4;
|
||||||
|
|
||||||
// Create excitatory projection connecting target population to input population.
|
// Create excitatory projection connecting target population to input population (not in delta mode).
|
||||||
auto target_to_input_proj_excitatory = constructor.add_projection(
|
if (!delta_mode)
|
||||||
target_to_input_synapse_excitatory, knp::framework::projection::creators::all_to_all<DeltaSynapse>,
|
{
|
||||||
pops.target_pop_, pops.input_pop_, false, false);
|
auto target_to_input_proj_excitatory = constructor.add_projection(
|
||||||
|
target_to_input_synapse_excitatory, knp::framework::projection::creators::all_to_all<DeltaSynapse>,
|
||||||
// Record excitatory projection for label processing.
|
pops.target_pop_, pops.input_pop_, false, false);
|
||||||
network.data_.projections_from_classes_.push_back(target_to_input_proj_excitatory);
|
|
||||||
|
// Record excitatory projection for label processing.
|
||||||
|
network.data_.projections_from_classes_.push_back(target_to_input_proj_excitatory);
|
||||||
|
}
|
||||||
|
|
||||||
// Configure synapse for target to gate projection.
|
// Configure synapse for target to gate projection.
|
||||||
DeltaSynapseParams target_to_gate_synapse;
|
DeltaSynapseParams target_to_gate_synapse;
|
||||||
target_to_gate_synapse.output_type_ = knp::synapse_traits::OutputType::EXCITATORY;
|
target_to_gate_synapse.output_type_ = knp::synapse_traits::OutputType::EXCITATORY;
|
||||||
target_to_gate_synapse.weight_ = 10 * 1000;
|
target_to_gate_synapse.weight_ = 10 * 1000;
|
||||||
|
|
||||||
// Create projection connecting target population to gate population.
|
// Create projection connecting target population to gate population (no forcing in delta mode).
|
||||||
auto target_to_gate_proj = constructor.add_projection(
|
if (!delta_mode)
|
||||||
target_to_gate_synapse, knp::framework::projection::creators::aligned<DeltaSynapse>, pops.target_pop_,
|
{
|
||||||
pops.gate_pop_, false, false);
|
auto target_to_gate_proj = constructor.add_projection(
|
||||||
|
target_to_gate_synapse, knp::framework::projection::creators::aligned<DeltaSynapse>, pops.target_pop_,
|
||||||
// Record gate projection for label processing.
|
pops.gate_pop_, false, false);
|
||||||
network.data_.projections_from_classes_.push_back(target_to_gate_proj);
|
|
||||||
|
// Record gate projection for label processing.
|
||||||
|
network.data_.projections_from_classes_.push_back(target_to_gate_proj);
|
||||||
|
}
|
||||||
|
|
||||||
// Configure synapse for input to output projection.
|
// Configure synapse for input to output projection.
|
||||||
DeltaSynapseParams input_to_output_synapse;
|
DeltaSynapseParams input_to_output_synapse;
|
||||||
|
|
@ -276,6 +301,31 @@ static void create_projections(
|
||||||
pops.input_pop_, false, false);
|
pops.input_pop_, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delta mode: a neuron's own spike sends it negative dopamine on the next step (dopamine = target - output).
|
||||||
|
if (delta_mode)
|
||||||
|
{
|
||||||
|
DeltaSynapseParams self_dopamine;
|
||||||
|
self_dopamine.output_type_ = knp::synapse_traits::OutputType::DOPAMINE;
|
||||||
|
self_dopamine.weight_ = -delta[1];
|
||||||
|
self_dopamine.delay_ = 1;
|
||||||
|
auto self_creator = [self_dopamine](
|
||||||
|
const knp::core::UID &pre_uid, const knp::core::UID &post_uid, size_t pre_size,
|
||||||
|
size_t post_size, auto)
|
||||||
|
{
|
||||||
|
return knp::framework::projection::creators::index_based<DeltaSynapse>(
|
||||||
|
pre_uid, post_uid, pre_size, post_size,
|
||||||
|
[self_dopamine](size_t pre, size_t post) -> std::optional<DeltaSynapseParams>
|
||||||
|
{
|
||||||
|
if (pre != post) return std::nullopt;
|
||||||
|
return self_dopamine;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
auto self_proj =
|
||||||
|
constructor.add_projection(self_dopamine, self_creator, pops.input_pop_, pops.input_pop_, false, false);
|
||||||
|
// Training signal only: not part of the inference network.
|
||||||
|
network.data_.inference_internal_projection_.erase(self_proj);
|
||||||
|
}
|
||||||
|
|
||||||
// Lateral inhibition inside each column.
|
// Lateral inhibition inside each column.
|
||||||
if (const float lateral = env_float("KNP_COL_LATERAL", 0); lateral > 0)
|
if (const float lateral = env_float("KNP_COL_LATERAL", 0); lateral > 0)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// cppcheck-suppress missingInclude
|
// cppcheck-suppress missingInclude
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
#include "global_config.h"
|
#include "global_config.h"
|
||||||
#include "network_functions.h"
|
#include "network_functions.h"
|
||||||
|
|
||||||
|
|
@ -37,7 +39,10 @@ make_training_labels_spikes_generator<knp::neuron_traits::AltAILIF>(const Datase
|
||||||
knp::core::messaging::SpikeData message;
|
knp::core::messaging::SpikeData message;
|
||||||
|
|
||||||
knp::core::Step local_step = step % steps_per_image;
|
knp::core::Step local_step = step % steps_per_image;
|
||||||
if (local_step == 11) message.push_back(dataset.get_data_for_training().first[step / steps_per_image].first);
|
// KNP_DELTA (error-driven training): the label is sent on every active step, in step with the image.
|
||||||
|
static const bool every_step = std::getenv("KNP_DELTA") != nullptr;
|
||||||
|
if (every_step ? local_step < active_steps : local_step == 11)
|
||||||
|
message.push_back(dataset.get_data_for_training().first[step / steps_per_image].first);
|
||||||
return message;
|
return message;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
90
readout/build_delta_cpu.py
Normal file
90
readout/build_delta_cpu.py
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
"""Build an isolated CPU single-threaded backend with an error-driven (delta-rule) dopamine plasticity option.
|
||||||
|
|
||||||
|
KNP_DELTA_RULE=<rate> (environment, read once): for every AltAI STDP neuron with non-zero dopamine D on a step,
|
||||||
|
each trainable synapse whose presynaptic spike arrives on that step changes its synaptic resource by rate * D,
|
||||||
|
whether or not the neuron itself spiked; stability is not used. Without the variable the backend behaves like
|
||||||
|
the stock one. Original sources and libraries are not modified.
|
||||||
|
|
||||||
|
usage: python3 build_delta_cpu.py → /home/int/run/delta-cpu/libknp-cpu-delta-backend.so
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
root = Path('/home/int/run/delta-cpu')
|
||||||
|
root.mkdir(parents=True, exist_ok=True)
|
||||||
|
source_root = Path('/mnt/c/KNP/examples/knp/backends/cpu')
|
||||||
|
shutil.copytree(source_root / 'cpu-library/include', root / 'include', dirs_exist_ok=True)
|
||||||
|
altai = root / 'include/knp/backends/cpu-library/impl/populations/altai/altai_stdp.h'
|
||||||
|
s = altai.read_text()
|
||||||
|
marker = ''' for (size_t neuron_index = 0; neuron_index < population.size(); ++neuron_index)
|
||||||
|
{
|
||||||
|
auto &neuron = population[neuron_index];
|
||||||
|
// Dopamine processing. Dopamine punishment if forced does nothing.'''
|
||||||
|
assert marker in s
|
||||||
|
s = s.replace(marker, ''' // Experimental delta rule (KNP_DELTA_RULE=<rate>), see build_delta_cpu.py.
|
||||||
|
// Step offset between the dopamine step and the presynaptic arrival step (KNP_DELTA_LAG, default 0).
|
||||||
|
static const int64_t DELTA_LAG = []
|
||||||
|
{
|
||||||
|
const char *value = std::getenv("KNP_DELTA_LAG");
|
||||||
|
return value ? std::stol(value) : 0L;
|
||||||
|
}();
|
||||||
|
static const float delta_rate = []
|
||||||
|
{
|
||||||
|
const char *value = std::getenv("KNP_DELTA_RULE");
|
||||||
|
return value ? std::stof(value) : 0.F;
|
||||||
|
}();
|
||||||
|
for (size_t neuron_index = 0; neuron_index < population.size(); ++neuron_index)
|
||||||
|
{
|
||||||
|
auto &neuron = population[neuron_index];
|
||||||
|
if (delta_rate > 0)
|
||||||
|
{
|
||||||
|
if (neuron.dopamine_value_ == 0.0) continue;
|
||||||
|
std::vector<std::reference_wrapper<SynapseParamType>> delta_synapses =
|
||||||
|
training::stdp::get_all_connected_synapses<SynapseType>(working_projections, neuron_index);
|
||||||
|
static const bool debug = std::getenv("KNP_DELTA_DEBUG") != nullptr;
|
||||||
|
static uint64_t calls = 0, hist[7] = {0, 0, 0, 0, 0, 0, 0};
|
||||||
|
for (auto &synapse : delta_synapses)
|
||||||
|
{
|
||||||
|
const int64_t lag = static_cast<int64_t>(step) -
|
||||||
|
static_cast<int64_t>(synapse.get().rule_.last_spike_step_ + synapse.get().delay_ - 1);
|
||||||
|
if (debug && lag >= -3 && lag <= 3) ++hist[lag + 3];
|
||||||
|
// The presynaptic spike reaches the neuron on this step.
|
||||||
|
if (lag == DELTA_LAG)
|
||||||
|
synapse.get().rule_.synaptic_resource_ += delta_rate * neuron.dopamine_value_;
|
||||||
|
}
|
||||||
|
if (debug && ++calls % 500 == 0)
|
||||||
|
std::fprintf(stderr, "KNP_DELTA_DEBUG calls=%lu lag-3..3: %lu %lu %lu %lu %lu %lu %lu\\n",
|
||||||
|
static_cast<unsigned long>(calls), hist[0], hist[1], hist[2], hist[3], hist[4], hist[5], hist[6]);
|
||||||
|
training::stdp::recalculate_synapse_weights(delta_synapses);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Dopamine processing. Dopamine punishment if forced does nothing.''')
|
||||||
|
if '#include <cstdlib>' not in s:
|
||||||
|
s = s.replace('#include <algorithm>', '#include <algorithm>\n#include <cstdio>\n#include <cstdlib>\n#include <string>', 1)
|
||||||
|
altai.write_text(s)
|
||||||
|
build = Path('/home/int/knp-build/knp/backends/cpu/cpu-single-threaded-backend')
|
||||||
|
folder = build / 'CMakeFiles/knp-cpu-single-threaded-backend.dir'
|
||||||
|
flags = {}
|
||||||
|
for line in (folder / 'flags.make').read_text().splitlines():
|
||||||
|
if ' = ' in line:
|
||||||
|
key, value = line.split(' = ', 1)
|
||||||
|
flags[key] = shlex.split(value)
|
||||||
|
obj = root / 'backend.o'
|
||||||
|
subprocess.run(['/usr/bin/c++', *flags['CXX_DEFINES'], '-I' + str(root / 'include'),
|
||||||
|
*flags['CXX_INCLUDES'], *flags['CXX_FLAGS'], '-c',
|
||||||
|
str(source_root / 'cpu-single-threaded-backend/impl/backend.cpp'), '-o', str(obj)], check=True)
|
||||||
|
link = shlex.split((folder / 'link.txt').read_text())
|
||||||
|
rebuilt = []
|
||||||
|
for item in link:
|
||||||
|
if item.startswith('-Wl,--dependency-file='):
|
||||||
|
continue
|
||||||
|
if item.endswith('/impl/backend.cpp.o'):
|
||||||
|
item = str(obj)
|
||||||
|
elif item.startswith('-Wl,-soname,'):
|
||||||
|
item = '-Wl,-soname,libknp-cpu-delta-backend.so'
|
||||||
|
rebuilt.append(item)
|
||||||
|
rebuilt[rebuilt.index('-o') + 1] = str(root / 'libknp-cpu-delta-backend.so')
|
||||||
|
subprocess.run(rebuilt, cwd=build, check=True)
|
||||||
|
print(root / 'libknp-cpu-delta-backend.so', flush=True)
|
||||||
20
readout/delta_runs.sh
Normal file
20
readout/delta_runs.sh
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Error-driven (delta-rule) readout in KNP on coarse stroke features, 30k train, validation.
|
||||||
|
# spec: name rate a b jitter
|
||||||
|
cd ~/run/exp
|
||||||
|
export LD_LIBRARY_PATH=$HOME/knp-build/bin:$HOME/deps/lib
|
||||||
|
run() { n=$1; [ -f $n/done ] && return; mkdir -p $n
|
||||||
|
echo | env KNP_COL_SIZE=1 KNP_COL_JITTER=$5 KNP_MNIST_THR=60 KNP_MNIST_ADD_THR_SCALE=0 KNP_MNIST_INFER_INHIB=255 \
|
||||||
|
KNP_DELTA=$3,$4 KNP_DELTA_RULE=$2 KNP_DELTA_LAG=1 KNP_MNIST_DUMP=$HOME/run/exp/$n \
|
||||||
|
$HOME/knp-build/bin/mnist_learn_example -m altai --images ${DATA:-fcoarse_val}30000.bin --labels ${DATA:-fcoarse_val}30000.target \
|
||||||
|
-t 30000 -i 10000 -l error --training_backend $HOME/run/delta-cpu/libknp-cpu-delta-backend.so > $n/log.txt 2>&1
|
||||||
|
touch $n/done; }
|
||||||
|
export -f run
|
||||||
|
cat <<SPECS | xargs -P 6 -L 1 bash -c 'run "$@"' _
|
||||||
|
dl_r1_a1_b1_j20 1 1 1 20
|
||||||
|
dl_r3_a1_b1_j20 3 1 1 20
|
||||||
|
dl_r10_a1_b1_j20 10 1 1 20
|
||||||
|
dl_r3_a1_b0.5_j20 3 1 0.5 20
|
||||||
|
dl_r3_a1_b2_j20 3 1 2 20
|
||||||
|
dl_r3_a1_b1_j0 3 1 1 0
|
||||||
|
SPECS
|
||||||
Loading…
Add table
Reference in a new issue