ON/OFF input coding for mnist-learn: KNP_MNIST_INPUT_SIZE, dataset builder, simulator --pix
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
979aa90bb4
commit
db4b1d08b8
6 changed files with 90 additions and 4 deletions
|
|
@ -42,7 +42,7 @@ Dataset process_dataset(ModelDescription const& model_desc)
|
|||
// Process images and labels into spike representations.
|
||||
dataset.process_labels_and_images(
|
||||
images_stream, labels_stream, model_desc.train_images_amount_ + model_desc.inference_images_amount_,
|
||||
classes_amount, input_size, steps_per_image,
|
||||
classes_amount, get_input_size(), steps_per_image,
|
||||
dataset.make_incrementing_image_to_spikes_converter(active_steps, state_increment_factor));
|
||||
|
||||
// Split dataset into training and inference portions.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -36,7 +38,22 @@ constexpr size_t classes_amount = 10;
|
|||
*
|
||||
* @details MNIST images are 28×28 pixels, resulting in 784 total pixels per image.
|
||||
*/
|
||||
constexpr size_t input_size = 28 * 28;
|
||||
constexpr size_t mnist_input_size = 28 * 28;
|
||||
|
||||
/**
|
||||
* @brief Actual input size: `KNP_MNIST_INPUT_SIZE` environment variable or 784.
|
||||
*
|
||||
* @details ON/OFF coding stores two channels per pixel, so the dataset has 2 × 784 = 1568 bytes per image.
|
||||
*/
|
||||
inline size_t get_input_size()
|
||||
{
|
||||
static const size_t size = []
|
||||
{
|
||||
const char *value = std::getenv("KNP_MNIST_INPUT_SIZE");
|
||||
return value ? static_cast<size_t>(std::stoul(value)) : mnist_input_size;
|
||||
}();
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Total number of simulation steps per input image.
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ static NetworkPopulations create_populations(NetworkConstructor &constructor)
|
|||
constructor.add_population(default_neuron, classes_amount, PopulationRole::NORMAL, false, "GATE");
|
||||
|
||||
// Create raster population for channelized image processing (28x28 = 784 neurons).
|
||||
const auto &raster_pop = constructor.add_channeled_population(input_size, true);
|
||||
const auto &raster_pop = constructor.add_channeled_population(get_input_size(), true);
|
||||
|
||||
// Create target population for label processing (10 neurons for 10 digit classes).
|
||||
const auto &target_pop = constructor.add_channeled_population(classes_amount, false);
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ static NetworkPopulations create_populations(NetworkConstructor &constructor)
|
|||
constructor.add_population(default_neuron, classes_amount, PopulationRole::NORMAL, false, "GATE");
|
||||
|
||||
// Create raster population for channelized image processing (28x28 = 784 neurons).
|
||||
const auto &raster_pop = constructor.add_channeled_population(input_size, true);
|
||||
const auto &raster_pop = constructor.add_channeled_population(get_input_size(), true);
|
||||
|
||||
// Create target population for label processing (10 neurons for 10 digit classes).
|
||||
const auto &target_pop = constructor.add_channeled_population(classes_amount, false);
|
||||
|
|
|
|||
65
onoff/prepare_onoff.py
Normal file
65
onoff/prepare_onoff.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
"""Build ON/OFF-coded MNIST datasets for mnist_learn_example (KNP_MNIST_INPUT_SIZE=1568).
|
||||
|
||||
Each image becomes 1568 bytes: 784 ON pixels (unchanged) followed by 784 OFF pixels.
|
||||
OFF modes:
|
||||
full - 255 - p everywhere;
|
||||
half - (255 - p) / 2 everywhere;
|
||||
local - 255 - p only within `--radius` pixels of the digit (p > 0), zero elsewhere.
|
||||
|
||||
usage: python prepare_onoff.py <src.bin> <src.target> <out_prefix> --mode local [--radius 2]
|
||||
Writes <out_prefix>.bin and <out_prefix>.target (labels copied as is).
|
||||
"""
|
||||
import argparse
|
||||
import shutil
|
||||
|
||||
import numpy as np
|
||||
|
||||
SIDE = 28
|
||||
|
||||
|
||||
def dilate(mask, radius):
|
||||
out = mask.copy()
|
||||
for dy in range(-radius, radius + 1):
|
||||
for dx in range(-radius, radius + 1):
|
||||
if dy * dy + dx * dx > radius * radius:
|
||||
continue
|
||||
shifted = np.zeros_like(mask)
|
||||
ys, yd = (slice(dy, None), slice(None, -dy or None)) if dy >= 0 else (slice(None, dy), slice(-dy, None))
|
||||
xs, xd = (slice(dx, None), slice(None, -dx or None)) if dx >= 0 else (slice(None, dx), slice(-dx, None))
|
||||
shifted[:, ys, xs] = mask[:, yd, xd]
|
||||
out |= shifted
|
||||
return out
|
||||
|
||||
|
||||
def off_channel(imgs, mode, radius):
|
||||
inv = 255 - imgs.astype(np.int32)
|
||||
if mode == "full":
|
||||
off = inv
|
||||
elif mode == "half":
|
||||
off = inv // 2
|
||||
elif mode == "local":
|
||||
off = inv * dilate(imgs > 0, radius)
|
||||
else:
|
||||
raise ValueError(mode)
|
||||
return off.astype(np.uint8)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("images")
|
||||
ap.add_argument("labels")
|
||||
ap.add_argument("out")
|
||||
ap.add_argument("--mode", choices=["full", "half", "local"], required=True)
|
||||
ap.add_argument("--radius", type=int, default=2)
|
||||
a = ap.parse_args()
|
||||
imgs = np.fromfile(a.images, dtype=np.uint8).reshape(-1, SIDE, SIDE)
|
||||
off = off_channel(imgs, a.mode, a.radius)
|
||||
out = np.concatenate([imgs.reshape(len(imgs), -1), off.reshape(len(imgs), -1)], axis=1)
|
||||
out.tofile(a.out + ".bin")
|
||||
shutil.copyfile(a.labels, a.out + ".target")
|
||||
on_spk, off_spk = imgs.mean() / 255 * 10 * SIDE * SIDE, off.mean() / 255 * 10 * SIDE * SIDE
|
||||
print(f"{a.out}: {len(out)} images, ~{on_spk:.0f} ON / ~{off_spk:.0f} OFF spikes per image")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -4,6 +4,7 @@ Reproduces KNP inference: raster -> INPUT (delay 1), optional INPUT -> INPUT inh
|
|||
INPUT -> OUTPUT column vote (delay 1), 15 steps per image, potentials carried across images.
|
||||
|
||||
usage: python3 altai_sim.py <dump_dir> <images.bin> <labels.target> [--inhib 255] [--thr 40,60,80] [--first N] [--test 10000]
|
||||
[--pix 1568] (ON/OFF input, KNP_MNIST_INPUT_SIZE)
|
||||
"""
|
||||
import argparse
|
||||
import csv
|
||||
|
|
@ -91,7 +92,10 @@ def main():
|
|||
ap.add_argument("--first", type=int, default=None, help="index of first test image (default: last --test)")
|
||||
ap.add_argument("--test", type=int, default=10000)
|
||||
ap.add_argument("--thr", default="", help="comma-separated thresholds to sweep instead of the dumped ones")
|
||||
ap.add_argument("--pix", type=int, default=784, help="input size per image (1568 for ON/OFF)")
|
||||
a = ap.parse_args()
|
||||
global PIX
|
||||
PIX = a.pix
|
||||
w, thr = load_dump(a.dump)
|
||||
total = len(open(a.labels).read().split())
|
||||
first = total - a.test if a.first is None else a.first
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue