@oxide-js/spiking 1.1.0 → 1.3.0
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.
- package/CHANGELOG.md +19 -0
- package/index.cjs +322 -0
- package/index.d.ts +5 -13
- package/index.js +6 -2
- package/package.json +1 -1
- package/spiking-native.linux-x64-gnu.node +0 -0
- package/src/index.ts +4 -2
- package/src/layers/SpikingDense.ts +71 -42
- package/src/layers/SpikingDenseBPTT.ts +303 -0
- package/src/layers/SpikingEmbedding.ts +154 -142
- package/src/layers/SpikingSelfAttention.ts +335 -0
- package/src/native_backend.ts +39 -3
- package/src-rust/src/contrastive.rs +85 -0
- package/src-rust/src/delta.rs +51 -0
- package/src-rust/src/dot_product.rs +47 -0
- package/src-rust/src/embedding.rs +28 -0
- package/src-rust/src/lib.rs +16 -460
- package/src-rust/src/lif.rs +44 -0
- package/src-rust/src/surrogate.rs +28 -0
- package/test/SpikingDenseBPTT.test.ts +151 -0
- package/test/SpikingSelfAttention.test.ts +148 -0
- package/test/test_embedding_overlap.ts +181 -0
- package/examples/demo.ts +0 -101
- package/src/models/SpikingSentenceEmbedder.ts +0 -135
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @oxide-js/spiking
|
|
2
2
|
|
|
3
|
+
## 1.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1f6489c: - Added `SpikingDenseBPTT` temporal pooler with Sequence-as-Time dynamics.
|
|
8
|
+
- Implemented Spike-Count Accumulation with L2 Normalization to solve membrane saturation in long sequences.
|
|
9
|
+
- Updated documentation for SpikingDenseBPTT in API layers.
|
|
10
|
+
|
|
11
|
+
## 1.2.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- f0b623b: Refactored Native Rust Backend to implement Rayon parallelism and added Heterogeneous Neuron Dynamics
|
|
16
|
+
|
|
17
|
+
- **Parallel Computing**: Integrated `rayon` into the native Rust backend to parallelize operations (`lifStep`, `surrogateMask`, `dotProduct`, `deltaUpdates`).
|
|
18
|
+
- **Heterogeneous Neuron Dynamics**: Decoupled `beta` (Leakage) and `threshold` scalars into random per-neuron array buffers for model stochasticity.
|
|
19
|
+
- **Improved Mathematical Stability**: Enforced membrane potential clamping at `1.0` and strict weight/bias clipping within the `[-1.0, 1.0]` range to prevent gradient explosions during Hebbian Updates.
|
|
20
|
+
- **Native Data Races Handled**: Safely extracted slice pointers before processing `Float32Array` buffers with Rayon to guarantee thread-safe (Send + Sync) execution across parallel units.
|
|
21
|
+
|
|
3
22
|
## 1.1.0
|
|
4
23
|
|
|
5
24
|
### Minor Changes
|
package/index.cjs
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/* prettier-ignore */
|
|
4
|
+
|
|
5
|
+
/* auto-generated by NAPI-RS */
|
|
6
|
+
|
|
7
|
+
const { existsSync, readFileSync } = require('fs')
|
|
8
|
+
const { join } = require('path')
|
|
9
|
+
|
|
10
|
+
const { platform, arch } = process
|
|
11
|
+
|
|
12
|
+
let nativeBinding = null
|
|
13
|
+
let localFileExisted = false
|
|
14
|
+
let loadError = null
|
|
15
|
+
|
|
16
|
+
function isMusl() {
|
|
17
|
+
// For Node 10
|
|
18
|
+
if (!process.report || typeof process.report.getReport !== 'function') {
|
|
19
|
+
try {
|
|
20
|
+
const lddPath = require('child_process').execSync('which ldd').toString().trim()
|
|
21
|
+
return readFileSync(lddPath, 'utf8').includes('musl')
|
|
22
|
+
} catch (e) {
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
const { glibcVersionRuntime } = process.report.getReport().header
|
|
27
|
+
return !glibcVersionRuntime
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
switch (platform) {
|
|
32
|
+
case 'android':
|
|
33
|
+
switch (arch) {
|
|
34
|
+
case 'arm64':
|
|
35
|
+
localFileExisted = existsSync(join(__dirname, 'spiking-native.android-arm64.node'))
|
|
36
|
+
try {
|
|
37
|
+
if (localFileExisted) {
|
|
38
|
+
nativeBinding = require('./spiking-native.android-arm64.node')
|
|
39
|
+
} else {
|
|
40
|
+
nativeBinding = require('@oxide-js/spiking-android-arm64')
|
|
41
|
+
}
|
|
42
|
+
} catch (e) {
|
|
43
|
+
loadError = e
|
|
44
|
+
}
|
|
45
|
+
break
|
|
46
|
+
case 'arm':
|
|
47
|
+
localFileExisted = existsSync(join(__dirname, 'spiking-native.android-arm-eabi.node'))
|
|
48
|
+
try {
|
|
49
|
+
if (localFileExisted) {
|
|
50
|
+
nativeBinding = require('./spiking-native.android-arm-eabi.node')
|
|
51
|
+
} else {
|
|
52
|
+
nativeBinding = require('@oxide-js/spiking-android-arm-eabi')
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
loadError = e
|
|
56
|
+
}
|
|
57
|
+
break
|
|
58
|
+
default:
|
|
59
|
+
throw new Error(`Unsupported architecture on Android ${arch}`)
|
|
60
|
+
}
|
|
61
|
+
break
|
|
62
|
+
case 'win32':
|
|
63
|
+
switch (arch) {
|
|
64
|
+
case 'x64':
|
|
65
|
+
localFileExisted = existsSync(
|
|
66
|
+
join(__dirname, 'spiking-native.win32-x64-msvc.node')
|
|
67
|
+
)
|
|
68
|
+
try {
|
|
69
|
+
if (localFileExisted) {
|
|
70
|
+
nativeBinding = require('./spiking-native.win32-x64-msvc.node')
|
|
71
|
+
} else {
|
|
72
|
+
nativeBinding = require('@oxide-js/spiking-win32-x64-msvc')
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
loadError = e
|
|
76
|
+
}
|
|
77
|
+
break
|
|
78
|
+
case 'ia32':
|
|
79
|
+
localFileExisted = existsSync(
|
|
80
|
+
join(__dirname, 'spiking-native.win32-ia32-msvc.node')
|
|
81
|
+
)
|
|
82
|
+
try {
|
|
83
|
+
if (localFileExisted) {
|
|
84
|
+
nativeBinding = require('./spiking-native.win32-ia32-msvc.node')
|
|
85
|
+
} else {
|
|
86
|
+
nativeBinding = require('@oxide-js/spiking-win32-ia32-msvc')
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
loadError = e
|
|
90
|
+
}
|
|
91
|
+
break
|
|
92
|
+
case 'arm64':
|
|
93
|
+
localFileExisted = existsSync(
|
|
94
|
+
join(__dirname, 'spiking-native.win32-arm64-msvc.node')
|
|
95
|
+
)
|
|
96
|
+
try {
|
|
97
|
+
if (localFileExisted) {
|
|
98
|
+
nativeBinding = require('./spiking-native.win32-arm64-msvc.node')
|
|
99
|
+
} else {
|
|
100
|
+
nativeBinding = require('@oxide-js/spiking-win32-arm64-msvc')
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
loadError = e
|
|
104
|
+
}
|
|
105
|
+
break
|
|
106
|
+
default:
|
|
107
|
+
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
|
108
|
+
}
|
|
109
|
+
break
|
|
110
|
+
case 'darwin':
|
|
111
|
+
localFileExisted = existsSync(join(__dirname, 'spiking-native.darwin-universal.node'))
|
|
112
|
+
try {
|
|
113
|
+
if (localFileExisted) {
|
|
114
|
+
nativeBinding = require('./spiking-native.darwin-universal.node')
|
|
115
|
+
} else {
|
|
116
|
+
nativeBinding = require('@oxide-js/spiking-darwin-universal')
|
|
117
|
+
}
|
|
118
|
+
break
|
|
119
|
+
} catch {}
|
|
120
|
+
switch (arch) {
|
|
121
|
+
case 'x64':
|
|
122
|
+
localFileExisted = existsSync(join(__dirname, 'spiking-native.darwin-x64.node'))
|
|
123
|
+
try {
|
|
124
|
+
if (localFileExisted) {
|
|
125
|
+
nativeBinding = require('./spiking-native.darwin-x64.node')
|
|
126
|
+
} else {
|
|
127
|
+
nativeBinding = require('@oxide-js/spiking-darwin-x64')
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {
|
|
130
|
+
loadError = e
|
|
131
|
+
}
|
|
132
|
+
break
|
|
133
|
+
case 'arm64':
|
|
134
|
+
localFileExisted = existsSync(
|
|
135
|
+
join(__dirname, 'spiking-native.darwin-arm64.node')
|
|
136
|
+
)
|
|
137
|
+
try {
|
|
138
|
+
if (localFileExisted) {
|
|
139
|
+
nativeBinding = require('./spiking-native.darwin-arm64.node')
|
|
140
|
+
} else {
|
|
141
|
+
nativeBinding = require('@oxide-js/spiking-darwin-arm64')
|
|
142
|
+
}
|
|
143
|
+
} catch (e) {
|
|
144
|
+
loadError = e
|
|
145
|
+
}
|
|
146
|
+
break
|
|
147
|
+
default:
|
|
148
|
+
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
|
149
|
+
}
|
|
150
|
+
break
|
|
151
|
+
case 'freebsd':
|
|
152
|
+
if (arch !== 'x64') {
|
|
153
|
+
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
|
|
154
|
+
}
|
|
155
|
+
localFileExisted = existsSync(join(__dirname, 'spiking-native.freebsd-x64.node'))
|
|
156
|
+
try {
|
|
157
|
+
if (localFileExisted) {
|
|
158
|
+
nativeBinding = require('./spiking-native.freebsd-x64.node')
|
|
159
|
+
} else {
|
|
160
|
+
nativeBinding = require('@oxide-js/spiking-freebsd-x64')
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
loadError = e
|
|
164
|
+
}
|
|
165
|
+
break
|
|
166
|
+
case 'linux':
|
|
167
|
+
switch (arch) {
|
|
168
|
+
case 'x64':
|
|
169
|
+
if (isMusl()) {
|
|
170
|
+
localFileExisted = existsSync(
|
|
171
|
+
join(__dirname, 'spiking-native.linux-x64-musl.node')
|
|
172
|
+
)
|
|
173
|
+
try {
|
|
174
|
+
if (localFileExisted) {
|
|
175
|
+
nativeBinding = require('./spiking-native.linux-x64-musl.node')
|
|
176
|
+
} else {
|
|
177
|
+
nativeBinding = require('@oxide-js/spiking-linux-x64-musl')
|
|
178
|
+
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
loadError = e
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
localFileExisted = existsSync(
|
|
184
|
+
join(__dirname, 'spiking-native.linux-x64-gnu.node')
|
|
185
|
+
)
|
|
186
|
+
try {
|
|
187
|
+
if (localFileExisted) {
|
|
188
|
+
nativeBinding = require('./spiking-native.linux-x64-gnu.node')
|
|
189
|
+
} else {
|
|
190
|
+
nativeBinding = require('@oxide-js/spiking-linux-x64-gnu')
|
|
191
|
+
}
|
|
192
|
+
} catch (e) {
|
|
193
|
+
loadError = e
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
break
|
|
197
|
+
case 'arm64':
|
|
198
|
+
if (isMusl()) {
|
|
199
|
+
localFileExisted = existsSync(
|
|
200
|
+
join(__dirname, 'spiking-native.linux-arm64-musl.node')
|
|
201
|
+
)
|
|
202
|
+
try {
|
|
203
|
+
if (localFileExisted) {
|
|
204
|
+
nativeBinding = require('./spiking-native.linux-arm64-musl.node')
|
|
205
|
+
} else {
|
|
206
|
+
nativeBinding = require('@oxide-js/spiking-linux-arm64-musl')
|
|
207
|
+
}
|
|
208
|
+
} catch (e) {
|
|
209
|
+
loadError = e
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
localFileExisted = existsSync(
|
|
213
|
+
join(__dirname, 'spiking-native.linux-arm64-gnu.node')
|
|
214
|
+
)
|
|
215
|
+
try {
|
|
216
|
+
if (localFileExisted) {
|
|
217
|
+
nativeBinding = require('./spiking-native.linux-arm64-gnu.node')
|
|
218
|
+
} else {
|
|
219
|
+
nativeBinding = require('@oxide-js/spiking-linux-arm64-gnu')
|
|
220
|
+
}
|
|
221
|
+
} catch (e) {
|
|
222
|
+
loadError = e
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
break
|
|
226
|
+
case 'arm':
|
|
227
|
+
if (isMusl()) {
|
|
228
|
+
localFileExisted = existsSync(
|
|
229
|
+
join(__dirname, 'spiking-native.linux-arm-musleabihf.node')
|
|
230
|
+
)
|
|
231
|
+
try {
|
|
232
|
+
if (localFileExisted) {
|
|
233
|
+
nativeBinding = require('./spiking-native.linux-arm-musleabihf.node')
|
|
234
|
+
} else {
|
|
235
|
+
nativeBinding = require('@oxide-js/spiking-linux-arm-musleabihf')
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
loadError = e
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
localFileExisted = existsSync(
|
|
242
|
+
join(__dirname, 'spiking-native.linux-arm-gnueabihf.node')
|
|
243
|
+
)
|
|
244
|
+
try {
|
|
245
|
+
if (localFileExisted) {
|
|
246
|
+
nativeBinding = require('./spiking-native.linux-arm-gnueabihf.node')
|
|
247
|
+
} else {
|
|
248
|
+
nativeBinding = require('@oxide-js/spiking-linux-arm-gnueabihf')
|
|
249
|
+
}
|
|
250
|
+
} catch (e) {
|
|
251
|
+
loadError = e
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
break
|
|
255
|
+
case 'riscv64':
|
|
256
|
+
if (isMusl()) {
|
|
257
|
+
localFileExisted = existsSync(
|
|
258
|
+
join(__dirname, 'spiking-native.linux-riscv64-musl.node')
|
|
259
|
+
)
|
|
260
|
+
try {
|
|
261
|
+
if (localFileExisted) {
|
|
262
|
+
nativeBinding = require('./spiking-native.linux-riscv64-musl.node')
|
|
263
|
+
} else {
|
|
264
|
+
nativeBinding = require('@oxide-js/spiking-linux-riscv64-musl')
|
|
265
|
+
}
|
|
266
|
+
} catch (e) {
|
|
267
|
+
loadError = e
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
localFileExisted = existsSync(
|
|
271
|
+
join(__dirname, 'spiking-native.linux-riscv64-gnu.node')
|
|
272
|
+
)
|
|
273
|
+
try {
|
|
274
|
+
if (localFileExisted) {
|
|
275
|
+
nativeBinding = require('./spiking-native.linux-riscv64-gnu.node')
|
|
276
|
+
} else {
|
|
277
|
+
nativeBinding = require('@oxide-js/spiking-linux-riscv64-gnu')
|
|
278
|
+
}
|
|
279
|
+
} catch (e) {
|
|
280
|
+
loadError = e
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
break
|
|
284
|
+
case 's390x':
|
|
285
|
+
localFileExisted = existsSync(
|
|
286
|
+
join(__dirname, 'spiking-native.linux-s390x-gnu.node')
|
|
287
|
+
)
|
|
288
|
+
try {
|
|
289
|
+
if (localFileExisted) {
|
|
290
|
+
nativeBinding = require('./spiking-native.linux-s390x-gnu.node')
|
|
291
|
+
} else {
|
|
292
|
+
nativeBinding = require('@oxide-js/spiking-linux-s390x-gnu')
|
|
293
|
+
}
|
|
294
|
+
} catch (e) {
|
|
295
|
+
loadError = e
|
|
296
|
+
}
|
|
297
|
+
break
|
|
298
|
+
default:
|
|
299
|
+
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
|
300
|
+
}
|
|
301
|
+
break
|
|
302
|
+
default:
|
|
303
|
+
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!nativeBinding) {
|
|
307
|
+
if (loadError) {
|
|
308
|
+
throw loadError
|
|
309
|
+
}
|
|
310
|
+
throw new Error(`Failed to load native binding`)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const { NativeSpikingNetwork, dotProductAddOnlyNative, lifStepNative, maskSurrogateNative, applyAddOnlyDeltaNative, learnHebbianNative, supervisedStdpNative, learnContrastiveBatchNative } = nativeBinding
|
|
314
|
+
|
|
315
|
+
module.exports.NativeSpikingNetwork = NativeSpikingNetwork
|
|
316
|
+
module.exports.dotProductAddOnlyNative = dotProductAddOnlyNative
|
|
317
|
+
module.exports.lifStepNative = lifStepNative
|
|
318
|
+
module.exports.maskSurrogateNative = maskSurrogateNative
|
|
319
|
+
module.exports.applyAddOnlyDeltaNative = applyAddOnlyDeltaNative
|
|
320
|
+
module.exports.learnHebbianNative = learnHebbianNative
|
|
321
|
+
module.exports.supervisedStdpNative = supervisedStdpNative
|
|
322
|
+
module.exports.learnContrastiveBatchNative = learnContrastiveBatchNative
|
package/index.d.ts
CHANGED
|
@@ -4,16 +4,8 @@
|
|
|
4
4
|
/* auto-generated by NAPI-RS */
|
|
5
5
|
|
|
6
6
|
export declare function dotProductAddOnlyNative(aData: Float32Array, aRowsOrig: number, aColsOrig: number, bData: Float32Array, bRowsOrig: number, bColsOrig: number, transA: boolean, transB: boolean, outData: Float32Array): void
|
|
7
|
-
export declare
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
step(): void
|
|
13
|
-
resetState(): void
|
|
14
|
-
getSpikes(): Array<number>
|
|
15
|
-
getPotentials(): Array<number>
|
|
16
|
-
updateStdp(learningRate: number, tauPlus: number, tauMinus: number, aPlus: number, aMinus: number, wMax: number, wMin: number): void
|
|
17
|
-
saveToFile(filepath: string): void
|
|
18
|
-
loadFromFile(filepath: string): void
|
|
19
|
-
}
|
|
7
|
+
export declare function lifStepNative(potentials: Float32Array, dot: Float32Array, spikes: Float32Array, lastPotentials: Float32Array, beta: Float32Array, threshold: Float32Array): void
|
|
8
|
+
export declare function maskSurrogateNative(errorSignal: Float32Array, potentials: Float32Array, threshold: Float32Array, windowSize: number): void
|
|
9
|
+
export declare function applyAddOnlyDeltaNative(kernel: Float32Array, bias: Float32Array, inputs: Float32Array, errorSignal: Float32Array, learningRate: number, batch: number, inFeatures: number, units: number, useBias: boolean): void
|
|
10
|
+
export declare function applyEmbeddingDeltaNative(embeddings: Float32Array, inputs: Float32Array, errorSignal: Float32Array, learningRate: number, inputDim: number, outputDim: number): void
|
|
11
|
+
export declare function contrastiveHebbianNative(spikes: Float32Array, errData: Float32Array, numPairs: number, sequenceLength: number, dModel: number): number
|
package/index.js
CHANGED
|
@@ -310,7 +310,11 @@ if (!nativeBinding) {
|
|
|
310
310
|
throw new Error(`Failed to load native binding`)
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
const {
|
|
313
|
+
const { dotProductAddOnlyNative, lifStepNative, maskSurrogateNative, applyAddOnlyDeltaNative, applyEmbeddingDeltaNative, contrastiveHebbianNative } = nativeBinding
|
|
314
314
|
|
|
315
|
-
module.exports.NativeSpikingNetwork = NativeSpikingNetwork
|
|
316
315
|
module.exports.dotProductAddOnlyNative = dotProductAddOnlyNative
|
|
316
|
+
module.exports.lifStepNative = lifStepNative
|
|
317
|
+
module.exports.maskSurrogateNative = maskSurrogateNative
|
|
318
|
+
module.exports.applyAddOnlyDeltaNative = applyAddOnlyDeltaNative
|
|
319
|
+
module.exports.applyEmbeddingDeltaNative = applyEmbeddingDeltaNative
|
|
320
|
+
module.exports.contrastiveHebbianNative = contrastiveHebbianNative
|
package/package.json
CHANGED
|
Binary file
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
1
|
export { default as dotProductAddOnly } from "./math/dotProductAddOnly.js";
|
|
3
2
|
export { SpikingDense, type SpikingDenseConfig } from "./layers/SpikingDense.js";
|
|
3
|
+
export { SpikingDenseBPTT, type SpikingDenseBPTTConfig } from "./layers/SpikingDenseBPTT.js";
|
|
4
4
|
export { SpikingEmbedding, type SpikingEmbeddingConfig } from "./layers/SpikingEmbedding.js";
|
|
5
|
-
export {
|
|
5
|
+
export { SpikingSelfAttention, type SpikingSelfAttentionConfig } from "./layers/SpikingSelfAttention.js";
|
|
6
|
+
// export { SpikingSentenceEmbedder, type SpikingSentenceConfig } from "./models/SpikingSentenceEmbedder.js";
|
|
7
|
+
export { contrastiveHebbianNativeWrapper, isNativeAvailable } from "./native_backend.js";
|
|
@@ -12,8 +12,8 @@ export interface SpikingDenseConfig extends LayerConfig {
|
|
|
12
12
|
useBias?: boolean;
|
|
13
13
|
kernelInitializer?: string;
|
|
14
14
|
biasInitializer?: string;
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
betaRange?: [number, number];
|
|
16
|
+
thresholdRange?: [number, number];
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export class SpikingDense extends BaseLayer {
|
|
@@ -21,8 +21,10 @@ export class SpikingDense extends BaseLayer {
|
|
|
21
21
|
public useBias: boolean;
|
|
22
22
|
public kernelInitializer: string;
|
|
23
23
|
public biasInitializer: string;
|
|
24
|
-
public
|
|
25
|
-
public
|
|
24
|
+
public betaRange: [number, number];
|
|
25
|
+
public thresholdRange: [number, number];
|
|
26
|
+
public beta!: Float32Array;
|
|
27
|
+
public threshold!: Float32Array;
|
|
26
28
|
|
|
27
29
|
public potentials!: Matrix;
|
|
28
30
|
public lastPotentials?: Matrix;
|
|
@@ -43,8 +45,8 @@ export class SpikingDense extends BaseLayer {
|
|
|
43
45
|
this.useBias = config.useBias ?? true;
|
|
44
46
|
this.kernelInitializer = config.kernelInitializer || "glorot_normal";
|
|
45
47
|
this.biasInitializer = config.biasInitializer || "zeros";
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
48
|
+
this.betaRange = config.betaRange || [0.8, 0.99];
|
|
49
|
+
this.thresholdRange = config.thresholdRange || [0.5, 1.0];
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
public computeOutputShape(inputShape: number[]): number[] {
|
|
@@ -65,19 +67,31 @@ export class SpikingDense extends BaseLayer {
|
|
|
65
67
|
this.addParameter("bias", biasVal, true, [this.units, 1]);
|
|
66
68
|
}
|
|
67
69
|
|
|
70
|
+
// Inisialisasi beta dan threshold secara acak untuk setiap neuron
|
|
71
|
+
this.beta = new Float32Array(this.units);
|
|
72
|
+
this.threshold = new Float32Array(this.units);
|
|
73
|
+
for (let i = 0; i < this.units; i++) {
|
|
74
|
+
this.beta[i] = this.betaRange[0] + Math.random() * (this.betaRange[1] - this.betaRange[0]);
|
|
75
|
+
this.threshold[i] = this.thresholdRange[0] + Math.random() * (this.thresholdRange[1] - this.thresholdRange[0]);
|
|
76
|
+
}
|
|
77
|
+
|
|
68
78
|
// Inisialisasi state
|
|
69
79
|
this.potentials = Matrix.fromFlat(new Float32Array(this.units), [1, this.units]);
|
|
70
80
|
}
|
|
71
81
|
|
|
82
|
+
private outSpikesDataBuffer?: Float32Array;
|
|
83
|
+
|
|
72
84
|
private ensurePotentialsShape(batch: number) {
|
|
73
|
-
if (this.potentials._shape[0] !== batch) {
|
|
85
|
+
if (this.potentials._shape[0] !== batch || !this.outSpikesDataBuffer) {
|
|
74
86
|
this.potentials = Matrix.fromFlat(new Float32Array(batch * this.units), [batch, this.units]);
|
|
87
|
+
this.outSpikesDataBuffer = new Float32Array(batch * this.units);
|
|
88
|
+
this.lastPotentials = Matrix.fromFlat(new Float32Array(batch * this.units), [batch, this.units]);
|
|
75
89
|
}
|
|
76
90
|
}
|
|
77
91
|
|
|
78
92
|
public resetState() {
|
|
79
93
|
if (this.potentials) this.potentials._data.fill(0);
|
|
80
|
-
this.lastPotentials
|
|
94
|
+
if (this.lastPotentials) this.lastPotentials._data.fill(0);
|
|
81
95
|
this.lastInputs = undefined;
|
|
82
96
|
this.lastSpikes = undefined;
|
|
83
97
|
}
|
|
@@ -96,35 +110,40 @@ export class SpikingDense extends BaseLayer {
|
|
|
96
110
|
}
|
|
97
111
|
|
|
98
112
|
// 3 & 4. Leaky Integrate, Fire & Reset
|
|
99
|
-
const outData =
|
|
113
|
+
const outData = this.outSpikesDataBuffer!;
|
|
114
|
+
outData.fill(0);
|
|
100
115
|
const outSpikes = Matrix.fromFlat(outData, [batch, this.units]);
|
|
101
|
-
this.lastPotentials = Matrix.fromFlat(new Float32Array(batch * this.units), [batch, this.units]);
|
|
102
116
|
|
|
103
117
|
if (isNativeAvailable()) {
|
|
104
118
|
lifStepNativeWrapper(
|
|
105
119
|
this.potentials._data,
|
|
106
120
|
dot._data,
|
|
107
121
|
outSpikes._data,
|
|
108
|
-
this.lastPotentials
|
|
122
|
+
this.lastPotentials!._data,
|
|
109
123
|
this.beta,
|
|
110
124
|
this.threshold
|
|
111
125
|
);
|
|
112
126
|
} else {
|
|
113
127
|
const potData = this.potentials._data;
|
|
114
128
|
const dotData = dot._data;
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
for (let
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
const lpData = this.lastPotentials!._data;
|
|
130
|
+
|
|
131
|
+
for (let b = 0; b < batch; b++) {
|
|
132
|
+
const offset = b * this.units;
|
|
133
|
+
for (let i = 0; i < this.units; i++) {
|
|
134
|
+
const idx = offset + i;
|
|
135
|
+
potData[idx] = Math.min((potData[idx] * this.beta[i]) + dotData[idx], 1.0); // Clamp potential max 1.0
|
|
136
|
+
lpData[idx] = potData[idx];
|
|
137
|
+
}
|
|
138
|
+
for (let i = 0; i < this.units; i++) {
|
|
139
|
+
const idx = offset + i;
|
|
140
|
+
if (potData[idx] >= this.threshold[i]) {
|
|
141
|
+
outData[idx] = 1;
|
|
142
|
+
potData[idx] -= this.threshold[i];
|
|
143
|
+
} else {
|
|
144
|
+
outData[idx] = 0;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
128
147
|
}
|
|
129
148
|
}
|
|
130
149
|
|
|
@@ -146,22 +165,26 @@ export class SpikingDense extends BaseLayer {
|
|
|
146
165
|
|
|
147
166
|
// Surrogate Mask: Boxcar (Murni Add-Only mask, tanpa perkalian float!)
|
|
148
167
|
if (this.lastPotentials) {
|
|
168
|
+
const eData = eHidden._data;
|
|
169
|
+
const pData = this.lastPotentials._data;
|
|
170
|
+
const windowSize = 1.0;
|
|
171
|
+
|
|
149
172
|
if (isNativeAvailable()) {
|
|
150
173
|
maskSurrogateNativeWrapper(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this.threshold,
|
|
154
|
-
|
|
174
|
+
eData,
|
|
175
|
+
pData,
|
|
176
|
+
this.threshold,
|
|
177
|
+
windowSize
|
|
155
178
|
);
|
|
156
179
|
} else {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
180
|
+
const batch = eHidden._shape[0];
|
|
181
|
+
for (let b = 0; b < batch; b++) {
|
|
182
|
+
const offset = b * this.units;
|
|
183
|
+
for (let i = 0; i < this.units; i++) {
|
|
184
|
+
const idx = offset + i;
|
|
185
|
+
if (Math.abs(pData[idx] - this.threshold[i]) > windowSize) {
|
|
186
|
+
eData[idx] = 0;
|
|
187
|
+
}
|
|
165
188
|
}
|
|
166
189
|
}
|
|
167
190
|
}
|
|
@@ -197,6 +220,12 @@ export class SpikingDense extends BaseLayer {
|
|
|
197
220
|
units,
|
|
198
221
|
this.useBias
|
|
199
222
|
);
|
|
223
|
+
|
|
224
|
+
for(let i = 0; i < kernel.length; i++) kernel[i] = Math.max(-1.0, Math.min(1.0, kernel[i]));
|
|
225
|
+
if (this.useBias && this.bias) {
|
|
226
|
+
const biasData = this.bias._data;
|
|
227
|
+
for(let i = 0; i < biasData.length; i++) biasData[i] = Math.max(-1.0, Math.min(1.0, biasData[i]));
|
|
228
|
+
}
|
|
200
229
|
} else {
|
|
201
230
|
// Delta rule add-only
|
|
202
231
|
for (let b = 0; b < batch; b++) {
|
|
@@ -204,11 +233,12 @@ export class SpikingDense extends BaseLayer {
|
|
|
204
233
|
const errOffset = b * units;
|
|
205
234
|
|
|
206
235
|
for (let k = 0; k < inFeatures; k++) {
|
|
207
|
-
// HANYA update jika input menyala (Spike
|
|
208
|
-
if (inputs[inOffset + k]
|
|
236
|
+
// HANYA update jika input menyala (Spike > 0.5) -> Add Only Update!
|
|
237
|
+
if (inputs[inOffset + k] > 0.5) {
|
|
209
238
|
const kOffset = k * units;
|
|
210
239
|
for (let j = 0; j < units; j++) {
|
|
211
240
|
kernel[kOffset + j] += learningRate * err[errOffset + j];
|
|
241
|
+
kernel[kOffset + j] = Math.max(-1.0, Math.min(1.0, kernel[kOffset + j]));
|
|
212
242
|
}
|
|
213
243
|
}
|
|
214
244
|
}
|
|
@@ -216,7 +246,8 @@ export class SpikingDense extends BaseLayer {
|
|
|
216
246
|
if (this.useBias && this.bias) {
|
|
217
247
|
const biasData = this.bias._data;
|
|
218
248
|
for (let j = 0; j < units; j++) {
|
|
219
|
-
biasData[j] += learningRate * err[errOffset + j];
|
|
249
|
+
biasData[j] += (learningRate * err[errOffset + j]) / batch;
|
|
250
|
+
biasData[j] = Math.max(-1.0, Math.min(1.0, biasData[j]));
|
|
220
251
|
}
|
|
221
252
|
}
|
|
222
253
|
}
|
|
@@ -229,9 +260,7 @@ export class SpikingDense extends BaseLayer {
|
|
|
229
260
|
units: this.units,
|
|
230
261
|
useBias: this.useBias,
|
|
231
262
|
kernelInitializer: this.kernelInitializer,
|
|
232
|
-
biasInitializer: this.biasInitializer
|
|
233
|
-
beta: this.beta,
|
|
234
|
-
threshold: this.threshold
|
|
263
|
+
biasInitializer: this.biasInitializer
|
|
235
264
|
};
|
|
236
265
|
}
|
|
237
266
|
}
|