scalar-autograd 0.1.3 → 0.1.5

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/Value.spec.ts DELETED
@@ -1,268 +0,0 @@
1
- import { Value } from "./Value";
2
-
3
- function numericalGrad(f: (x: number) => number, x0: number, eps = 1e-6): number {
4
- return (f(x0 + eps) - f(x0 - eps)) / (2 * eps);
5
- }
6
-
7
- function testUnaryGrad(opName: string, op: (x: Value) => Value, dOp: (x: number) => number, xval: number) {
8
- const x = new Value(xval, "x", true);
9
- const y = op(x);
10
- y.backward();
11
- const analytic = x.grad;
12
- const numeric = numericalGrad(z => op(new Value(z, "x", false)).data, xval);
13
- expect(analytic).toBeCloseTo(numeric, 4);
14
- }
15
-
16
- function testBinaryGrad(opName: string, op: (a: Value, b: Value) => Value, dOpA: (a: number, b: number) => number, dOpB: (a: number, b: number) => number, aval: number, bval: number) {
17
- const a = new Value(aval, "a", true);
18
- const b = new Value(bval, "b", true);
19
- const c = op(a, b);
20
- c.backward();
21
- const analyticA = a.grad;
22
- const analyticB = b.grad;
23
- const numericA = numericalGrad(x => op(new Value(x, "a", false), new Value(bval, "b", false)).data, aval);
24
- const numericB = numericalGrad(x => op(new Value(aval, "a", false), new Value(x, "b", false)).data, bval);
25
- expect(analyticA).toBeCloseTo(numericA, 4);
26
- expect(analyticB).toBeCloseTo(numericB, 4);
27
- }
28
- describe('Value autograd system', () => {
29
- it('runs the forward and backward pass example', () => {
30
- const a = new Value(2, 'a', true);
31
- const b = new Value(-3, 'b', true);
32
- const c = new Value(10, 'c', true);
33
- const e = a.mul(b); // e = a * b
34
- const d = e.add(c); // d = e + c
35
- const f = d.tanh(); // f = tanh(d)
36
-
37
- f.backward();
38
-
39
- expect(Number(a.data)).toBe(2);
40
- expect(Number(b.data)).toBe(-3);
41
- expect(Number(c.data)).toBe(10);
42
- expect(f.toString()).toMatch(/Value\(data=.*?, grad=.*?, label=tanh\(\(.+\)\)\)/);
43
- expect(Number.isFinite(a.grad)).toBe(true);
44
- expect(Number.isFinite(b.grad)).toBe(true);
45
- expect(Number.isFinite(c.grad)).toBe(true);
46
- });
47
-
48
-
49
-
50
- describe('Value new operators: powValue, mod, cmp, softplus, floor/ceil/round, square/cube, reciprocal, clamp, sum, mean', () => {
51
- it('powValue matches number math and gradients', () => {
52
- const a = new Value(2, 'a', true);
53
- const b = new Value(3, 'b', true);
54
- const c = a.powValue(b);
55
- c.backward();
56
- // da = b * a^(b-1); db = log(a) * a^b
57
- expect(c.data).toBeCloseTo(8);
58
- expect(a.grad).toBeCloseTo(3 * (2 ** 2));
59
- expect(b.grad).toBeCloseTo(Math.log(2) * 8);
60
- });
61
-
62
- it('mod computes values modulo', () => {
63
- const a = new Value(7);
64
- const b = new Value(3);
65
- expect(a.mod(b).data).toBeCloseTo(1);
66
- });
67
-
68
- it('cmp functions eq/neq/gt/lt/gte/lte match JS', () => {
69
- const a = new Value(5);
70
- const b = new Value(7);
71
- expect(a.eq(b).data).toBe(0);
72
- expect(b.eq(b).data).toBe(1);
73
- expect(a.neq(b).data).toBe(1);
74
- expect(b.neq(b).data).toBe(0);
75
- expect(a.gt(b).data).toBe(0);
76
- expect(b.gt(a).data).toBe(1);
77
- expect(a.lt(b).data).toBe(1);
78
- expect(b.lt(a).data).toBe(0);
79
- expect(a.gte(b).data).toBe(0);
80
- expect(b.gte(a).data).toBe(1);
81
- expect(a.lte(b).data).toBe(1);
82
- expect(b.lte(a).data).toBe(0);
83
- });
84
-
85
- it('softplus and its gradient', () => {
86
- const x = new Value(0.5, 'x', true);
87
- const y = x.softplus();
88
- y.backward();
89
- expect(y.data).toBeCloseTo(Math.log(1 + Math.exp(0.5)), 5);
90
- expect(x.grad).toBeCloseTo(1 / (1 + Math.exp(-0.5)), 5);
91
- });
92
-
93
- it('floor, ceil and round logic', () => {
94
- const x = new Value(-2.7);
95
- expect(x.floor().data).toBe(-3);
96
- expect(x.ceil().data).toBe(-2);
97
- expect(new Value(1.4).round().data).toBe(1);
98
- expect(new Value(1.6).round().data).toBe(2);
99
- });
100
-
101
- it('square, cube, reciprocal logic', () => {
102
- const x = new Value(3, 'x', true);
103
- const sq = x.square();
104
- const cu = x.cube();
105
- const rec = x.reciprocal();
106
- sq.backward();
107
- expect(sq.data).toBe(9);
108
- expect(x.grad).toBe(6);
109
- x.grad = 0;
110
- cu.backward();
111
- expect(cu.data).toBe(27);
112
- expect(x.grad).toBe(27);
113
- x.grad = 0;
114
- rec.backward();
115
- expect(rec.data).toBeCloseTo(1/3);
116
- expect(x.grad).toBeCloseTo(-1/9);
117
- });
118
-
119
- it('clamp clamps value and only has gradient when in interior', () => {
120
- const x = new Value(5, 'x', true);
121
- const c1 = x.clamp(0, 3);
122
- expect(c1.data).toBe(3);
123
- c1.backward();
124
- expect(x.grad).toBe(0);
125
- x.grad = 0;
126
- const c2 = x.clamp(0, 10);
127
- expect(c2.data).toBe(5);
128
- c2.backward();
129
- expect(x.grad).toBe(1);
130
- x.grad = 0;
131
- const c3 = x.clamp(7, 9);
132
- expect(c3.data).toBe(7);
133
- c3.backward();
134
- expect(x.grad).toBe(0);
135
- });
136
-
137
- it('sum and mean logic for array inputs', () => {
138
- const vals = [1, 3, 5].map((n, i) => new Value(n, 'v'+i, true));
139
- const s = Value.sum(vals);
140
- const m = Value.mean(vals);
141
- expect(s.data).toBe(9);
142
- expect(m.data).toBe(3);
143
- m.backward();
144
- for (const v of vals) expect(v.grad).toBeCloseTo(1/3);
145
- });
146
- });
147
-
148
- it('computes gradients only for required nodes (example from user)', () => {
149
- const x = new Value(2.0, "x", true);
150
- const y = new Value(3.0, "y", false); // y doesn't require gradients
151
- const z = x.mul(y).add(x.pow(2));
152
- z.backward();
153
- expect(Number(x.grad)).toBeCloseTo(7.0);
154
- expect(Number(y.grad)).toBeCloseTo(0.0);
155
- expect(x.toString()).toMatch(/Value\(data=2.0000, grad=7.0000, label=x\)/);
156
- expect(y.toString()).toMatch(/Value\(data=3.0000, grad=0.0000, label=y\)/);
157
- });
158
-
159
- it('computes gradients for add operation', () => {
160
- const a = new Value(1.5, 'a', true);
161
- const b = new Value(-0.7, 'b', true);
162
- const c = a.add(b);
163
- c.backward();
164
- // dc/da = 1, dc/db = 1
165
- expect(a.grad).toBeCloseTo(1.0);
166
- expect(b.grad).toBeCloseTo(1.0);
167
- });
168
-
169
- it('computes gradients for mul operation', () => {
170
- const a = new Value(2, 'a', true);
171
- const b = new Value(3, 'b', true);
172
- const c = a.mul(b);
173
- c.backward();
174
- // dc/da = b, dc/db = a
175
- expect(a.grad).toBeCloseTo(3.0);
176
- expect(b.grad).toBeCloseTo(2.0);
177
- });
178
-
179
- it('computes gradients for sub operation', () => {
180
- const a = new Value(2.5, 'a', true);
181
- const b = new Value(1.2, 'b', true);
182
- const c = a.sub(b);
183
- c.backward();
184
- // dc/da = 1, dc/db = -1
185
- expect(a.grad).toBeCloseTo(1.0);
186
- expect(b.grad).toBeCloseTo(-1.0);
187
- });
188
-
189
- it('computes gradients for div operation', () => {
190
- const a = new Value(6, 'a', true);
191
- const b = new Value(2, 'b', true);
192
- const c = a.div(b);
193
- c.backward();
194
- // dc/da = 1/b, dc/db = -a/b^2
195
- expect(a.grad).toBeCloseTo(0.5);
196
- expect(b.grad).toBeCloseTo(-1.5);
197
- });
198
-
199
- it('computes gradients for pow operation', () => {
200
- const a = new Value(4, 'a', true);
201
- const c = a.pow(3);
202
- c.backward();
203
- // dc/da = 3*a^2 = 48
204
- expect(a.grad).toBeCloseTo(48.0);
205
- });
206
-
207
- it('computes gradients for tanh operation', () => {
208
- const a = new Value(1, 'a', true);
209
- const c = a.tanh();
210
- c.backward();
211
- // dc/da = 1-tanh(a)^2
212
- const t = Math.tanh(1);
213
- expect(a.grad).toBeCloseTo(1 - t*t);
214
- });
215
-
216
- it('computes gradients for sigmoid operation', () => {
217
- const a = new Value(0.7, 'a', true);
218
- const c = a.sigmoid();
219
- c.backward();
220
- // dc/da = sigmoid(a)*(1-sigmoid(a))
221
- const s = 1/(1+Math.exp(-0.7));
222
- expect(a.grad).toBeCloseTo(s*(1-s));
223
- });
224
-
225
- it('does not track graph when using Value.withNoGrad', () => {
226
- const a = new Value(5, 'a', true);
227
- const b = new Value(7, 'b', true);
228
- let c: Value | undefined = undefined;
229
- Value.withNoGrad(() => {
230
- c = a.add(b);
231
- });
232
- expect(c).toBeDefined();
233
- expect(c!.requiresGrad).toBe(false);
234
- expect(c!['prev'].length).toBe(0);
235
- c!.backward();
236
- expect(a.grad).toBe(0);
237
- expect(b.grad).toBe(0);
238
- });
239
- });
240
-
241
- describe('Value unary and binary operators: trigs, relu, abs, exp/log, min/max', () => {
242
- // Numerical vs analytic gradient checks for unary operators
243
- it('numerical gradient: sin', () => testUnaryGrad('sin', x=>x.sin(), x=>Math.cos(x), 1.1));
244
- it('numerical gradient: cos', () => testUnaryGrad('cos', x=>x.cos(), x=>-Math.sin(x), 0.5));
245
- it('numerical gradient: tan', () => testUnaryGrad('tan', x=>x.tan(), x=>1/(Math.cos(x)**2), 0.8));
246
- it('numerical gradient: asin', () => testUnaryGrad('asin', x=>x.asin(), x=>1/Math.sqrt(1-x*x), 0.25));
247
- it('numerical gradient: acos', () => testUnaryGrad('acos', x=>x.acos(), x=>-1/Math.sqrt(1-x*x), 0.25));
248
- it('numerical gradient: atan', () => testUnaryGrad('atan', x=>x.atan(), x=>1/(1+x*x), 1.3));
249
- it('numerical gradient: relu', () => testUnaryGrad('relu', x=>x.relu(), x=>(x>0?1:0), 3.0));
250
- it('numerical gradient: abs', () => testUnaryGrad('abs', x=>x.abs(), x=>(x >= 0 ? 1 : -1), -3));
251
- it('numerical gradient: exp', () => testUnaryGrad('exp', x=>x.exp(), x=>Math.exp(x), 1.2));
252
- it('numerical gradient: log', () => testUnaryGrad('log', x=>x.log(), x=>1/x, 1.5));
253
- it('numerical gradient: tanh', () => testUnaryGrad('tanh', x=>x.tanh(), x=>1-Math.tanh(x)**2, 0.9));
254
- it('numerical gradient: sigmoid',() => testUnaryGrad('sigmoid',x=>x.sigmoid(), x=>{const s=1/(1+Math.exp(-x));return s*(1-s);},0.7));
255
-
256
- // Numerical vs analytic gradient checks for binary operators
257
- it('numerical gradient: add', () => testBinaryGrad('add', (a,b)=>a.add(b), (a,b)=>1, (a,b)=>1, 1.3, -2.1));
258
- it('numerical gradient: sub', () => testBinaryGrad('sub', (a,b)=>a.sub(b), (a,b)=>1, (a,b)=>-1, 5.2, -1.2));
259
- it('numerical gradient: mul', () => testBinaryGrad('mul', (a,b)=>a.mul(b), (a,b)=>b, (a,b)=>a, 1.7, 2.5));
260
- it('numerical gradient: div', () => testBinaryGrad('div', (a,b)=>a.div(b), (a,b)=>1/b, (a,b)=>-a/(b*b), 4.0, -2.2));
261
- it('numerical gradient: pow', () => {
262
- const exp = 3.3;
263
- const grad = (a:number) => exp*Math.pow(a, exp-1);
264
- testUnaryGrad('pow', x=>x.pow(exp), grad, 2.0);
265
- });
266
- it('numerical gradient: min', () => testBinaryGrad('min', (a,b)=>a.min(b), (a,b)=>a<b?1:0, (a,b)=>b<a?1:0, -1.0, 0.8));
267
- it('numerical gradient: max', () => testBinaryGrad('max', (a,b)=>a.max(b), (a,b)=>a>b?1:0, (a,b)=>b>a?1:0, 2.3, -4.5));
268
- });