scalar-autograd 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/LICENSE +21 -0
- package/Losses.ts +144 -0
- package/Optimizers.ts +222 -0
- package/README.md +113 -0
- package/V.ts +0 -0
- package/Value.edge-cases.spec.ts +60 -0
- package/Value.grad-flow.spec.ts +24 -0
- package/Value.losses-edge-cases.spec.ts +32 -0
- package/Value.memory.spec.ts +25 -0
- package/Value.nn.spec.ts +109 -0
- package/Value.spec.ts +268 -0
- package/Value.ts +456 -0
- package/ValueActivation.ts +51 -0
- package/ValueArithmetic.ts +272 -0
- package/ValueComparison.ts +85 -0
- package/ValueTrig.ts +70 -0
- package/package.json +41 -0
package/Value.nn.spec.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Value } from "./Value";
|
|
2
|
+
import { SGD, Adam } from "./Optimizers";
|
|
3
|
+
import { mse, binaryCrossEntropy } from "./Losses";
|
|
4
|
+
|
|
5
|
+
describe("can train scalar neural networks on minimal problems", () => {
|
|
6
|
+
|
|
7
|
+
it("1. learns linear regression (y = 2x + 3) with SGD", () => {
|
|
8
|
+
let w = new Value(Math.random(), "w", true);
|
|
9
|
+
let b = new Value(Math.random(), "b", true);
|
|
10
|
+
const examples = [
|
|
11
|
+
{ x: 1, y: 5 },
|
|
12
|
+
{ x: 2, y: 7 },
|
|
13
|
+
{ x: 3, y: 9 },
|
|
14
|
+
];
|
|
15
|
+
const opt = new SGD([w, b], { learningRate: 0.1 });
|
|
16
|
+
for (let epoch = 0; epoch < 300; ++epoch) {
|
|
17
|
+
let preds: Value[] = [];
|
|
18
|
+
let targets: Value[] = [];
|
|
19
|
+
for (const ex of examples) {
|
|
20
|
+
const x = new Value(ex.x, "x");
|
|
21
|
+
const pred = w.mul(x).add(b);
|
|
22
|
+
preds.push(pred);
|
|
23
|
+
targets.push(new Value(ex.y));
|
|
24
|
+
}
|
|
25
|
+
let loss = mse(preds, targets);
|
|
26
|
+
if (loss.data < 1e-4) break;
|
|
27
|
+
w.grad = 0; b.grad = 0;
|
|
28
|
+
loss.backward();
|
|
29
|
+
opt.step();
|
|
30
|
+
}
|
|
31
|
+
expect(w.data).toBeCloseTo(2, 1);
|
|
32
|
+
expect(b.data).toBeCloseTo(3, 1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("2. learns quadratic fit (y = x^2) with SGD", () => {
|
|
36
|
+
let a = new Value(Math.random(), "a", true);
|
|
37
|
+
let b = new Value(Math.random(), "b", true);
|
|
38
|
+
let c = new Value(Math.random(), "c", true);
|
|
39
|
+
const examples = [
|
|
40
|
+
{ x: -1, y: 1 },
|
|
41
|
+
{ x: 0, y: 0 },
|
|
42
|
+
{ x: 2, y: 4 },
|
|
43
|
+
{ x: 3, y: 9 },
|
|
44
|
+
];
|
|
45
|
+
const opt = new SGD([a, b, c], { learningRate: 0.01 });
|
|
46
|
+
|
|
47
|
+
for (let epoch = 0; epoch < 400; ++epoch) {
|
|
48
|
+
let preds: Value[] = [];
|
|
49
|
+
let targets: Value[] = [];
|
|
50
|
+
for (const ex of examples) {
|
|
51
|
+
const x = new Value(ex.x);
|
|
52
|
+
const pred = a.mul(x.square()).add(b.mul(x)).add(c);
|
|
53
|
+
preds.push(pred);
|
|
54
|
+
targets.push(new Value(ex.y));
|
|
55
|
+
}
|
|
56
|
+
let loss = mse(preds, targets);
|
|
57
|
+
if (loss.data < 1e-4) break;
|
|
58
|
+
a.grad = 0; b.grad = 0; c.grad = 0;
|
|
59
|
+
loss.backward();
|
|
60
|
+
opt.step();
|
|
61
|
+
}
|
|
62
|
+
expect(a.data).toBeCloseTo(1, 1);
|
|
63
|
+
expect(Math.abs(b.data)).toBeLessThan(0.5);
|
|
64
|
+
expect(Math.abs(c.data)).toBeLessThan(0.5);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
// This is hard to get to work reliably, I believe it's a difficult problem to solve!?
|
|
69
|
+
it("3. learns XOR with tiny MLP (2-2-1) with SGD", () => {
|
|
70
|
+
function mlp(x1: Value, x2: Value, params: Value[]): Value {
|
|
71
|
+
const [w1, w2, w3, w4, b1, b2, v1, v2, c] = params;
|
|
72
|
+
const h1 = w1.mul(x1).add(w2.mul(x2)).add(b1).tanh();
|
|
73
|
+
const h2 = w3.mul(x1).add(w4.mul(x2)).add(b2).tanh();
|
|
74
|
+
return v1.mul(h1).add(v2.mul(h2)).add(c).sigmoid();
|
|
75
|
+
}
|
|
76
|
+
let params = Array.from({ length: 9 }, (_, i) => new Value(Math.random() - 0.5, "p" + i, true));
|
|
77
|
+
const data = [
|
|
78
|
+
{ x: [0, 0], y: 0 },
|
|
79
|
+
{ x: [0, 1], y: 1 },
|
|
80
|
+
{ x: [1, 0], y: 1 },
|
|
81
|
+
{ x: [1, 1], y: 0 },
|
|
82
|
+
];
|
|
83
|
+
const opt = new SGD(params, { learningRate: 0.01 });
|
|
84
|
+
for (let epoch = 0; epoch < 5000; ++epoch) {
|
|
85
|
+
let preds: Value[] = [];
|
|
86
|
+
let targets: Value[] = [];
|
|
87
|
+
for (const ex of data) {
|
|
88
|
+
const x1 = new Value(ex.x[0]);
|
|
89
|
+
const x2 = new Value(ex.x[1]);
|
|
90
|
+
const pred = mlp(x1, x2, params);
|
|
91
|
+
preds.push(pred);
|
|
92
|
+
targets.push(new Value(ex.y));
|
|
93
|
+
}
|
|
94
|
+
let loss = binaryCrossEntropy(preds, targets);
|
|
95
|
+
if (loss.data < 1e-3) break;
|
|
96
|
+
for (const p of params) p.grad = 0;
|
|
97
|
+
loss.backward();
|
|
98
|
+
opt.step();
|
|
99
|
+
}
|
|
100
|
+
const out00 = mlp(new Value(0), new Value(0), params).data;
|
|
101
|
+
const out01 = mlp(new Value(0), new Value(1), params).data;
|
|
102
|
+
const out10 = mlp(new Value(1), new Value(0), params).data;
|
|
103
|
+
const out11 = mlp(new Value(1), new Value(1), params).data;
|
|
104
|
+
expect((out00 < 0.4 || out00 > 0.6)).toBe(true);
|
|
105
|
+
expect(out01).toBeGreaterThan(0.6);
|
|
106
|
+
expect(out10).toBeGreaterThan(0.6);
|
|
107
|
+
expect(out11).toBeLessThan(0.4);
|
|
108
|
+
});*/
|
|
109
|
+
});
|
package/Value.spec.ts
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
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
|
+
});
|