@dniskav/neuron 0.1.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/README.md +120 -0
- package/dist/index.d.mts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +177 -0
- package/dist/index.mjs +146 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @dniskav/neuron
|
|
2
|
+
|
|
3
|
+
A minimal, dependency-free neural network library built from scratch in TypeScript. Designed for learning and experimentation — every line of math is readable.
|
|
4
|
+
|
|
5
|
+
## What's inside
|
|
6
|
+
|
|
7
|
+
| Class | Description |
|
|
8
|
+
|-------|-------------|
|
|
9
|
+
| `Neuron` | Single-input neuron. The simplest possible unit: one weight, one bias. |
|
|
10
|
+
| `NeuronN` | N-input neuron with Xavier initialization and sigmoid activation. |
|
|
11
|
+
| `Layer` | A group of `NeuronN` neurons that share the same inputs. |
|
|
12
|
+
| `Network` | Two-layer network (hidden + output) with backpropagation. |
|
|
13
|
+
| `NetworkN` | Deep network of arbitrary depth. Define your architecture as `[inputs, ...hidden, outputs]`. |
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @dniskav/neuron
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Single neuron — learn a threshold
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { Neuron } from "@dniskav/neuron";
|
|
27
|
+
|
|
28
|
+
const neuron = new Neuron();
|
|
29
|
+
|
|
30
|
+
// Train: output 1 if input >= 18, else 0
|
|
31
|
+
for (let epoch = 0; epoch < 1000; epoch++) {
|
|
32
|
+
neuron.train(20, 1, 0.1); // adult
|
|
33
|
+
neuron.train(15, 0, 0.1); // minor
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(neuron.predict(17)); // ~0.1 (minor)
|
|
37
|
+
console.log(neuron.predict(25)); // ~0.9 (adult)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### N-input neuron — multi-feature classification
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { NeuronN } from "@dniskav/neuron";
|
|
44
|
+
|
|
45
|
+
const neuron = new NeuronN(3); // 3 inputs: R, G, B
|
|
46
|
+
|
|
47
|
+
// Teach it to detect bright colors (luminance > 0.65)
|
|
48
|
+
neuron.train([1, 1, 1], 1, 0.05); // white → bright
|
|
49
|
+
neuron.train([0, 0, 0], 0, 0.05); // black → dark
|
|
50
|
+
|
|
51
|
+
console.log(neuron.predict([0.9, 0.9, 0.9])); // close to 1
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Network — non-linear classification
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { Network } from "@dniskav/neuron";
|
|
58
|
+
|
|
59
|
+
// 2 inputs → 8 hidden neurons → 1 output
|
|
60
|
+
const net = new Network(2, 8, 1);
|
|
61
|
+
|
|
62
|
+
// Train on XOR (not linearly separable — needs hidden layer)
|
|
63
|
+
const data = [[0,0,0], [0,1,1], [1,0,1], [1,1,0]];
|
|
64
|
+
|
|
65
|
+
for (let epoch = 0; epoch < 5000; epoch++) {
|
|
66
|
+
for (const [x, y, t] of data) {
|
|
67
|
+
net.train([x, y], t, 0.3);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(net.predict([0, 1])); // ~0.97
|
|
72
|
+
console.log(net.predict([1, 1])); // ~0.03
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### NetworkN — deep network with custom architecture
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { NetworkN } from "@dniskav/neuron";
|
|
79
|
+
|
|
80
|
+
// 3 inputs → 24 hidden → 16 hidden → 2 outputs
|
|
81
|
+
const net = new NetworkN([3, 24, 16, 2]);
|
|
82
|
+
|
|
83
|
+
// Train with multiple targets
|
|
84
|
+
net.train([0.5, 0.3, 0.8], [1, 0], 0.05);
|
|
85
|
+
|
|
86
|
+
// Predict returns an array — one value per output neuron
|
|
87
|
+
const [out1, out2] = net.predict([0.5, 0.3, 0.8]);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### trainWithDeltas — custom loss / physics-based gradients
|
|
91
|
+
|
|
92
|
+
`NetworkN` also exposes `trainWithDeltas` for when you compute your own output-layer deltas (e.g., from a physics simulation or a custom loss function):
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
net.trainWithDeltas(inputs, [0.4, -0.2], 0.05);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## How it works
|
|
99
|
+
|
|
100
|
+
Every class uses **sigmoid** as its activation function and **gradient descent** to update weights:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
weight += lr × error × input
|
|
104
|
+
bias += lr × error
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`NetworkN` implements full **backpropagation** across all layers, propagating deltas from the output back to the first layer using the chain rule.
|
|
108
|
+
|
|
109
|
+
`NeuronN` uses simplified **Xavier initialization** — weights start in `[-√(1/n), +√(1/n)]` — so gradients flow well from the start of training.
|
|
110
|
+
|
|
111
|
+
## Build
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm run build # outputs CJS + ESM + type declarations to dist/
|
|
115
|
+
npm run dev # watch mode
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
declare class Neuron {
|
|
2
|
+
weight: number;
|
|
3
|
+
bias: number;
|
|
4
|
+
constructor();
|
|
5
|
+
predict(input: number): number;
|
|
6
|
+
train(input: number, target: number, lr: number): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare class NeuronN {
|
|
10
|
+
weights: number[];
|
|
11
|
+
bias: number;
|
|
12
|
+
constructor(nInputs: number);
|
|
13
|
+
predict(inputs: number[]): number;
|
|
14
|
+
train(inputs: number[], target: number, lr: number): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare class Layer {
|
|
18
|
+
neurons: NeuronN[];
|
|
19
|
+
constructor(nNeurons: number, nInputs: number);
|
|
20
|
+
predict(inputs: number[]): number[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare class Network {
|
|
24
|
+
hiddenLayer: Layer;
|
|
25
|
+
outputLayer: Layer;
|
|
26
|
+
constructor(nInputs: number, nHidden: number, nOutputs: number);
|
|
27
|
+
predict(inputs: number[]): number;
|
|
28
|
+
train(inputs: number[], target: number, lr: number): number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare class NetworkN {
|
|
32
|
+
readonly structure: number[];
|
|
33
|
+
layers: Layer[];
|
|
34
|
+
constructor(structure: number[]);
|
|
35
|
+
predict(inputs: number[]): number[];
|
|
36
|
+
train(inputs: number[], targets: number[], lr: number): number;
|
|
37
|
+
trainWithDeltas(inputs: number[], outputDeltas: number[], lr: number): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { Layer, Network, NetworkN, Neuron, NeuronN };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
declare class Neuron {
|
|
2
|
+
weight: number;
|
|
3
|
+
bias: number;
|
|
4
|
+
constructor();
|
|
5
|
+
predict(input: number): number;
|
|
6
|
+
train(input: number, target: number, lr: number): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare class NeuronN {
|
|
10
|
+
weights: number[];
|
|
11
|
+
bias: number;
|
|
12
|
+
constructor(nInputs: number);
|
|
13
|
+
predict(inputs: number[]): number;
|
|
14
|
+
train(inputs: number[], target: number, lr: number): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare class Layer {
|
|
18
|
+
neurons: NeuronN[];
|
|
19
|
+
constructor(nNeurons: number, nInputs: number);
|
|
20
|
+
predict(inputs: number[]): number[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare class Network {
|
|
24
|
+
hiddenLayer: Layer;
|
|
25
|
+
outputLayer: Layer;
|
|
26
|
+
constructor(nInputs: number, nHidden: number, nOutputs: number);
|
|
27
|
+
predict(inputs: number[]): number;
|
|
28
|
+
train(inputs: number[], target: number, lr: number): number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare class NetworkN {
|
|
32
|
+
readonly structure: number[];
|
|
33
|
+
layers: Layer[];
|
|
34
|
+
constructor(structure: number[]);
|
|
35
|
+
predict(inputs: number[]): number[];
|
|
36
|
+
train(inputs: number[], targets: number[], lr: number): number;
|
|
37
|
+
trainWithDeltas(inputs: number[], outputDeltas: number[], lr: number): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { Layer, Network, NetworkN, Neuron, NeuronN };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Layer: () => Layer,
|
|
24
|
+
Network: () => Network,
|
|
25
|
+
NetworkN: () => NetworkN,
|
|
26
|
+
Neuron: () => Neuron,
|
|
27
|
+
NeuronN: () => NeuronN
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/Neuron.ts
|
|
32
|
+
function sigmoid(x) {
|
|
33
|
+
return 1 / (1 + Math.exp(-x));
|
|
34
|
+
}
|
|
35
|
+
var Neuron = class {
|
|
36
|
+
constructor() {
|
|
37
|
+
this.weight = Math.random() * 0.1;
|
|
38
|
+
this.bias = Math.random() * 0.1;
|
|
39
|
+
}
|
|
40
|
+
predict(input) {
|
|
41
|
+
return sigmoid(input * this.weight + this.bias);
|
|
42
|
+
}
|
|
43
|
+
train(input, target, lr) {
|
|
44
|
+
const prediction = this.predict(input);
|
|
45
|
+
const error = target - prediction;
|
|
46
|
+
this.weight += lr * error * input;
|
|
47
|
+
this.bias += lr * error;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/NeuronN.ts
|
|
52
|
+
function sigmoid2(x) {
|
|
53
|
+
return 1 / (1 + Math.exp(-x));
|
|
54
|
+
}
|
|
55
|
+
var NeuronN = class {
|
|
56
|
+
constructor(nInputs) {
|
|
57
|
+
const limit = Math.sqrt(1 / nInputs);
|
|
58
|
+
this.weights = Array.from({ length: nInputs }, () => (Math.random() * 2 - 1) * limit);
|
|
59
|
+
this.bias = 0;
|
|
60
|
+
}
|
|
61
|
+
predict(inputs) {
|
|
62
|
+
const sum = inputs.reduce((acc, e, i) => acc + e * this.weights[i], this.bias);
|
|
63
|
+
return sigmoid2(sum);
|
|
64
|
+
}
|
|
65
|
+
train(inputs, target, lr) {
|
|
66
|
+
const prediction = this.predict(inputs);
|
|
67
|
+
const error = target - prediction;
|
|
68
|
+
this.weights = this.weights.map((w, i) => w + lr * error * inputs[i]);
|
|
69
|
+
this.bias += lr * error;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/Layer.ts
|
|
74
|
+
var Layer = class {
|
|
75
|
+
constructor(nNeurons, nInputs) {
|
|
76
|
+
this.neurons = Array.from({ length: nNeurons }, () => new NeuronN(nInputs));
|
|
77
|
+
}
|
|
78
|
+
predict(inputs) {
|
|
79
|
+
return this.neurons.map((n) => n.predict(inputs));
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// src/Network.ts
|
|
84
|
+
var Network = class {
|
|
85
|
+
constructor(nInputs, nHidden, nOutputs) {
|
|
86
|
+
this.hiddenLayer = new Layer(nHidden, nInputs);
|
|
87
|
+
this.outputLayer = new Layer(nOutputs, nHidden);
|
|
88
|
+
}
|
|
89
|
+
predict(inputs) {
|
|
90
|
+
const hiddenOut = this.hiddenLayer.predict(inputs);
|
|
91
|
+
return this.outputLayer.predict(hiddenOut)[0];
|
|
92
|
+
}
|
|
93
|
+
// Trains on a single example. Returns the squared error.
|
|
94
|
+
train(inputs, target, lr) {
|
|
95
|
+
const hiddenOut = this.hiddenLayer.predict(inputs);
|
|
96
|
+
const prediction = this.outputLayer.predict(hiddenOut)[0];
|
|
97
|
+
const outputError = target - prediction;
|
|
98
|
+
const outputDelta = outputError * prediction * (1 - prediction);
|
|
99
|
+
const outputNeuron = this.outputLayer.neurons[0];
|
|
100
|
+
outputNeuron.weights = outputNeuron.weights.map(
|
|
101
|
+
(w, i) => w + lr * outputDelta * hiddenOut[i]
|
|
102
|
+
);
|
|
103
|
+
outputNeuron.bias += lr * outputDelta;
|
|
104
|
+
this.hiddenLayer.neurons.forEach((neuron, i) => {
|
|
105
|
+
const hiddenOut_i = hiddenOut[i];
|
|
106
|
+
const hiddenError = outputDelta * outputNeuron.weights[i];
|
|
107
|
+
const hiddenDelta = hiddenError * hiddenOut_i * (1 - hiddenOut_i);
|
|
108
|
+
neuron.weights = neuron.weights.map((w, j) => w + lr * hiddenDelta * inputs[j]);
|
|
109
|
+
neuron.bias += lr * hiddenDelta;
|
|
110
|
+
});
|
|
111
|
+
return outputError * outputError;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// src/NetworkN.ts
|
|
116
|
+
var NetworkN = class {
|
|
117
|
+
constructor(structure) {
|
|
118
|
+
this.structure = structure;
|
|
119
|
+
this.layers = [];
|
|
120
|
+
for (let i = 1; i < structure.length; i++) {
|
|
121
|
+
this.layers.push(new Layer(structure[i], structure[i - 1]));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
predict(inputs) {
|
|
125
|
+
return this.layers.reduce((acc, layer) => layer.predict(acc), inputs);
|
|
126
|
+
}
|
|
127
|
+
// Generalized backpropagation across L layers.
|
|
128
|
+
// Returns the mean squared error for the example.
|
|
129
|
+
train(inputs, targets, lr) {
|
|
130
|
+
const act = [inputs];
|
|
131
|
+
for (const layer of this.layers) act.push(layer.predict(act[act.length - 1]));
|
|
132
|
+
const pred = act[act.length - 1];
|
|
133
|
+
let deltas = pred.map((p, i) => (targets[i] - p) * p * (1 - p));
|
|
134
|
+
for (let l = this.layers.length - 1; l >= 0; l--) {
|
|
135
|
+
const layer = this.layers[l];
|
|
136
|
+
const layerIn = act[l];
|
|
137
|
+
const prevDeltas = layerIn.map((out, j) => {
|
|
138
|
+
const errProp = layer.neurons.reduce((s, n, k) => s + deltas[k] * n.weights[j], 0);
|
|
139
|
+
return errProp * out * (1 - out);
|
|
140
|
+
});
|
|
141
|
+
layer.neurons.forEach((n, k) => {
|
|
142
|
+
n.weights = n.weights.map((w, j) => w + lr * deltas[k] * layerIn[j]);
|
|
143
|
+
n.bias += lr * deltas[k];
|
|
144
|
+
});
|
|
145
|
+
deltas = prevDeltas;
|
|
146
|
+
}
|
|
147
|
+
return pred.reduce((s, p, i) => s + (targets[i] - p) ** 2, 0) / pred.length;
|
|
148
|
+
}
|
|
149
|
+
// Backprop with externally provided output-layer deltas.
|
|
150
|
+
// Useful for custom loss functions (e.g. physics-based gradients).
|
|
151
|
+
trainWithDeltas(inputs, outputDeltas, lr) {
|
|
152
|
+
const act = [inputs];
|
|
153
|
+
for (const layer of this.layers) act.push(layer.predict(act[act.length - 1]));
|
|
154
|
+
let deltas = outputDeltas;
|
|
155
|
+
for (let l = this.layers.length - 1; l >= 0; l--) {
|
|
156
|
+
const layer = this.layers[l];
|
|
157
|
+
const layerIn = act[l];
|
|
158
|
+
const prevDeltas = layerIn.map((out, j) => {
|
|
159
|
+
const errProp = layer.neurons.reduce((s, n, k) => s + deltas[k] * n.weights[j], 0);
|
|
160
|
+
return errProp * out * (1 - out);
|
|
161
|
+
});
|
|
162
|
+
layer.neurons.forEach((n, k) => {
|
|
163
|
+
n.weights = n.weights.map((w, j) => w + lr * deltas[k] * layerIn[j]);
|
|
164
|
+
n.bias += lr * deltas[k];
|
|
165
|
+
});
|
|
166
|
+
deltas = prevDeltas;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
171
|
+
0 && (module.exports = {
|
|
172
|
+
Layer,
|
|
173
|
+
Network,
|
|
174
|
+
NetworkN,
|
|
175
|
+
Neuron,
|
|
176
|
+
NeuronN
|
|
177
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// src/Neuron.ts
|
|
2
|
+
function sigmoid(x) {
|
|
3
|
+
return 1 / (1 + Math.exp(-x));
|
|
4
|
+
}
|
|
5
|
+
var Neuron = class {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.weight = Math.random() * 0.1;
|
|
8
|
+
this.bias = Math.random() * 0.1;
|
|
9
|
+
}
|
|
10
|
+
predict(input) {
|
|
11
|
+
return sigmoid(input * this.weight + this.bias);
|
|
12
|
+
}
|
|
13
|
+
train(input, target, lr) {
|
|
14
|
+
const prediction = this.predict(input);
|
|
15
|
+
const error = target - prediction;
|
|
16
|
+
this.weight += lr * error * input;
|
|
17
|
+
this.bias += lr * error;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/NeuronN.ts
|
|
22
|
+
function sigmoid2(x) {
|
|
23
|
+
return 1 / (1 + Math.exp(-x));
|
|
24
|
+
}
|
|
25
|
+
var NeuronN = class {
|
|
26
|
+
constructor(nInputs) {
|
|
27
|
+
const limit = Math.sqrt(1 / nInputs);
|
|
28
|
+
this.weights = Array.from({ length: nInputs }, () => (Math.random() * 2 - 1) * limit);
|
|
29
|
+
this.bias = 0;
|
|
30
|
+
}
|
|
31
|
+
predict(inputs) {
|
|
32
|
+
const sum = inputs.reduce((acc, e, i) => acc + e * this.weights[i], this.bias);
|
|
33
|
+
return sigmoid2(sum);
|
|
34
|
+
}
|
|
35
|
+
train(inputs, target, lr) {
|
|
36
|
+
const prediction = this.predict(inputs);
|
|
37
|
+
const error = target - prediction;
|
|
38
|
+
this.weights = this.weights.map((w, i) => w + lr * error * inputs[i]);
|
|
39
|
+
this.bias += lr * error;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/Layer.ts
|
|
44
|
+
var Layer = class {
|
|
45
|
+
constructor(nNeurons, nInputs) {
|
|
46
|
+
this.neurons = Array.from({ length: nNeurons }, () => new NeuronN(nInputs));
|
|
47
|
+
}
|
|
48
|
+
predict(inputs) {
|
|
49
|
+
return this.neurons.map((n) => n.predict(inputs));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/Network.ts
|
|
54
|
+
var Network = class {
|
|
55
|
+
constructor(nInputs, nHidden, nOutputs) {
|
|
56
|
+
this.hiddenLayer = new Layer(nHidden, nInputs);
|
|
57
|
+
this.outputLayer = new Layer(nOutputs, nHidden);
|
|
58
|
+
}
|
|
59
|
+
predict(inputs) {
|
|
60
|
+
const hiddenOut = this.hiddenLayer.predict(inputs);
|
|
61
|
+
return this.outputLayer.predict(hiddenOut)[0];
|
|
62
|
+
}
|
|
63
|
+
// Trains on a single example. Returns the squared error.
|
|
64
|
+
train(inputs, target, lr) {
|
|
65
|
+
const hiddenOut = this.hiddenLayer.predict(inputs);
|
|
66
|
+
const prediction = this.outputLayer.predict(hiddenOut)[0];
|
|
67
|
+
const outputError = target - prediction;
|
|
68
|
+
const outputDelta = outputError * prediction * (1 - prediction);
|
|
69
|
+
const outputNeuron = this.outputLayer.neurons[0];
|
|
70
|
+
outputNeuron.weights = outputNeuron.weights.map(
|
|
71
|
+
(w, i) => w + lr * outputDelta * hiddenOut[i]
|
|
72
|
+
);
|
|
73
|
+
outputNeuron.bias += lr * outputDelta;
|
|
74
|
+
this.hiddenLayer.neurons.forEach((neuron, i) => {
|
|
75
|
+
const hiddenOut_i = hiddenOut[i];
|
|
76
|
+
const hiddenError = outputDelta * outputNeuron.weights[i];
|
|
77
|
+
const hiddenDelta = hiddenError * hiddenOut_i * (1 - hiddenOut_i);
|
|
78
|
+
neuron.weights = neuron.weights.map((w, j) => w + lr * hiddenDelta * inputs[j]);
|
|
79
|
+
neuron.bias += lr * hiddenDelta;
|
|
80
|
+
});
|
|
81
|
+
return outputError * outputError;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/NetworkN.ts
|
|
86
|
+
var NetworkN = class {
|
|
87
|
+
constructor(structure) {
|
|
88
|
+
this.structure = structure;
|
|
89
|
+
this.layers = [];
|
|
90
|
+
for (let i = 1; i < structure.length; i++) {
|
|
91
|
+
this.layers.push(new Layer(structure[i], structure[i - 1]));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
predict(inputs) {
|
|
95
|
+
return this.layers.reduce((acc, layer) => layer.predict(acc), inputs);
|
|
96
|
+
}
|
|
97
|
+
// Generalized backpropagation across L layers.
|
|
98
|
+
// Returns the mean squared error for the example.
|
|
99
|
+
train(inputs, targets, lr) {
|
|
100
|
+
const act = [inputs];
|
|
101
|
+
for (const layer of this.layers) act.push(layer.predict(act[act.length - 1]));
|
|
102
|
+
const pred = act[act.length - 1];
|
|
103
|
+
let deltas = pred.map((p, i) => (targets[i] - p) * p * (1 - p));
|
|
104
|
+
for (let l = this.layers.length - 1; l >= 0; l--) {
|
|
105
|
+
const layer = this.layers[l];
|
|
106
|
+
const layerIn = act[l];
|
|
107
|
+
const prevDeltas = layerIn.map((out, j) => {
|
|
108
|
+
const errProp = layer.neurons.reduce((s, n, k) => s + deltas[k] * n.weights[j], 0);
|
|
109
|
+
return errProp * out * (1 - out);
|
|
110
|
+
});
|
|
111
|
+
layer.neurons.forEach((n, k) => {
|
|
112
|
+
n.weights = n.weights.map((w, j) => w + lr * deltas[k] * layerIn[j]);
|
|
113
|
+
n.bias += lr * deltas[k];
|
|
114
|
+
});
|
|
115
|
+
deltas = prevDeltas;
|
|
116
|
+
}
|
|
117
|
+
return pred.reduce((s, p, i) => s + (targets[i] - p) ** 2, 0) / pred.length;
|
|
118
|
+
}
|
|
119
|
+
// Backprop with externally provided output-layer deltas.
|
|
120
|
+
// Useful for custom loss functions (e.g. physics-based gradients).
|
|
121
|
+
trainWithDeltas(inputs, outputDeltas, lr) {
|
|
122
|
+
const act = [inputs];
|
|
123
|
+
for (const layer of this.layers) act.push(layer.predict(act[act.length - 1]));
|
|
124
|
+
let deltas = outputDeltas;
|
|
125
|
+
for (let l = this.layers.length - 1; l >= 0; l--) {
|
|
126
|
+
const layer = this.layers[l];
|
|
127
|
+
const layerIn = act[l];
|
|
128
|
+
const prevDeltas = layerIn.map((out, j) => {
|
|
129
|
+
const errProp = layer.neurons.reduce((s, n, k) => s + deltas[k] * n.weights[j], 0);
|
|
130
|
+
return errProp * out * (1 - out);
|
|
131
|
+
});
|
|
132
|
+
layer.neurons.forEach((n, k) => {
|
|
133
|
+
n.weights = n.weights.map((w, j) => w + lr * deltas[k] * layerIn[j]);
|
|
134
|
+
n.bias += lr * deltas[k];
|
|
135
|
+
});
|
|
136
|
+
deltas = prevDeltas;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
export {
|
|
141
|
+
Layer,
|
|
142
|
+
Network,
|
|
143
|
+
NetworkN,
|
|
144
|
+
Neuron,
|
|
145
|
+
NeuronN
|
|
146
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dniskav/neuron",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Minimal neural network from scratch — neuron, layer, network, backpropagation. No dependencies.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist"],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
18
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch"
|
|
19
|
+
},
|
|
20
|
+
"keywords": ["neural-network", "machine-learning", "backpropagation", "typescript"],
|
|
21
|
+
"author": "dniskav",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"tsup": "^8.0.0",
|
|
25
|
+
"typescript": "^5.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|