@alex000291/jstorch 0.2.0 → 0.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/build/win/jstorch.node +0 -0
- package/package.json +1 -1
- package/src/tensor.js +152 -12
package/build/win/jstorch.node
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/tensor.js
CHANGED
|
@@ -14,7 +14,11 @@ export class Tensor {
|
|
|
14
14
|
this._native = new NativeTensor(data);
|
|
15
15
|
this.requires_grad = requires_grad && is_grad_enabled();
|
|
16
16
|
this.grad = null;
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
// Computational graph
|
|
19
|
+
this._grad_fn = null; // Backward function
|
|
20
|
+
this._prev = []; // Parent nodes (inputs)
|
|
21
|
+
this._op = ''; // Operation name (for debugging)
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
// Properties
|
|
@@ -33,6 +37,10 @@ export class Tensor {
|
|
|
33
37
|
|
|
34
38
|
if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
|
|
35
39
|
result.requires_grad = true;
|
|
40
|
+
result._prev = [this, other];
|
|
41
|
+
result._op = 'add';
|
|
42
|
+
|
|
43
|
+
// Gradient function: d(a+b)/da = 1, d(a+b)/db = 1
|
|
36
44
|
result._grad_fn = () => {
|
|
37
45
|
if (this.requires_grad) {
|
|
38
46
|
this.grad = this.grad ? this.grad.add(result.grad) : result.grad;
|
|
@@ -54,8 +62,22 @@ export class Tensor {
|
|
|
54
62
|
sub(other) {
|
|
55
63
|
const result = new Tensor(null);
|
|
56
64
|
result._native = this._native.sub(other._native);
|
|
65
|
+
|
|
57
66
|
if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
|
|
58
67
|
result.requires_grad = true;
|
|
68
|
+
result._prev = [this, other];
|
|
69
|
+
result._op = 'sub';
|
|
70
|
+
|
|
71
|
+
// d(a-b)/da = 1, d(a-b)/db = -1
|
|
72
|
+
result._grad_fn = () => {
|
|
73
|
+
if (this.requires_grad) {
|
|
74
|
+
this.grad = this.grad ? this.grad.add(result.grad) : result.grad;
|
|
75
|
+
}
|
|
76
|
+
if (other.requires_grad) {
|
|
77
|
+
const neg_grad = result.grad.neg();
|
|
78
|
+
other.grad = other.grad ? other.grad.add(neg_grad) : neg_grad;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
59
81
|
}
|
|
60
82
|
return result;
|
|
61
83
|
}
|
|
@@ -63,8 +85,23 @@ export class Tensor {
|
|
|
63
85
|
mul(other) {
|
|
64
86
|
const result = new Tensor(null);
|
|
65
87
|
result._native = this._native.mul(other._native);
|
|
88
|
+
|
|
66
89
|
if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
|
|
67
90
|
result.requires_grad = true;
|
|
91
|
+
result._prev = [this, other];
|
|
92
|
+
result._op = 'mul';
|
|
93
|
+
|
|
94
|
+
// d(a*b)/da = b, d(a*b)/db = a
|
|
95
|
+
result._grad_fn = () => {
|
|
96
|
+
if (this.requires_grad) {
|
|
97
|
+
const grad_a = result.grad.mul(other);
|
|
98
|
+
this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
|
|
99
|
+
}
|
|
100
|
+
if (other.requires_grad) {
|
|
101
|
+
const grad_b = result.grad.mul(this);
|
|
102
|
+
other.grad = other.grad ? other.grad.add(grad_b) : grad_b;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
68
105
|
}
|
|
69
106
|
return result;
|
|
70
107
|
}
|
|
@@ -81,8 +118,19 @@ export class Tensor {
|
|
|
81
118
|
mulScalar(scalar) {
|
|
82
119
|
const result = new Tensor(null);
|
|
83
120
|
result._native = this._native.mulScalar(scalar);
|
|
121
|
+
|
|
84
122
|
if (is_grad_enabled() && this.requires_grad) {
|
|
85
123
|
result.requires_grad = true;
|
|
124
|
+
result._prev = [this];
|
|
125
|
+
result._op = 'mulScalar';
|
|
126
|
+
|
|
127
|
+
// d(a*c)/da = c (where c is scalar)
|
|
128
|
+
result._grad_fn = () => {
|
|
129
|
+
if (this.requires_grad) {
|
|
130
|
+
const grad_a = result.grad.mulScalar(scalar);
|
|
131
|
+
this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
86
134
|
}
|
|
87
135
|
return result;
|
|
88
136
|
}
|
|
@@ -117,8 +165,21 @@ export class Tensor {
|
|
|
117
165
|
sum() {
|
|
118
166
|
const result = new Tensor(null);
|
|
119
167
|
result._native = this._native.sum();
|
|
168
|
+
|
|
120
169
|
if (is_grad_enabled() && this.requires_grad) {
|
|
121
170
|
result.requires_grad = true;
|
|
171
|
+
result._prev = [this];
|
|
172
|
+
result._op = 'sum';
|
|
173
|
+
|
|
174
|
+
// d(sum(a))/da = ones_like(a) * grad_output
|
|
175
|
+
result._grad_fn = () => {
|
|
176
|
+
if (this.requires_grad) {
|
|
177
|
+
// Gradient broadcasts to all elements
|
|
178
|
+
const grad_value = result.grad.item();
|
|
179
|
+
const grad_a = ones(this.shape).mulScalar(grad_value);
|
|
180
|
+
this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
122
183
|
}
|
|
123
184
|
return result;
|
|
124
185
|
}
|
|
@@ -126,8 +187,20 @@ export class Tensor {
|
|
|
126
187
|
mean() {
|
|
127
188
|
const result = new Tensor(null);
|
|
128
189
|
result._native = this._native.mean();
|
|
190
|
+
|
|
129
191
|
if (is_grad_enabled() && this.requires_grad) {
|
|
130
192
|
result.requires_grad = true;
|
|
193
|
+
result._prev = [this];
|
|
194
|
+
result._op = 'mean';
|
|
195
|
+
|
|
196
|
+
// d(mean(a))/da = ones_like(a) / size * grad_output
|
|
197
|
+
result._grad_fn = () => {
|
|
198
|
+
if (this.requires_grad) {
|
|
199
|
+
const grad_value = result.grad.item();
|
|
200
|
+
const grad_a = ones(this.shape).mulScalar(grad_value / this.size);
|
|
201
|
+
this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
131
204
|
}
|
|
132
205
|
return result;
|
|
133
206
|
}
|
|
@@ -138,8 +211,11 @@ export class Tensor {
|
|
|
138
211
|
|
|
139
212
|
if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
|
|
140
213
|
result.requires_grad = true;
|
|
214
|
+
result._prev = [this, other];
|
|
215
|
+
result._op = 'matmul';
|
|
216
|
+
|
|
217
|
+
// d(A@B)/dA = dC @ B^T, d(A@B)/dB = A^T @ dC
|
|
141
218
|
result._grad_fn = () => {
|
|
142
|
-
// Backward: dL/dA = dL/dC @ B^T, dL/dB = A^T @ dL/dC
|
|
143
219
|
if (this.requires_grad) {
|
|
144
220
|
const grad_a = result.grad.matmul(other.transpose());
|
|
145
221
|
this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
|
|
@@ -166,12 +242,19 @@ export class Tensor {
|
|
|
166
242
|
|
|
167
243
|
if (is_grad_enabled() && this.requires_grad) {
|
|
168
244
|
result.requires_grad = true;
|
|
169
|
-
|
|
245
|
+
result._prev = [this];
|
|
246
|
+
result._op = 'relu';
|
|
247
|
+
|
|
248
|
+
// Save input for backward
|
|
249
|
+
const input_data = this.toArray();
|
|
250
|
+
|
|
251
|
+
// d(relu(a))/da = (a > 0) ? 1 : 0
|
|
170
252
|
result._grad_fn = () => {
|
|
171
|
-
// Backward: gradient passes through where input > 0
|
|
172
|
-
// TODO: implement element-wise multiplication with mask
|
|
173
253
|
if (this.requires_grad) {
|
|
174
|
-
|
|
254
|
+
// Create mask: 1 where input > 0, 0 otherwise
|
|
255
|
+
const mask = this._createMask(input_data, x => x > 0 ? 1 : 0);
|
|
256
|
+
const grad_a = result.grad.mul(mask);
|
|
257
|
+
this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
|
|
175
258
|
}
|
|
176
259
|
};
|
|
177
260
|
}
|
|
@@ -179,6 +262,17 @@ export class Tensor {
|
|
|
179
262
|
return result;
|
|
180
263
|
}
|
|
181
264
|
|
|
265
|
+
// Helper: create mask tensor from nested array
|
|
266
|
+
_createMask(data, fn) {
|
|
267
|
+
const applyMask = (arr) => {
|
|
268
|
+
if (Array.isArray(arr)) {
|
|
269
|
+
return arr.map(applyMask);
|
|
270
|
+
}
|
|
271
|
+
return fn(arr);
|
|
272
|
+
};
|
|
273
|
+
return tensor(applyMask(data));
|
|
274
|
+
}
|
|
275
|
+
|
|
182
276
|
relu_() {
|
|
183
277
|
const result = new Tensor(null);
|
|
184
278
|
result._native = this._native.relu();
|
|
@@ -203,9 +297,29 @@ export class Tensor {
|
|
|
203
297
|
// Initialize gradient
|
|
204
298
|
this.grad = gradient || ones(this.shape);
|
|
205
299
|
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
300
|
+
// Build topological order
|
|
301
|
+
const topo = [];
|
|
302
|
+
const visited = new Set();
|
|
303
|
+
|
|
304
|
+
const build_topo = (node) => {
|
|
305
|
+
if (!node.requires_grad || visited.has(node)) return;
|
|
306
|
+
visited.add(node);
|
|
307
|
+
|
|
308
|
+
for (const parent of node._prev) {
|
|
309
|
+
build_topo(parent);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
topo.push(node);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
build_topo(this);
|
|
316
|
+
|
|
317
|
+
// Execute gradients in reverse topological order
|
|
318
|
+
for (let i = topo.length - 1; i >= 0; i--) {
|
|
319
|
+
const node = topo[i];
|
|
320
|
+
if (node._grad_fn) {
|
|
321
|
+
node._grad_fn();
|
|
322
|
+
}
|
|
209
323
|
}
|
|
210
324
|
}
|
|
211
325
|
|
|
@@ -213,10 +327,36 @@ export class Tensor {
|
|
|
213
327
|
this.grad = null;
|
|
214
328
|
}
|
|
215
329
|
|
|
216
|
-
//
|
|
330
|
+
// Transpose (2D only for now)
|
|
217
331
|
transpose() {
|
|
218
|
-
|
|
219
|
-
|
|
332
|
+
if (this.shape.length !== 2) {
|
|
333
|
+
throw new Error('transpose() only supports 2D tensors for now');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const data = this.toArray();
|
|
337
|
+
const [rows, cols] = this.shape;
|
|
338
|
+
|
|
339
|
+
// Transpose data
|
|
340
|
+
const transposed = Array.from({ length: cols }, (_, i) =>
|
|
341
|
+
Array.from({ length: rows }, (_, j) => data[j][i])
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
const result = tensor(transposed, this.requires_grad && is_grad_enabled());
|
|
345
|
+
|
|
346
|
+
if (is_grad_enabled() && this.requires_grad) {
|
|
347
|
+
result._prev = [this];
|
|
348
|
+
result._op = 'transpose';
|
|
349
|
+
|
|
350
|
+
// d(A^T)/dA = (dC)^T
|
|
351
|
+
result._grad_fn = () => {
|
|
352
|
+
if (this.requires_grad) {
|
|
353
|
+
const grad_a = result.grad.transpose();
|
|
354
|
+
this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return result;
|
|
220
360
|
}
|
|
221
361
|
}
|
|
222
362
|
|