spikeforge 0.3.1__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {spikeforge-0.3.1 → spikeforge-0.3.2}/PKG-INFO +2 -1
- {spikeforge-0.3.1 → spikeforge-0.3.2}/README.md +1 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/pyproject.toml +1 -1
- spikeforge-0.3.2/spikeforge/memory/embedding_readout.py +51 -0
- spikeforge-0.3.2/spikeforge/memory/episode_sampler.py +63 -0
- spikeforge-0.3.2/spikeforge/memory/episodic_trainer.py +77 -0
- spikeforge-0.3.2/spikeforge/memory/few_shot_generalization_poc.py +148 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/memory/held_out_digit_poc.py +22 -6
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/memory/one_shot_associative_memory.py +16 -1
- spikeforge-0.3.2/spikeforge/memory/prototypical.py +48 -0
- spikeforge-0.3.2/spikeforge/rrt/__init__.py +10 -0
- spikeforge-0.3.2/spikeforge/rrt/context_model.py +98 -0
- spikeforge-0.3.2/spikeforge/rrt/falsification_poc.py +110 -0
- spikeforge-0.3.2/spikeforge/rrt/protocol.py +130 -0
- spikeforge-0.3.2/spikeforge/rrt/relation.py +50 -0
- spikeforge-0.3.2/spikeforge/rrt/trial_features.py +66 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge.egg-info/PKG-INFO +2 -1
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge.egg-info/SOURCES.txt +11 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/LICENSE +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/main.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/main_encodings.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/setup.cfg +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/__main__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/cli.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/compare.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/config.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/energy.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/harness.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/memory.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/serving.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/store.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/suite.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/timing.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/cli/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/cli/backend_cli.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/cli/extract_cli.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/cli/fixture.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/cli/onnx_cli.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/cli/records_cli.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/cli/verify.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/compression/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/compression/codec.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/compression/errors.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/compression/pruning.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/compression/report.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/config.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/data_loader.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/dataset_spec.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/datasets.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/download_cli.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/event_errors.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/event_geometry.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/event_loader.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/image_size.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/sample_source.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/sequence_source.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/encoding/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/encoding/delta_trainer.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/encoding/latency_trainer.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/encoding/random_spikegen.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/encoding/spike_encoder.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/events/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/events/dense.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/events/event_bridge.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/events/event_sample.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/events/event_source.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/events/synthetic.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/events/tonic_api.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/delta_exporter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/exporter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/latency_curve_exporter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/latency_raster_exporter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/latency_video_exporter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/plot_utils.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/presentation_exporter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/random_spike_raster_exporter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/random_spike_video_exporter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/raster_exporter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/reconstruction_exporter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/spike_gif_exporter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/video_exporter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/comparison.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/decoding.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/encoding.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/firing_rate.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/histogram.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/isi.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/metrics.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/sparsity.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/surrogate.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/memory/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/memory/frozen_classifier.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/memory/hebbian_synapse.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/memory/lockstep_runner.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/memory/stdp_synapse.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/memory/stdp_window_poc.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/network/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/network/hidden_frames.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/network/inference.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/network/model_diff.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/network/model_search.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/network/model_store.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/network/spiking_net.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/alpha.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/contract.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/handler.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/lapicque.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/leaky.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/recurrent.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/registry.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/spike_grad.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/synaptic.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/api.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/array_codec.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/drift.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/errors.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/exporter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/extract.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/ingest.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/interpreter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/interpreter_result.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/jsonable.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/mapped_node.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/mapper.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/neuron_kwargs.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/neuron_nodes.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/node_builders.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/node_names.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/ops_common.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/ops_linear.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/ops_neuron.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/ops_registry.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/planner.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/require.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/roundtrip.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/serialization.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/stage_builders.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/stage_mapping.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/stages_unmappable.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/tolerances.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/torch_map.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/validation_report.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/validator.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/json_formatter.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/logging_setup.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/metrics.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/persistence.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/prometheus.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/registry.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/snapshot.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/store.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/timer.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/api.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/errors.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/export.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/import_onnx.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/metadata.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/roundtrip.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/step_module.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/py.typed +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/runtime/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/runtime/device.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/runtime/execution_mode.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/runtime/system_stats.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/bundle.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/bundle_manifest.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/encode_spec.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/errors.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/prediction.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/preprocess.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/session.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/state_tree.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/step.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/tensor_codec.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/compiled_step.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/execution.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/frames.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/grad_policy.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/input_shape.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/module_spec.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/parallel_runner.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/production.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/production_result.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/runner.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/state.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/step_stages.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/trajectory.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/streaming/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/streaming/encoding.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/streaming/recipe.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/streaming/serving.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/streaming/stream_source.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/streaming/window_spec.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/attention.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/builder.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/edge.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/kinds.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/multihead_attention.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/ordering.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/positional_encoding.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/presets.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/registry.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/sequence_presets.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/sequence_stages.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/spec.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/stage.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/stage_module.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/stage_modules.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/sum_pool.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/validation.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/config_hash.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/determinism.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/manifest.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/seed.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/sink.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/sink_probe.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/sink_records.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/sinks.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/tensorboard_sink.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/versions.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/wandb_sink.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/__init__.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/amp_controller.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/checkpoint_mixin.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/encoding_mixin.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/eval_mixin.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/event_batches.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/event_engine.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/logger.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/multi_device.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/scaleup_mixin.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/topology_mixin.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/trainer.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/training_engine.py +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge.egg-info/dependency_links.txt +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge.egg-info/entry_points.txt +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge.egg-info/not-zip-safe +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge.egg-info/requires.txt +0 -0
- {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spikeforge
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Spiking neural network trainer/experiments for MNIST built with snnTorch and PyTorch.
|
|
5
5
|
Author-email: Capsize LLC <contact@capsizegames.com>
|
|
6
6
|
Maintainer-email: Capsize LLC <contact@capsizegames.com>
|
|
@@ -56,6 +56,7 @@ Dynamic: license-file
|
|
|
56
56
|
# spikeforge
|
|
57
57
|
|
|
58
58
|
[](https://github.com/capsize-games/spikeforge/actions/workflows/ci.yml)
|
|
59
|
+
[](https://capsizegames.com/discord)
|
|
59
60
|
[](OPEN_SOURCE_CHECKLIST.md)
|
|
60
61
|
[](LICENSE)
|
|
61
62
|
[](https://www.python.org/downloads/)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# spikeforge
|
|
2
2
|
|
|
3
3
|
[](https://github.com/capsize-games/spikeforge/actions/workflows/ci.yml)
|
|
4
|
+
[](https://capsizegames.com/discord)
|
|
4
5
|
[](OPEN_SOURCE_CHECKLIST.md)
|
|
5
6
|
[](LICENSE)
|
|
6
7
|
[](https://www.python.org/downloads/)
|
|
@@ -15,7 +15,7 @@ build-backend = "setuptools.build_meta"
|
|
|
15
15
|
|
|
16
16
|
[project]
|
|
17
17
|
name = "spikeforge"
|
|
18
|
-
version = "0.3.
|
|
18
|
+
version = "0.3.2"
|
|
19
19
|
description = "Spiking neural network trainer/experiments for MNIST built with snnTorch and PyTorch."
|
|
20
20
|
# ``README.md`` and ``LICENSE`` are committed symlinks to the repository-root
|
|
21
21
|
# files: the distribution metadata points at the root documents while
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Bridge a spiking embedder's output spikes into memory-module calls.
|
|
2
|
+
|
|
3
|
+
Unlike :mod:`spikeforge.memory.lockstep_runner` (issue #22, a frozen
|
|
4
|
+
*classifier* with a hidden layer feeding memory), an episodic embedder
|
|
5
|
+
(issue #24) has no separate classification head: its own output stage
|
|
6
|
+
*is* the spike pattern memory reads.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Tuple
|
|
10
|
+
|
|
11
|
+
import torch
|
|
12
|
+
|
|
13
|
+
from spikeforge.memory.one_shot_associative_memory import (
|
|
14
|
+
OneShotAssociativeMemory,
|
|
15
|
+
)
|
|
16
|
+
from spikeforge.simulator.module_spec import spec_of
|
|
17
|
+
from spikeforge.simulator.runner import run
|
|
18
|
+
from spikeforge.simulator.trajectory import Trajectory
|
|
19
|
+
from spikeforge.topology.stage_module import StageModule
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def output_spike_trace(
|
|
23
|
+
net: StageModule, spikes: torch.Tensor,
|
|
24
|
+
) -> Tuple[torch.Tensor, Trajectory]:
|
|
25
|
+
"""Return ``([T, B, D]`` output spikes, full trajectory)`` for ``net``."""
|
|
26
|
+
trajectory = run(net, spikes, track=True)
|
|
27
|
+
return trajectory.spikes[spec_of(net).output], trajectory
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def teach_from_example(
|
|
31
|
+
memory: OneShotAssociativeMemory,
|
|
32
|
+
net: StageModule,
|
|
33
|
+
spikes: torch.Tensor,
|
|
34
|
+
) -> int:
|
|
35
|
+
"""One-shot teach ``memory`` from one example's encoded ``spikes``."""
|
|
36
|
+
trace, _ = output_spike_trace(net, spikes)
|
|
37
|
+
return memory.teach(trace[:, 0, :])
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def memory_predictions(
|
|
41
|
+
memory: OneShotAssociativeMemory,
|
|
42
|
+
net: StageModule,
|
|
43
|
+
spikes: torch.Tensor,
|
|
44
|
+
) -> torch.Tensor:
|
|
45
|
+
"""Return ``[B]`` predicted class indices from memory alone."""
|
|
46
|
+
trace, _ = output_spike_trace(net, spikes)
|
|
47
|
+
memory.reset_state(trace.size(1))
|
|
48
|
+
counts = torch.zeros(trace.size(1), memory.num_classes)
|
|
49
|
+
for step in trace:
|
|
50
|
+
counts += memory.step(step)
|
|
51
|
+
return counts.argmax(dim=1)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""N-way K-shot episode sampling for episodic metric-learning (issue #24)."""
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
from typing import Dict, List, Sequence, Tuple
|
|
5
|
+
|
|
6
|
+
import torch
|
|
7
|
+
from torch.utils.data import Dataset
|
|
8
|
+
|
|
9
|
+
#: One sampled episode: support images/labels, then query images/labels.
|
|
10
|
+
Episode = Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def class_indices(
|
|
14
|
+
dataset: Dataset, classes: Sequence[int],
|
|
15
|
+
) -> Dict[int, List[int]]:
|
|
16
|
+
"""Return each of ``classes``'s sample indices within ``dataset``."""
|
|
17
|
+
targets = dataset.targets
|
|
18
|
+
return {
|
|
19
|
+
cls: torch.nonzero(targets == cls).squeeze(1).tolist()
|
|
20
|
+
for cls in classes
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def sample_episode(
|
|
25
|
+
dataset: Dataset,
|
|
26
|
+
by_class: Dict[int, List[int]],
|
|
27
|
+
n_way: int,
|
|
28
|
+
k_shot: int,
|
|
29
|
+
n_query: int,
|
|
30
|
+
rng: random.Random,
|
|
31
|
+
) -> Episode:
|
|
32
|
+
"""Sample one episode: ``n_way`` classes, ``k_shot`` + ``n_query`` each.
|
|
33
|
+
|
|
34
|
+
Labels are episode-local (``0..n_way-1``), independent of the
|
|
35
|
+
dataset's real class ids -- the point of episodic training is a
|
|
36
|
+
task-agnostic "these match / these don't" signal, not a fixed
|
|
37
|
+
N-way classifier.
|
|
38
|
+
"""
|
|
39
|
+
chosen = rng.sample(list(by_class), n_way)
|
|
40
|
+
support: Tuple[List[torch.Tensor], List[int]] = ([], [])
|
|
41
|
+
query: Tuple[List[torch.Tensor], List[int]] = ([], [])
|
|
42
|
+
for label, cls in enumerate(chosen):
|
|
43
|
+
indices = rng.sample(by_class[cls], k_shot + n_query)
|
|
44
|
+
_fill(dataset, indices[:k_shot], label, support)
|
|
45
|
+
_fill(dataset, indices[k_shot:], label, query)
|
|
46
|
+
return (
|
|
47
|
+
torch.stack(support[0]), torch.tensor(support[1]),
|
|
48
|
+
torch.stack(query[0]), torch.tensor(query[1]),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _fill(
|
|
53
|
+
dataset: Dataset,
|
|
54
|
+
indices: List[int],
|
|
55
|
+
label: int,
|
|
56
|
+
accumulator: Tuple[List[torch.Tensor], List[int]],
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Append ``dataset``'s images at ``indices`` under one episode label."""
|
|
59
|
+
images, labels = accumulator
|
|
60
|
+
for index in indices:
|
|
61
|
+
image, _ = dataset[index]
|
|
62
|
+
images.append(image)
|
|
63
|
+
labels.append(label)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Episodic (metric-learning) training loop for a spiking embedder."""
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
from typing import Dict, List, Sequence
|
|
5
|
+
|
|
6
|
+
import torch
|
|
7
|
+
from torch.utils.data import Dataset
|
|
8
|
+
|
|
9
|
+
from spikeforge.encoding.spike_encoder import SpikeEncoder
|
|
10
|
+
from spikeforge.memory.episode_sampler import class_indices, sample_episode
|
|
11
|
+
from spikeforge.memory.prototypical import embed, prototypical_loss
|
|
12
|
+
from spikeforge.topology.registry import build_topology
|
|
13
|
+
from spikeforge.topology.stage_module import StageModule
|
|
14
|
+
|
|
15
|
+
#: Learning rate for the embedder's Adam optimizer.
|
|
16
|
+
LEARNING_RATE = 1e-3
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def build_episodic_embedder(
|
|
20
|
+
dataset: Dataset,
|
|
21
|
+
classes: Sequence[int],
|
|
22
|
+
encoder: SpikeEncoder,
|
|
23
|
+
embed_dim: int,
|
|
24
|
+
hidden: int,
|
|
25
|
+
episodes: int,
|
|
26
|
+
n_way: int,
|
|
27
|
+
k_shot: int,
|
|
28
|
+
n_query: int,
|
|
29
|
+
seed: int = 0,
|
|
30
|
+
) -> StageModule:
|
|
31
|
+
"""Train ``fc_small`` as an embedder via prototypical-network episodes.
|
|
32
|
+
|
|
33
|
+
Unlike ``fc_small``'s ordinary use as a fixed-class classifier,
|
|
34
|
+
the final layer here carries no softmax semantics: episodic
|
|
35
|
+
training only ever asks it to place same-class spike patterns
|
|
36
|
+
close together and different classes apart, which is what should
|
|
37
|
+
let it generalise -- to classes held out of every episode, and in
|
|
38
|
+
the fuller claim this issue tests, to a script never trained on
|
|
39
|
+
at all (see ``few_shot_generalization_poc.py``).
|
|
40
|
+
"""
|
|
41
|
+
_, net = build_topology(
|
|
42
|
+
"fc_small", {"hidden": hidden, "num_classes": embed_dim},
|
|
43
|
+
)
|
|
44
|
+
optimizer = torch.optim.Adam(net.parameters(), lr=LEARNING_RATE)
|
|
45
|
+
rng = random.Random(seed)
|
|
46
|
+
by_class = class_indices(dataset, classes)
|
|
47
|
+
for _ in range(episodes):
|
|
48
|
+
_train_one_episode(
|
|
49
|
+
net, optimizer, dataset, by_class, encoder,
|
|
50
|
+
n_way, k_shot, n_query, rng,
|
|
51
|
+
)
|
|
52
|
+
return net
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _train_one_episode(
|
|
56
|
+
net: StageModule,
|
|
57
|
+
optimizer: torch.optim.Optimizer,
|
|
58
|
+
dataset: Dataset,
|
|
59
|
+
by_class: Dict[int, List[int]],
|
|
60
|
+
encoder: SpikeEncoder,
|
|
61
|
+
n_way: int,
|
|
62
|
+
k_shot: int,
|
|
63
|
+
n_query: int,
|
|
64
|
+
rng: random.Random,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Sample one episode and take one prototypical-loss gradient step."""
|
|
67
|
+
support_x, support_y, query_x, query_y = sample_episode(
|
|
68
|
+
dataset, by_class, n_way, k_shot, n_query, rng,
|
|
69
|
+
)
|
|
70
|
+
support_emb = embed(net, encoder, support_x)
|
|
71
|
+
query_emb = embed(net, encoder, query_x)
|
|
72
|
+
loss, _ = prototypical_loss(
|
|
73
|
+
support_emb, support_y, query_emb, query_y, n_way,
|
|
74
|
+
)
|
|
75
|
+
optimizer.zero_grad()
|
|
76
|
+
loss.backward()
|
|
77
|
+
optimizer.step()
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Few-shot generalization to never-trained characters (issue #24).
|
|
2
|
+
|
|
3
|
+
Trains a spiking embedder episodically on EMNIST letters (English
|
|
4
|
+
script), freezes it, then tests whether it supports one-shot
|
|
5
|
+
discrimination among KMNIST (Japanese Kuzushiji) characters -- a
|
|
6
|
+
script the embedder never saw in any form during training. The
|
|
7
|
+
readout is #22's unchanged
|
|
8
|
+
:class:`~spikeforge.memory.one_shot_associative_memory.\
|
|
9
|
+
OneShotAssociativeMemory`: one memory neuron per class in the
|
|
10
|
+
evaluation episode, taught from a single support example and scored
|
|
11
|
+
on held-out queries.
|
|
12
|
+
|
|
13
|
+
This is a genuinely harder and uncertain claim than issue #22: no
|
|
14
|
+
amount of clever memory retrieval helps distinguish two classes the
|
|
15
|
+
embedder was never shown anything resembling; only a spike-pattern
|
|
16
|
+
embedding that generalises across scripts can.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import random
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from typing import Dict, List
|
|
22
|
+
|
|
23
|
+
import torch
|
|
24
|
+
from torch.utils.data import Dataset
|
|
25
|
+
|
|
26
|
+
from spikeforge.data.datasets import build_dataset
|
|
27
|
+
from spikeforge.encoding.spike_encoder import SpikeEncoder
|
|
28
|
+
from spikeforge.memory.embedding_readout import (
|
|
29
|
+
memory_predictions,
|
|
30
|
+
teach_from_example,
|
|
31
|
+
)
|
|
32
|
+
from spikeforge.memory.episode_sampler import class_indices, sample_episode
|
|
33
|
+
from spikeforge.memory.episodic_trainer import build_episodic_embedder
|
|
34
|
+
from spikeforge.memory.one_shot_associative_memory import (
|
|
35
|
+
OneShotAssociativeMemory,
|
|
36
|
+
)
|
|
37
|
+
from spikeforge.topology.stage_module import StageModule
|
|
38
|
+
|
|
39
|
+
#: The script the embedder is trained on (English letters, 26 classes).
|
|
40
|
+
#: torchvision's EMNIST "letters" split labels classes 1-26, not 0-25.
|
|
41
|
+
TRAIN_DATASET = "emnist_letters"
|
|
42
|
+
TRAIN_CLASSES = tuple(range(1, 27))
|
|
43
|
+
#: The script it is evaluated on -- never seen during training.
|
|
44
|
+
EVAL_DATASET = "kmnist"
|
|
45
|
+
EVAL_CLASSES = tuple(range(10))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class FewShotResult:
|
|
50
|
+
"""Mean one-shot discrimination accuracy over held-out episodes."""
|
|
51
|
+
|
|
52
|
+
accuracy: float
|
|
53
|
+
chance: float
|
|
54
|
+
episodes: int
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def run_few_shot_generalization_poc(
|
|
58
|
+
seed: int = 0,
|
|
59
|
+
hidden: int = 64,
|
|
60
|
+
embed_dim: int = 32,
|
|
61
|
+
num_steps: int = 15,
|
|
62
|
+
train_episodes: int = 300,
|
|
63
|
+
train_n_way: int = 5,
|
|
64
|
+
train_k_shot: int = 5,
|
|
65
|
+
train_n_query: int = 5,
|
|
66
|
+
eval_episodes: int = 50,
|
|
67
|
+
eval_n_way: int = 5,
|
|
68
|
+
eval_n_query: int = 5,
|
|
69
|
+
) -> FewShotResult:
|
|
70
|
+
"""Train on EMNIST letters, evaluate one-shot recall on KMNIST."""
|
|
71
|
+
torch.manual_seed(seed)
|
|
72
|
+
encoder = SpikeEncoder(coding="rate", num_steps=num_steps)
|
|
73
|
+
net = _train_embedder(
|
|
74
|
+
encoder, hidden, embed_dim, train_episodes,
|
|
75
|
+
train_n_way, train_k_shot, train_n_query, seed,
|
|
76
|
+
)
|
|
77
|
+
accuracy = _evaluate_cross_script(
|
|
78
|
+
net, encoder, embed_dim, eval_episodes, eval_n_way,
|
|
79
|
+
eval_n_query, seed,
|
|
80
|
+
)
|
|
81
|
+
return FewShotResult(accuracy, 1.0 / eval_n_way, eval_episodes)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _train_embedder(
|
|
85
|
+
encoder: SpikeEncoder,
|
|
86
|
+
hidden: int,
|
|
87
|
+
embed_dim: int,
|
|
88
|
+
episodes: int,
|
|
89
|
+
n_way: int,
|
|
90
|
+
k_shot: int,
|
|
91
|
+
n_query: int,
|
|
92
|
+
seed: int,
|
|
93
|
+
) -> StageModule:
|
|
94
|
+
"""Episodically train and freeze the embedder on EMNIST letters."""
|
|
95
|
+
train_data = build_dataset(TRAIN_DATASET, train=True)
|
|
96
|
+
net = build_episodic_embedder(
|
|
97
|
+
train_data, TRAIN_CLASSES, encoder, embed_dim, hidden,
|
|
98
|
+
episodes, n_way, k_shot, n_query, seed,
|
|
99
|
+
)
|
|
100
|
+
net.requires_grad_(False)
|
|
101
|
+
net.eval()
|
|
102
|
+
return net
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _evaluate_cross_script(
|
|
106
|
+
net: StageModule,
|
|
107
|
+
encoder: SpikeEncoder,
|
|
108
|
+
embed_dim: int,
|
|
109
|
+
eval_episodes: int,
|
|
110
|
+
n_way: int,
|
|
111
|
+
n_query: int,
|
|
112
|
+
seed: int,
|
|
113
|
+
) -> float:
|
|
114
|
+
"""Average one-shot discrimination accuracy over held-out episodes."""
|
|
115
|
+
eval_data = build_dataset(EVAL_DATASET, train=False)
|
|
116
|
+
by_class = class_indices(eval_data, EVAL_CLASSES)
|
|
117
|
+
rng = random.Random(seed + 1)
|
|
118
|
+
scores = [
|
|
119
|
+
_run_one_episode(
|
|
120
|
+
net, encoder, eval_data, by_class, embed_dim, n_way,
|
|
121
|
+
n_query, rng,
|
|
122
|
+
)
|
|
123
|
+
for _ in range(eval_episodes)
|
|
124
|
+
]
|
|
125
|
+
return sum(scores) / len(scores)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _run_one_episode(
|
|
129
|
+
net: StageModule,
|
|
130
|
+
encoder: SpikeEncoder,
|
|
131
|
+
eval_data: Dataset,
|
|
132
|
+
by_class: Dict[int, List[int]],
|
|
133
|
+
embed_dim: int,
|
|
134
|
+
n_way: int,
|
|
135
|
+
n_query: int,
|
|
136
|
+
rng: random.Random,
|
|
137
|
+
) -> float:
|
|
138
|
+
"""Teach one memory neuron per class from one example; score queries."""
|
|
139
|
+
support_x, support_y, query_x, query_y = sample_episode(
|
|
140
|
+
eval_data, by_class, n_way, k_shot=1, n_query=n_query, rng=rng,
|
|
141
|
+
)
|
|
142
|
+
memory = OneShotAssociativeMemory(in_features=embed_dim)
|
|
143
|
+
with torch.no_grad():
|
|
144
|
+
for label in range(n_way):
|
|
145
|
+
example = support_x[support_y == label]
|
|
146
|
+
teach_from_example(memory, net, encoder.encode(example))
|
|
147
|
+
preds = memory_predictions(memory, net, encoder.encode(query_x))
|
|
148
|
+
return float((preds == query_y).float().mean())
|
|
@@ -61,12 +61,15 @@ def run_held_out_digit_poc(
|
|
|
61
61
|
batch_size: int = 64,
|
|
62
62
|
eval_batch_size: int = 256,
|
|
63
63
|
train_subset: int = 1,
|
|
64
|
+
teach_examples: int = 1,
|
|
64
65
|
) -> HeldOutDigitResult:
|
|
65
66
|
"""Run the full protocol once and return its two accuracy numbers.
|
|
66
67
|
|
|
67
68
|
``train_subset`` (see :func:`_train_base`) never touches the
|
|
68
69
|
held-out digit or either evaluation set.
|
|
69
70
|
"""
|
|
71
|
+
if teach_examples < 1:
|
|
72
|
+
raise ValueError("teach_examples must be positive")
|
|
70
73
|
torch.manual_seed(seed)
|
|
71
74
|
encoder = SpikeEncoder(coding="rate", num_steps=num_steps)
|
|
72
75
|
train_data = build_dataset("mnist", train=True)
|
|
@@ -75,7 +78,9 @@ def run_held_out_digit_poc(
|
|
|
75
78
|
net = _train_base(
|
|
76
79
|
train_data, encoder, hidden, epochs, batch_size, train_subset,
|
|
77
80
|
)
|
|
78
|
-
memory = _teach_held_out_digit(
|
|
81
|
+
memory = _teach_held_out_digit(
|
|
82
|
+
net, train_data, encoder, teach_examples,
|
|
83
|
+
)
|
|
79
84
|
return _evaluate_both(
|
|
80
85
|
net, memory, test_data, encoder, eval_batch_size,
|
|
81
86
|
)
|
|
@@ -125,13 +130,24 @@ def _train_base(
|
|
|
125
130
|
|
|
126
131
|
|
|
127
132
|
def _teach_held_out_digit(
|
|
128
|
-
net: StageModule,
|
|
133
|
+
net: StageModule,
|
|
134
|
+
train_data: Dataset,
|
|
135
|
+
encoder: SpikeEncoder,
|
|
136
|
+
teach_examples: int,
|
|
129
137
|
) -> OneShotAssociativeMemory:
|
|
130
|
-
"""Build
|
|
138
|
+
"""Build memory and teach it one or more digit-9 examples."""
|
|
131
139
|
memory = OneShotAssociativeMemory(in_features=hidden_size(net))
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
held_out = digit_subset(train_data, (HELD_OUT_DIGIT,))
|
|
141
|
+
if teach_examples > len(held_out):
|
|
142
|
+
raise ValueError("teach_examples exceeds available held-out samples")
|
|
143
|
+
traces = []
|
|
144
|
+
for index in range(teach_examples):
|
|
145
|
+
teach_image, _ = held_out[index]
|
|
146
|
+
hidden_spikes, _ = hidden_trace(
|
|
147
|
+
net, encoder.encode_image(teach_image),
|
|
148
|
+
)
|
|
149
|
+
traces.append(hidden_spikes[:, 0, :])
|
|
150
|
+
memory.teach_many(torch.stack(traces))
|
|
135
151
|
return memory
|
|
136
152
|
|
|
137
153
|
|
|
@@ -76,8 +76,23 @@ class OneShotAssociativeMemory:
|
|
|
76
76
|
the new class's index, stable for the module's lifetime and
|
|
77
77
|
mapped back to a real label by the caller.
|
|
78
78
|
"""
|
|
79
|
+
return self.teach_many(hidden_spike_train.unsqueeze(0), gain)
|
|
80
|
+
|
|
81
|
+
def teach_many(
|
|
82
|
+
self,
|
|
83
|
+
hidden_spike_trains: torch.Tensor,
|
|
84
|
+
gain: float = DEFAULT_TEACH_GAIN,
|
|
85
|
+
) -> int:
|
|
86
|
+
"""Bind a class from several examples with one averaged write.
|
|
87
|
+
|
|
88
|
+
``hidden_spike_trains`` is ``[N, T, H]``. Averaging the examples'
|
|
89
|
+
spike-count traces keeps this a single Hebbian write while reducing
|
|
90
|
+
sensitivity to the particular exemplar used for teaching.
|
|
91
|
+
"""
|
|
92
|
+
if hidden_spike_trains.ndim != 3:
|
|
93
|
+
raise ValueError("teaching examples must have shape [N, T, H]")
|
|
79
94
|
index = self._synapse.grow()
|
|
80
|
-
trace =
|
|
95
|
+
trace = hidden_spike_trains.sum(dim=1).mean(dim=0)
|
|
81
96
|
self._synapse.write(trace, index, gain)
|
|
82
97
|
self._mem = None
|
|
83
98
|
return index
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Prototypical-network embedding and loss over spiking readouts."""
|
|
2
|
+
|
|
3
|
+
from typing import Tuple
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
|
|
7
|
+
from spikeforge.encoding.spike_encoder import SpikeEncoder
|
|
8
|
+
from spikeforge.simulator.runner import run
|
|
9
|
+
from spikeforge.topology.stage_module import StageModule
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def embed(
|
|
13
|
+
net: StageModule, encoder: SpikeEncoder, images: torch.Tensor,
|
|
14
|
+
) -> torch.Tensor:
|
|
15
|
+
"""Return ``[B, embed_dim]`` firing-rate embeddings for ``images``.
|
|
16
|
+
|
|
17
|
+
The embedding is the readout's per-neuron mean firing rate over
|
|
18
|
+
time (``Trajectory.logits``) -- the same quantity a fixed
|
|
19
|
+
classifier would softmax over, repurposed here as a spike-pattern
|
|
20
|
+
embedding rather than a class score.
|
|
21
|
+
"""
|
|
22
|
+
return run(net, encoder.encode(images)).logits
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def prototypical_loss(
|
|
26
|
+
support_emb: torch.Tensor,
|
|
27
|
+
support_labels: torch.Tensor,
|
|
28
|
+
query_emb: torch.Tensor,
|
|
29
|
+
query_labels: torch.Tensor,
|
|
30
|
+
n_way: int,
|
|
31
|
+
) -> Tuple[torch.Tensor, float]:
|
|
32
|
+
"""Return ``(cross-entropy loss, accuracy)`` over prototype distances.
|
|
33
|
+
|
|
34
|
+
Each class's prototype is its support embeddings' mean; a query
|
|
35
|
+
is scored by negative distance to every prototype, so "close to
|
|
36
|
+
the right prototype" is trained the same way a fixed classifier
|
|
37
|
+
trains "high score for the right class".
|
|
38
|
+
"""
|
|
39
|
+
prototypes = torch.stack(
|
|
40
|
+
[
|
|
41
|
+
support_emb[support_labels == c].mean(dim=0)
|
|
42
|
+
for c in range(n_way)
|
|
43
|
+
]
|
|
44
|
+
)
|
|
45
|
+
distances = torch.cdist(query_emb, prototypes)
|
|
46
|
+
loss = torch.nn.functional.cross_entropy(-distances, query_labels)
|
|
47
|
+
accuracy = (distances.argmin(dim=1) == query_labels).float().mean()
|
|
48
|
+
return loss, float(accuracy)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Relational Reversal Task falsification harness (issue #26).
|
|
2
|
+
|
|
3
|
+
Tests "The Consciousness Gradient" paper's own pre-committed,
|
|
4
|
+
falsifiable benchmark (Section 9.2) against the most parametrically
|
|
5
|
+
determined architecture available: a frozen, meta-trained predictor
|
|
6
|
+
whose only adaptation mechanism is conditioning on growing in-context
|
|
7
|
+
history, never a weight update. Deliberately separate from the
|
|
8
|
+
memory-system architecture roadmap in :mod:`spikeforge.memory`
|
|
9
|
+
(issues #22-#25): this package tests a claim, not a feature.
|
|
10
|
+
"""
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""A frozen, meta-trained, context-conditioned predictor (issue #26).
|
|
2
|
+
|
|
3
|
+
Trained once via backprop across many randomly permuted hierarchy
|
|
4
|
+
sessions, then frozen. Its only test-time adaptation mechanism is the
|
|
5
|
+
input itself changing as :mod:`spikeforge.rrt.trial_features`
|
|
6
|
+
recomputes the context summary from a growing history -- no weight
|
|
7
|
+
update, no local plasticity: exactly the "strongest current
|
|
8
|
+
challenge" category the paper's benchmark names.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import random
|
|
12
|
+
from typing import List, Tuple
|
|
13
|
+
|
|
14
|
+
import torch
|
|
15
|
+
|
|
16
|
+
from spikeforge.encoding.spike_encoder import SpikeEncoder
|
|
17
|
+
from spikeforge.rrt.relation import (
|
|
18
|
+
Rank,
|
|
19
|
+
hierarchy_outcome,
|
|
20
|
+
sample_pair,
|
|
21
|
+
sample_rank,
|
|
22
|
+
)
|
|
23
|
+
from spikeforge.rrt.trial_features import Trial, feature_dim, trial_features
|
|
24
|
+
from spikeforge.simulator.runner import run
|
|
25
|
+
from spikeforge.topology.registry import build_topology
|
|
26
|
+
from spikeforge.topology.stage_module import StageModule
|
|
27
|
+
|
|
28
|
+
#: Learning rate for the predictor's (offline, one-time) Adam training.
|
|
29
|
+
LEARNING_RATE = 1e-2
|
|
30
|
+
#: Longest history sampled during meta-training.
|
|
31
|
+
MAX_TRAIN_CONTEXT = 12
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def build_frozen_predictor(
|
|
35
|
+
encoder: SpikeEncoder,
|
|
36
|
+
episodes: int,
|
|
37
|
+
hidden: int = 32,
|
|
38
|
+
batch_size: int = 32,
|
|
39
|
+
seed: int = 0,
|
|
40
|
+
) -> StageModule:
|
|
41
|
+
"""Meta-train a predictor over many random hierarchy permutations.
|
|
42
|
+
|
|
43
|
+
Every training example comes from the *hierarchy* family only,
|
|
44
|
+
never the reversed cycle -- the frozen predictor must never see a
|
|
45
|
+
reversal before the evaluation protocol springs it on it. Each
|
|
46
|
+
example resamples both the session's rank permutation and the
|
|
47
|
+
context length, so the only way to do well is to infer the
|
|
48
|
+
current session's hierarchy from its context, not memorize one.
|
|
49
|
+
"""
|
|
50
|
+
_, net = build_topology(
|
|
51
|
+
"fc_small",
|
|
52
|
+
{"hidden": hidden, "num_classes": 2, "input_size": feature_dim()},
|
|
53
|
+
)
|
|
54
|
+
optimizer = torch.optim.Adam(net.parameters(), lr=LEARNING_RATE)
|
|
55
|
+
rng = random.Random(seed)
|
|
56
|
+
for _ in range(episodes):
|
|
57
|
+
_train_batch(net, optimizer, encoder, batch_size, rng)
|
|
58
|
+
net.requires_grad_(False)
|
|
59
|
+
net.eval()
|
|
60
|
+
return net
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _train_batch(
|
|
64
|
+
net: StageModule,
|
|
65
|
+
optimizer: torch.optim.Optimizer,
|
|
66
|
+
encoder: SpikeEncoder,
|
|
67
|
+
batch_size: int,
|
|
68
|
+
rng: random.Random,
|
|
69
|
+
) -> None:
|
|
70
|
+
"""Sample one batch of (context, query, label) and take a step."""
|
|
71
|
+
examples = [_sample_training_example(rng) for _ in range(batch_size)]
|
|
72
|
+
features = torch.stack([feature for feature, _ in examples])
|
|
73
|
+
labels = torch.tensor([label for _, label in examples])
|
|
74
|
+
trajectory = run(net, encoder.encode(features))
|
|
75
|
+
loss = torch.nn.functional.cross_entropy(trajectory.logits, labels)
|
|
76
|
+
optimizer.zero_grad()
|
|
77
|
+
loss.backward()
|
|
78
|
+
optimizer.step()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _sample_training_example(
|
|
82
|
+
rng: random.Random,
|
|
83
|
+
) -> Tuple[torch.Tensor, int]:
|
|
84
|
+
"""Sample one (feature vector, label) pair from a fresh session."""
|
|
85
|
+
rank = sample_rank(rng)
|
|
86
|
+
context_len = rng.randint(0, MAX_TRAIN_CONTEXT)
|
|
87
|
+
history: List[Trial] = [
|
|
88
|
+
_random_trial(rank, rng) for _ in range(context_len)
|
|
89
|
+
]
|
|
90
|
+
i, j = sample_pair(rng)
|
|
91
|
+
label = hierarchy_outcome(rank, i, j)
|
|
92
|
+
return trial_features(i, j, history), label
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _random_trial(rank: Rank, rng: random.Random) -> Trial:
|
|
96
|
+
"""Sample one past ``(i, j, observed outcome)`` trial under ``rank``."""
|
|
97
|
+
i, j = sample_pair(rng)
|
|
98
|
+
return i, j, hierarchy_outcome(rank, i, j)
|