"""Build an isolated CPU single-threaded backend with an error-driven (delta-rule) dopamine plasticity option. KNP_DELTA_RULE= (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= 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=), 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=, 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> delta_synapses = training::stdp::get_all_connected_synapses(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(step) - static_cast(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(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 ' not in s: s = s.replace('#include ', '#include \n#include \n#include \n#include ', 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)