spikeforge 0.3.0__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.
Files changed (252) hide show
  1. {spikeforge-0.3.0 → spikeforge-0.3.2}/PKG-INFO +5 -4
  2. {spikeforge-0.3.0 → spikeforge-0.3.2}/README.md +4 -3
  3. {spikeforge-0.3.0 → spikeforge-0.3.2}/pyproject.toml +1 -1
  4. spikeforge-0.3.2/spikeforge/memory/embedding_readout.py +51 -0
  5. spikeforge-0.3.2/spikeforge/memory/episode_sampler.py +63 -0
  6. spikeforge-0.3.2/spikeforge/memory/episodic_trainer.py +77 -0
  7. spikeforge-0.3.2/spikeforge/memory/few_shot_generalization_poc.py +148 -0
  8. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/memory/held_out_digit_poc.py +22 -6
  9. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/memory/one_shot_associative_memory.py +16 -1
  10. spikeforge-0.3.2/spikeforge/memory/prototypical.py +48 -0
  11. spikeforge-0.3.2/spikeforge/memory/stdp_synapse.py +96 -0
  12. spikeforge-0.3.2/spikeforge/memory/stdp_window_poc.py +62 -0
  13. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/network/model_store.py +12 -2
  14. spikeforge-0.3.2/spikeforge/rrt/__init__.py +10 -0
  15. spikeforge-0.3.2/spikeforge/rrt/context_model.py +98 -0
  16. spikeforge-0.3.2/spikeforge/rrt/falsification_poc.py +110 -0
  17. spikeforge-0.3.2/spikeforge/rrt/protocol.py +130 -0
  18. spikeforge-0.3.2/spikeforge/rrt/relation.py +50 -0
  19. spikeforge-0.3.2/spikeforge/rrt/trial_features.py +66 -0
  20. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge.egg-info/PKG-INFO +5 -4
  21. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge.egg-info/SOURCES.txt +13 -0
  22. {spikeforge-0.3.0 → spikeforge-0.3.2}/LICENSE +0 -0
  23. {spikeforge-0.3.0 → spikeforge-0.3.2}/main.py +0 -0
  24. {spikeforge-0.3.0 → spikeforge-0.3.2}/main_encodings.py +0 -0
  25. {spikeforge-0.3.0 → spikeforge-0.3.2}/setup.cfg +0 -0
  26. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/__init__.py +0 -0
  27. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/benchmark/__init__.py +0 -0
  28. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/benchmark/__main__.py +0 -0
  29. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/benchmark/cli.py +0 -0
  30. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/benchmark/compare.py +0 -0
  31. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/benchmark/config.py +0 -0
  32. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/benchmark/energy.py +0 -0
  33. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/benchmark/harness.py +0 -0
  34. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/benchmark/memory.py +0 -0
  35. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/benchmark/serving.py +0 -0
  36. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/benchmark/store.py +0 -0
  37. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/benchmark/suite.py +0 -0
  38. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/benchmark/timing.py +0 -0
  39. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/cli/__init__.py +0 -0
  40. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/cli/backend_cli.py +0 -0
  41. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/cli/extract_cli.py +0 -0
  42. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/cli/fixture.py +0 -0
  43. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/cli/onnx_cli.py +0 -0
  44. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/cli/records_cli.py +0 -0
  45. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/cli/verify.py +0 -0
  46. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/compression/__init__.py +0 -0
  47. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/compression/codec.py +0 -0
  48. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/compression/errors.py +0 -0
  49. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/compression/pruning.py +0 -0
  50. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/compression/report.py +0 -0
  51. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/config.py +0 -0
  52. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/data/__init__.py +0 -0
  53. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/data/data_loader.py +0 -0
  54. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/data/dataset_spec.py +0 -0
  55. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/data/datasets.py +0 -0
  56. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/data/download_cli.py +0 -0
  57. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/data/event_errors.py +0 -0
  58. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/data/event_geometry.py +0 -0
  59. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/data/event_loader.py +0 -0
  60. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/data/image_size.py +0 -0
  61. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/data/sample_source.py +0 -0
  62. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/data/sequence_source.py +0 -0
  63. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/encoding/__init__.py +0 -0
  64. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/encoding/delta_trainer.py +0 -0
  65. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/encoding/latency_trainer.py +0 -0
  66. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/encoding/random_spikegen.py +0 -0
  67. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/encoding/spike_encoder.py +0 -0
  68. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/events/__init__.py +0 -0
  69. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/events/dense.py +0 -0
  70. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/events/event_bridge.py +0 -0
  71. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/events/event_sample.py +0 -0
  72. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/events/event_source.py +0 -0
  73. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/events/synthetic.py +0 -0
  74. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/events/tonic_api.py +0 -0
  75. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/exporters/__init__.py +0 -0
  76. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/exporters/delta_exporter.py +0 -0
  77. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/exporters/exporter.py +0 -0
  78. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/exporters/latency_curve_exporter.py +0 -0
  79. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/exporters/latency_raster_exporter.py +0 -0
  80. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/exporters/latency_video_exporter.py +0 -0
  81. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/exporters/plot_utils.py +0 -0
  82. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/exporters/presentation_exporter.py +0 -0
  83. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/exporters/random_spike_raster_exporter.py +0 -0
  84. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/exporters/random_spike_video_exporter.py +0 -0
  85. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/exporters/raster_exporter.py +0 -0
  86. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/exporters/reconstruction_exporter.py +0 -0
  87. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/exporters/spike_gif_exporter.py +0 -0
  88. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/exporters/video_exporter.py +0 -0
  89. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/introspection/__init__.py +0 -0
  90. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/introspection/comparison.py +0 -0
  91. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/introspection/decoding.py +0 -0
  92. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/introspection/encoding.py +0 -0
  93. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/introspection/firing_rate.py +0 -0
  94. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/introspection/histogram.py +0 -0
  95. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/introspection/isi.py +0 -0
  96. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/introspection/metrics.py +0 -0
  97. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/introspection/sparsity.py +0 -0
  98. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/introspection/surrogate.py +0 -0
  99. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/memory/__init__.py +0 -0
  100. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/memory/frozen_classifier.py +0 -0
  101. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/memory/hebbian_synapse.py +0 -0
  102. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/memory/lockstep_runner.py +0 -0
  103. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/network/__init__.py +0 -0
  104. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/network/hidden_frames.py +0 -0
  105. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/network/inference.py +0 -0
  106. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/network/model_diff.py +0 -0
  107. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/network/model_search.py +0 -0
  108. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/network/spiking_net.py +0 -0
  109. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/neurons/__init__.py +0 -0
  110. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/neurons/alpha.py +0 -0
  111. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/neurons/contract.py +0 -0
  112. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/neurons/handler.py +0 -0
  113. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/neurons/lapicque.py +0 -0
  114. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/neurons/leaky.py +0 -0
  115. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/neurons/recurrent.py +0 -0
  116. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/neurons/registry.py +0 -0
  117. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/neurons/spike_grad.py +0 -0
  118. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/neurons/synaptic.py +0 -0
  119. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/__init__.py +0 -0
  120. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/api.py +0 -0
  121. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/array_codec.py +0 -0
  122. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/drift.py +0 -0
  123. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/errors.py +0 -0
  124. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/exporter.py +0 -0
  125. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/extract.py +0 -0
  126. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/ingest.py +0 -0
  127. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/interpreter.py +0 -0
  128. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/interpreter_result.py +0 -0
  129. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/jsonable.py +0 -0
  130. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/mapped_node.py +0 -0
  131. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/mapper.py +0 -0
  132. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/neuron_kwargs.py +0 -0
  133. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/neuron_nodes.py +0 -0
  134. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/node_builders.py +0 -0
  135. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/node_names.py +0 -0
  136. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/ops_common.py +0 -0
  137. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/ops_linear.py +0 -0
  138. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/ops_neuron.py +0 -0
  139. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/ops_registry.py +0 -0
  140. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/planner.py +0 -0
  141. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/require.py +0 -0
  142. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/roundtrip.py +0 -0
  143. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/serialization.py +0 -0
  144. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/stage_builders.py +0 -0
  145. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/stage_mapping.py +0 -0
  146. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/stages_unmappable.py +0 -0
  147. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/tolerances.py +0 -0
  148. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/torch_map.py +0 -0
  149. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/validation_report.py +0 -0
  150. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/nir_bridge/validator.py +0 -0
  151. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/observability/__init__.py +0 -0
  152. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/observability/json_formatter.py +0 -0
  153. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/observability/logging_setup.py +0 -0
  154. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/observability/metrics.py +0 -0
  155. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/observability/persistence.py +0 -0
  156. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/observability/prometheus.py +0 -0
  157. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/observability/registry.py +0 -0
  158. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/observability/snapshot.py +0 -0
  159. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/observability/store.py +0 -0
  160. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/observability/timer.py +0 -0
  161. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/onnx_bridge/__init__.py +0 -0
  162. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/onnx_bridge/api.py +0 -0
  163. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/onnx_bridge/errors.py +0 -0
  164. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/onnx_bridge/export.py +0 -0
  165. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/onnx_bridge/import_onnx.py +0 -0
  166. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/onnx_bridge/metadata.py +0 -0
  167. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/onnx_bridge/roundtrip.py +0 -0
  168. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/onnx_bridge/step_module.py +0 -0
  169. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/py.typed +0 -0
  170. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/runtime/__init__.py +0 -0
  171. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/runtime/device.py +0 -0
  172. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/runtime/execution_mode.py +0 -0
  173. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/runtime/system_stats.py +0 -0
  174. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/serving/__init__.py +0 -0
  175. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/serving/bundle.py +0 -0
  176. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/serving/bundle_manifest.py +0 -0
  177. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/serving/encode_spec.py +0 -0
  178. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/serving/errors.py +0 -0
  179. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/serving/prediction.py +0 -0
  180. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/serving/preprocess.py +0 -0
  181. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/serving/session.py +0 -0
  182. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/serving/state_tree.py +0 -0
  183. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/serving/step.py +0 -0
  184. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/serving/tensor_codec.py +0 -0
  185. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/simulator/__init__.py +0 -0
  186. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/simulator/compiled_step.py +0 -0
  187. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/simulator/execution.py +0 -0
  188. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/simulator/frames.py +0 -0
  189. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/simulator/grad_policy.py +0 -0
  190. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/simulator/input_shape.py +0 -0
  191. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/simulator/module_spec.py +0 -0
  192. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/simulator/parallel_runner.py +0 -0
  193. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/simulator/production.py +0 -0
  194. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/simulator/production_result.py +0 -0
  195. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/simulator/runner.py +0 -0
  196. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/simulator/state.py +0 -0
  197. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/simulator/step_stages.py +0 -0
  198. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/simulator/trajectory.py +0 -0
  199. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/streaming/__init__.py +0 -0
  200. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/streaming/encoding.py +0 -0
  201. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/streaming/recipe.py +0 -0
  202. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/streaming/serving.py +0 -0
  203. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/streaming/stream_source.py +0 -0
  204. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/streaming/window_spec.py +0 -0
  205. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/__init__.py +0 -0
  206. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/attention.py +0 -0
  207. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/builder.py +0 -0
  208. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/edge.py +0 -0
  209. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/kinds.py +0 -0
  210. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/multihead_attention.py +0 -0
  211. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/ordering.py +0 -0
  212. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/positional_encoding.py +0 -0
  213. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/presets.py +0 -0
  214. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/registry.py +0 -0
  215. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/sequence_presets.py +0 -0
  216. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/sequence_stages.py +0 -0
  217. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/spec.py +0 -0
  218. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/stage.py +0 -0
  219. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/stage_module.py +0 -0
  220. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/stage_modules.py +0 -0
  221. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/sum_pool.py +0 -0
  222. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/topology/validation.py +0 -0
  223. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/tracking/__init__.py +0 -0
  224. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/tracking/config_hash.py +0 -0
  225. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/tracking/determinism.py +0 -0
  226. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/tracking/manifest.py +0 -0
  227. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/tracking/seed.py +0 -0
  228. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/tracking/sink.py +0 -0
  229. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/tracking/sink_probe.py +0 -0
  230. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/tracking/sink_records.py +0 -0
  231. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/tracking/sinks.py +0 -0
  232. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/tracking/tensorboard_sink.py +0 -0
  233. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/tracking/versions.py +0 -0
  234. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/tracking/wandb_sink.py +0 -0
  235. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/training/__init__.py +0 -0
  236. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/training/amp_controller.py +0 -0
  237. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/training/checkpoint_mixin.py +0 -0
  238. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/training/encoding_mixin.py +0 -0
  239. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/training/eval_mixin.py +0 -0
  240. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/training/event_batches.py +0 -0
  241. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/training/event_engine.py +0 -0
  242. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/training/logger.py +0 -0
  243. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/training/multi_device.py +0 -0
  244. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/training/scaleup_mixin.py +0 -0
  245. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/training/topology_mixin.py +0 -0
  246. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/training/trainer.py +0 -0
  247. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge/training/training_engine.py +0 -0
  248. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge.egg-info/dependency_links.txt +0 -0
  249. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge.egg-info/entry_points.txt +0 -0
  250. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge.egg-info/not-zip-safe +0 -0
  251. {spikeforge-0.3.0 → spikeforge-0.3.2}/spikeforge.egg-info/requires.txt +0 -0
  252. {spikeforge-0.3.0 → 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.0
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
  [![CI](https://github.com/capsize-games/spikeforge/actions/workflows/ci.yml/badge.svg)](https://github.com/capsize-games/spikeforge/actions/workflows/ci.yml)
59
+ [![Discord](https://img.shields.io/badge/Discord-Join%20the%20community-5865F2?logo=discord&logoColor=white)](https://capsizegames.com/discord)
59
60
  [![Status: pre-1.0](https://img.shields.io/badge/status-pre--1.0-orange.svg)](OPEN_SOURCE_CHECKLIST.md)
60
61
  [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](LICENSE)
61
62
  [![Python 3.10–3.13](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-3776AB.svg)](https://www.python.org/downloads/)
@@ -63,15 +64,15 @@ Dynamic: license-file
63
64
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/capsize-games/spikeforge/blob/main/CONTRIBUTING.md)
64
65
  [![Docs](https://img.shields.io/badge/docs-long--form%20reference-blue.svg)](documentation/README.md)
65
66
 
67
+ ![spikeforge dashboard](images/dashboard.png)
68
+
66
69
  A spiking-neural-network (SNN) toolkit built on
67
70
  [snnTorch](https://snntorch.readthedocs.io/) and PyTorch. Loads MNIST-style
68
71
  and neuromorphic event datasets, encodes them into rate, latency, delta, and
69
72
  random spikes, and trains, validates, exports, and deploys LIF networks —
70
73
  with a live browser dashboard served over WebSockets.
71
74
 
72
- > **Pre-1.0 and unpublished.** `install.sh` is the supported path today;
73
- > PyPI ships once the distributions are published. Before trusting any
74
- > number this produces, read
75
+ > **Pre-1.0.** Before trusting any number this produces, read
75
76
  > [Implications and boundaries](documentation/implications-and-boundaries.md).
76
77
 
77
78
  ## Quickstart
@@ -1,6 +1,7 @@
1
1
  # spikeforge
2
2
 
3
3
  [![CI](https://github.com/capsize-games/spikeforge/actions/workflows/ci.yml/badge.svg)](https://github.com/capsize-games/spikeforge/actions/workflows/ci.yml)
4
+ [![Discord](https://img.shields.io/badge/Discord-Join%20the%20community-5865F2?logo=discord&logoColor=white)](https://capsizegames.com/discord)
4
5
  [![Status: pre-1.0](https://img.shields.io/badge/status-pre--1.0-orange.svg)](OPEN_SOURCE_CHECKLIST.md)
5
6
  [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](LICENSE)
6
7
  [![Python 3.10–3.13](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-3776AB.svg)](https://www.python.org/downloads/)
@@ -8,15 +9,15 @@
8
9
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/capsize-games/spikeforge/blob/main/CONTRIBUTING.md)
9
10
  [![Docs](https://img.shields.io/badge/docs-long--form%20reference-blue.svg)](documentation/README.md)
10
11
 
12
+ ![spikeforge dashboard](images/dashboard.png)
13
+
11
14
  A spiking-neural-network (SNN) toolkit built on
12
15
  [snnTorch](https://snntorch.readthedocs.io/) and PyTorch. Loads MNIST-style
13
16
  and neuromorphic event datasets, encodes them into rate, latency, delta, and
14
17
  random spikes, and trains, validates, exports, and deploys LIF networks —
15
18
  with a live browser dashboard served over WebSockets.
16
19
 
17
- > **Pre-1.0 and unpublished.** `install.sh` is the supported path today;
18
- > PyPI ships once the distributions are published. Before trusting any
19
- > number this produces, read
20
+ > **Pre-1.0.** Before trusting any number this produces, read
20
21
  > [Implications and boundaries](documentation/implications-and-boundaries.md).
21
22
 
22
23
  ## Quickstart
@@ -15,7 +15,7 @@ build-backend = "setuptools.build_meta"
15
15
 
16
16
  [project]
17
17
  name = "spikeforge"
18
- version = "0.3.0"
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(net, train_data, encoder)
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, train_data: Dataset, encoder: SpikeEncoder,
133
+ net: StageModule,
134
+ train_data: Dataset,
135
+ encoder: SpikeEncoder,
136
+ teach_examples: int,
129
137
  ) -> OneShotAssociativeMemory:
130
- """Build a memory module and teach it one digit-9 example."""
138
+ """Build memory and teach it one or more digit-9 examples."""
131
139
  memory = OneShotAssociativeMemory(in_features=hidden_size(net))
132
- teach_image, _ = digit_subset(train_data, (HELD_OUT_DIGIT,))[0]
133
- hidden_spikes, _ = hidden_trace(net, encoder.encode_image(teach_image))
134
- memory.teach(hidden_spikes[:, 0, :])
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 = hidden_spike_train.sum(dim=0)
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,96 @@
1
+ """A feedforward synapse written by pair-based spike-timing plasticity."""
2
+
3
+ from typing import Optional
4
+
5
+ import torch
6
+
7
+
8
+ class STDPSynapse:
9
+ """Weights written step-by-step from real pre/post spike timing.
10
+
11
+ :class:`~spikeforge.memory.hebbian_synapse.HebbianSynapse` writes one
12
+ column in a single outer-product shot from a summed spike-count trace,
13
+ which is a rate code, not a timing code. This synapse instead tracks
14
+ exponentially-decaying eligibility traces for both sides of the
15
+ connection and updates every weight at every timestep from the
16
+ genuine order of spikes: a postsynaptic spike potentiates whichever
17
+ presynaptic inputs recently fired (pre-before-post, causal), and a
18
+ presynaptic spike depresses whichever outputs recently fired
19
+ (post-before-pre, anti-causal). No optimizer, no loss, no backward
20
+ pass touches these weights — only :meth:`step`.
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ in_features: int,
26
+ out_features: int,
27
+ tau_pre: float = 20.0,
28
+ tau_post: float = 20.0,
29
+ a_plus: float = 0.01,
30
+ a_minus: float = 0.012,
31
+ w_min: float = 0.0,
32
+ w_max: float = 1.0,
33
+ device: Optional[torch.device] = None,
34
+ ) -> None:
35
+ """Start from zero weights and zero eligibility traces.
36
+
37
+ ``a_minus`` slightly exceeds ``a_plus`` (the classic Song/Abbott
38
+ ratio) so an uncorrelated pre/post pair decays rather than drifts
39
+ toward saturation; ``tau_pre``/``tau_post`` set how many steps a
40
+ spike's influence on the other side's timing window survives.
41
+ """
42
+ self._device = device or torch.device("cpu")
43
+ self.tau_pre = tau_pre
44
+ self.tau_post = tau_post
45
+ self.a_plus = a_plus
46
+ self.a_minus = a_minus
47
+ self.w_min = w_min
48
+ self.w_max = w_max
49
+ self._weight = torch.zeros(
50
+ in_features, out_features, device=self._device
51
+ )
52
+ self._pre_trace = torch.zeros(in_features, device=self._device)
53
+ self._post_trace = torch.zeros(out_features, device=self._device)
54
+
55
+ @property
56
+ def weight(self) -> torch.Tensor:
57
+ """Return the current synaptic weight matrix."""
58
+ return self._weight
59
+
60
+ def set_weight(self, weight: torch.Tensor) -> None:
61
+ """Overwrite the weight matrix (e.g. to seed a starting condition)."""
62
+ self._weight = weight.to(self._device)
63
+
64
+ def step(
65
+ self, pre_spikes: torch.Tensor, post_spikes: torch.Tensor
66
+ ) -> None:
67
+ """Advance both traces by one timestep and apply the STDP update.
68
+
69
+ Trace decay happens first so a spike this step reads the *prior*
70
+ step's trace when computing its update — the update a spike
71
+ causes must depend on what came before it, not on itself.
72
+ """
73
+ pre_spikes = pre_spikes.to(self._device)
74
+ post_spikes = post_spikes.to(self._device)
75
+ with torch.no_grad():
76
+ decay_pre = torch.exp(torch.tensor(-1.0 / self.tau_pre))
77
+ decay_post = torch.exp(torch.tensor(-1.0 / self.tau_post))
78
+ self._pre_trace = self._pre_trace * decay_pre
79
+ self._post_trace = self._post_trace * decay_post
80
+
81
+ # A post spike landing on a lingering pre trace is causal
82
+ # (pre fired first) and potentiates; a pre spike landing on a
83
+ # lingering post trace is anti-causal (post fired first) and
84
+ # depresses. Each side reads the *other* trace, not its own.
85
+ potentiation = torch.outer(self._pre_trace, post_spikes)
86
+ depression = torch.outer(pre_spikes, self._post_trace)
87
+ self._weight += self.a_plus * potentiation
88
+ self._weight -= self.a_minus * depression
89
+ self._weight.clamp_(self.w_min, self.w_max)
90
+
91
+ self._pre_trace += pre_spikes
92
+ self._post_trace += post_spikes
93
+
94
+ def forward(self, spikes: torch.Tensor) -> torch.Tensor:
95
+ """Return the synaptic current ``spikes @ weight`` for one step."""
96
+ return spikes.to(self._device) @ self._weight