100 lines
5.2 KiB
Python
100 lines
5.2 KiB
Python
"""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 arrived KNP_DELTA_LAG steps earlier changes its synaptic resource by
|
|
rate * D, whether or not the neuron itself spiked; stability is not used. KNP_DELTA_DECAY=<d> additionally changes
|
|
every other synapse of that neuron by -rate * D * d (competitive learning). 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;
|
|
}();
|
|
// Competitive (winner-take-all) learning: with KNP_DELTA_DECAY=<d>, synapses whose input did not arrive change by
|
|
// -rate * D * d, so a rewarded neuron moves its weights towards the current input pattern.
|
|
static const float delta_decay = []
|
|
{
|
|
const char *value = std::getenv("KNP_DELTA_DECAY");
|
|
return value ? std::stof(value) : 0.F;
|
|
}();
|
|
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_;
|
|
else if (delta_decay > 0)
|
|
synapse.get().rule_.synaptic_resource_ -= delta_rate * neuron.dopamine_value_ * delta_decay;
|
|
}
|
|
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)
|