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.
Files changed (252) hide show
  1. {spikeforge-0.3.1 → spikeforge-0.3.2}/PKG-INFO +2 -1
  2. {spikeforge-0.3.1 → spikeforge-0.3.2}/README.md +1 -0
  3. {spikeforge-0.3.1 → 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.1 → spikeforge-0.3.2}/spikeforge/memory/held_out_digit_poc.py +22 -6
  9. {spikeforge-0.3.1 → 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/rrt/__init__.py +10 -0
  12. spikeforge-0.3.2/spikeforge/rrt/context_model.py +98 -0
  13. spikeforge-0.3.2/spikeforge/rrt/falsification_poc.py +110 -0
  14. spikeforge-0.3.2/spikeforge/rrt/protocol.py +130 -0
  15. spikeforge-0.3.2/spikeforge/rrt/relation.py +50 -0
  16. spikeforge-0.3.2/spikeforge/rrt/trial_features.py +66 -0
  17. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge.egg-info/PKG-INFO +2 -1
  18. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge.egg-info/SOURCES.txt +11 -0
  19. {spikeforge-0.3.1 → spikeforge-0.3.2}/LICENSE +0 -0
  20. {spikeforge-0.3.1 → spikeforge-0.3.2}/main.py +0 -0
  21. {spikeforge-0.3.1 → spikeforge-0.3.2}/main_encodings.py +0 -0
  22. {spikeforge-0.3.1 → spikeforge-0.3.2}/setup.cfg +0 -0
  23. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/__init__.py +0 -0
  24. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/__init__.py +0 -0
  25. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/__main__.py +0 -0
  26. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/cli.py +0 -0
  27. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/compare.py +0 -0
  28. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/config.py +0 -0
  29. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/energy.py +0 -0
  30. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/harness.py +0 -0
  31. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/memory.py +0 -0
  32. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/serving.py +0 -0
  33. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/store.py +0 -0
  34. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/suite.py +0 -0
  35. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/benchmark/timing.py +0 -0
  36. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/cli/__init__.py +0 -0
  37. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/cli/backend_cli.py +0 -0
  38. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/cli/extract_cli.py +0 -0
  39. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/cli/fixture.py +0 -0
  40. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/cli/onnx_cli.py +0 -0
  41. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/cli/records_cli.py +0 -0
  42. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/cli/verify.py +0 -0
  43. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/compression/__init__.py +0 -0
  44. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/compression/codec.py +0 -0
  45. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/compression/errors.py +0 -0
  46. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/compression/pruning.py +0 -0
  47. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/compression/report.py +0 -0
  48. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/config.py +0 -0
  49. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/__init__.py +0 -0
  50. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/data_loader.py +0 -0
  51. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/dataset_spec.py +0 -0
  52. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/datasets.py +0 -0
  53. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/download_cli.py +0 -0
  54. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/event_errors.py +0 -0
  55. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/event_geometry.py +0 -0
  56. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/event_loader.py +0 -0
  57. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/image_size.py +0 -0
  58. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/sample_source.py +0 -0
  59. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/data/sequence_source.py +0 -0
  60. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/encoding/__init__.py +0 -0
  61. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/encoding/delta_trainer.py +0 -0
  62. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/encoding/latency_trainer.py +0 -0
  63. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/encoding/random_spikegen.py +0 -0
  64. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/encoding/spike_encoder.py +0 -0
  65. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/events/__init__.py +0 -0
  66. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/events/dense.py +0 -0
  67. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/events/event_bridge.py +0 -0
  68. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/events/event_sample.py +0 -0
  69. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/events/event_source.py +0 -0
  70. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/events/synthetic.py +0 -0
  71. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/events/tonic_api.py +0 -0
  72. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/__init__.py +0 -0
  73. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/delta_exporter.py +0 -0
  74. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/exporter.py +0 -0
  75. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/latency_curve_exporter.py +0 -0
  76. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/latency_raster_exporter.py +0 -0
  77. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/latency_video_exporter.py +0 -0
  78. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/plot_utils.py +0 -0
  79. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/presentation_exporter.py +0 -0
  80. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/random_spike_raster_exporter.py +0 -0
  81. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/random_spike_video_exporter.py +0 -0
  82. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/raster_exporter.py +0 -0
  83. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/reconstruction_exporter.py +0 -0
  84. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/spike_gif_exporter.py +0 -0
  85. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/exporters/video_exporter.py +0 -0
  86. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/__init__.py +0 -0
  87. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/comparison.py +0 -0
  88. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/decoding.py +0 -0
  89. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/encoding.py +0 -0
  90. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/firing_rate.py +0 -0
  91. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/histogram.py +0 -0
  92. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/isi.py +0 -0
  93. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/metrics.py +0 -0
  94. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/sparsity.py +0 -0
  95. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/introspection/surrogate.py +0 -0
  96. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/memory/__init__.py +0 -0
  97. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/memory/frozen_classifier.py +0 -0
  98. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/memory/hebbian_synapse.py +0 -0
  99. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/memory/lockstep_runner.py +0 -0
  100. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/memory/stdp_synapse.py +0 -0
  101. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/memory/stdp_window_poc.py +0 -0
  102. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/network/__init__.py +0 -0
  103. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/network/hidden_frames.py +0 -0
  104. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/network/inference.py +0 -0
  105. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/network/model_diff.py +0 -0
  106. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/network/model_search.py +0 -0
  107. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/network/model_store.py +0 -0
  108. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/network/spiking_net.py +0 -0
  109. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/__init__.py +0 -0
  110. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/alpha.py +0 -0
  111. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/contract.py +0 -0
  112. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/handler.py +0 -0
  113. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/lapicque.py +0 -0
  114. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/leaky.py +0 -0
  115. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/recurrent.py +0 -0
  116. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/registry.py +0 -0
  117. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/spike_grad.py +0 -0
  118. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/neurons/synaptic.py +0 -0
  119. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/__init__.py +0 -0
  120. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/api.py +0 -0
  121. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/array_codec.py +0 -0
  122. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/drift.py +0 -0
  123. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/errors.py +0 -0
  124. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/exporter.py +0 -0
  125. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/extract.py +0 -0
  126. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/ingest.py +0 -0
  127. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/interpreter.py +0 -0
  128. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/interpreter_result.py +0 -0
  129. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/jsonable.py +0 -0
  130. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/mapped_node.py +0 -0
  131. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/mapper.py +0 -0
  132. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/neuron_kwargs.py +0 -0
  133. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/neuron_nodes.py +0 -0
  134. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/node_builders.py +0 -0
  135. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/node_names.py +0 -0
  136. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/ops_common.py +0 -0
  137. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/ops_linear.py +0 -0
  138. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/ops_neuron.py +0 -0
  139. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/ops_registry.py +0 -0
  140. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/planner.py +0 -0
  141. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/require.py +0 -0
  142. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/roundtrip.py +0 -0
  143. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/serialization.py +0 -0
  144. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/stage_builders.py +0 -0
  145. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/stage_mapping.py +0 -0
  146. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/stages_unmappable.py +0 -0
  147. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/tolerances.py +0 -0
  148. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/torch_map.py +0 -0
  149. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/validation_report.py +0 -0
  150. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/nir_bridge/validator.py +0 -0
  151. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/__init__.py +0 -0
  152. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/json_formatter.py +0 -0
  153. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/logging_setup.py +0 -0
  154. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/metrics.py +0 -0
  155. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/persistence.py +0 -0
  156. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/prometheus.py +0 -0
  157. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/registry.py +0 -0
  158. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/snapshot.py +0 -0
  159. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/store.py +0 -0
  160. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/observability/timer.py +0 -0
  161. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/__init__.py +0 -0
  162. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/api.py +0 -0
  163. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/errors.py +0 -0
  164. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/export.py +0 -0
  165. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/import_onnx.py +0 -0
  166. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/metadata.py +0 -0
  167. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/roundtrip.py +0 -0
  168. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/onnx_bridge/step_module.py +0 -0
  169. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/py.typed +0 -0
  170. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/runtime/__init__.py +0 -0
  171. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/runtime/device.py +0 -0
  172. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/runtime/execution_mode.py +0 -0
  173. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/runtime/system_stats.py +0 -0
  174. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/__init__.py +0 -0
  175. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/bundle.py +0 -0
  176. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/bundle_manifest.py +0 -0
  177. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/encode_spec.py +0 -0
  178. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/errors.py +0 -0
  179. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/prediction.py +0 -0
  180. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/preprocess.py +0 -0
  181. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/session.py +0 -0
  182. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/state_tree.py +0 -0
  183. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/step.py +0 -0
  184. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/serving/tensor_codec.py +0 -0
  185. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/__init__.py +0 -0
  186. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/compiled_step.py +0 -0
  187. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/execution.py +0 -0
  188. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/frames.py +0 -0
  189. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/grad_policy.py +0 -0
  190. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/input_shape.py +0 -0
  191. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/module_spec.py +0 -0
  192. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/parallel_runner.py +0 -0
  193. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/production.py +0 -0
  194. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/production_result.py +0 -0
  195. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/runner.py +0 -0
  196. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/state.py +0 -0
  197. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/step_stages.py +0 -0
  198. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/simulator/trajectory.py +0 -0
  199. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/streaming/__init__.py +0 -0
  200. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/streaming/encoding.py +0 -0
  201. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/streaming/recipe.py +0 -0
  202. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/streaming/serving.py +0 -0
  203. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/streaming/stream_source.py +0 -0
  204. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/streaming/window_spec.py +0 -0
  205. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/__init__.py +0 -0
  206. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/attention.py +0 -0
  207. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/builder.py +0 -0
  208. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/edge.py +0 -0
  209. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/kinds.py +0 -0
  210. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/multihead_attention.py +0 -0
  211. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/ordering.py +0 -0
  212. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/positional_encoding.py +0 -0
  213. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/presets.py +0 -0
  214. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/registry.py +0 -0
  215. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/sequence_presets.py +0 -0
  216. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/sequence_stages.py +0 -0
  217. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/spec.py +0 -0
  218. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/stage.py +0 -0
  219. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/stage_module.py +0 -0
  220. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/stage_modules.py +0 -0
  221. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/sum_pool.py +0 -0
  222. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/topology/validation.py +0 -0
  223. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/__init__.py +0 -0
  224. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/config_hash.py +0 -0
  225. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/determinism.py +0 -0
  226. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/manifest.py +0 -0
  227. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/seed.py +0 -0
  228. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/sink.py +0 -0
  229. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/sink_probe.py +0 -0
  230. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/sink_records.py +0 -0
  231. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/sinks.py +0 -0
  232. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/tensorboard_sink.py +0 -0
  233. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/versions.py +0 -0
  234. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/tracking/wandb_sink.py +0 -0
  235. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/__init__.py +0 -0
  236. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/amp_controller.py +0 -0
  237. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/checkpoint_mixin.py +0 -0
  238. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/encoding_mixin.py +0 -0
  239. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/eval_mixin.py +0 -0
  240. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/event_batches.py +0 -0
  241. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/event_engine.py +0 -0
  242. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/logger.py +0 -0
  243. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/multi_device.py +0 -0
  244. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/scaleup_mixin.py +0 -0
  245. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/topology_mixin.py +0 -0
  246. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/trainer.py +0 -0
  247. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge/training/training_engine.py +0 -0
  248. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge.egg-info/dependency_links.txt +0 -0
  249. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge.egg-info/entry_points.txt +0 -0
  250. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge.egg-info/not-zip-safe +0 -0
  251. {spikeforge-0.3.1 → spikeforge-0.3.2}/spikeforge.egg-info/requires.txt +0 -0
  252. {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.1
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/)
@@ -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/)
@@ -15,7 +15,7 @@ build-backend = "setuptools.build_meta"
15
15
 
16
16
  [project]
17
17
  name = "spikeforge"
18
- version = "0.3.1"
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,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)