catniff 0.2.4 → 0.2.6
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/dist/core.d.ts +19 -10
- package/dist/core.js +169 -16
- package/package.json +1 -1
package/dist/core.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type TensorValue = number | TensorValue[];
|
|
2
2
|
export interface TensorOptions {
|
|
3
|
-
shape?: number[];
|
|
4
|
-
strides?: number[];
|
|
3
|
+
shape?: readonly number[];
|
|
4
|
+
strides?: readonly number[];
|
|
5
5
|
grad?: Tensor;
|
|
6
6
|
requiresGrad?: boolean;
|
|
7
7
|
gradFn?: Function;
|
|
@@ -9,20 +9,27 @@ export interface TensorOptions {
|
|
|
9
9
|
}
|
|
10
10
|
export declare class Tensor {
|
|
11
11
|
value: number[] | number;
|
|
12
|
-
readonly shape: number[];
|
|
13
|
-
readonly strides: number[];
|
|
12
|
+
readonly shape: readonly number[];
|
|
13
|
+
readonly strides: readonly number[];
|
|
14
14
|
grad?: Tensor;
|
|
15
15
|
requiresGrad: boolean;
|
|
16
16
|
gradFn: Function;
|
|
17
17
|
children: Tensor[];
|
|
18
18
|
constructor(value: TensorValue, options?: TensorOptions);
|
|
19
19
|
static flatten(tensor: TensorValue): number[] | number;
|
|
20
|
-
static getShape(tensor: TensorValue): number[];
|
|
21
|
-
static getStrides(shape: number[]): number[];
|
|
22
|
-
static padShape(stridesA: number[], stridesB: number[], shapeA: number[], shapeB: number[]):
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
static getShape(tensor: TensorValue): readonly number[];
|
|
21
|
+
static getStrides(shape: readonly number[]): readonly number[];
|
|
22
|
+
static padShape(stridesA: readonly number[], stridesB: readonly number[], shapeA: readonly number[], shapeB: readonly number[]): [
|
|
23
|
+
readonly number[],
|
|
24
|
+
readonly number[],
|
|
25
|
+
readonly number[],
|
|
26
|
+
readonly number[]
|
|
27
|
+
];
|
|
28
|
+
static broadcastShapes(shapeA: readonly number[], shapeB: readonly number[]): readonly number[];
|
|
29
|
+
static indexToCoords(index: number, shape: readonly number[], strides: readonly number[]): number[];
|
|
30
|
+
static coordsToUnbroadcastedIndex(coords: number[], shape: readonly number[], strides: readonly number[]): number;
|
|
31
|
+
static coordsToIndex(coords: number[], strides: readonly number[]): number;
|
|
32
|
+
static shapeToSize(shape: readonly number[]): number;
|
|
26
33
|
static elementWiseAB(tA: Tensor, tB: Tensor, op: (tA: number, tB: number) => number): Tensor;
|
|
27
34
|
static elementWiseSelf(tA: Tensor, op: (tA: number) => number): Tensor;
|
|
28
35
|
elementWiseABDAG(other: TensorValue | Tensor, op: (a: number, b: number) => number, thisGrad?: (self: Tensor, other: Tensor, outGrad: Tensor) => Tensor, otherGrad?: (self: Tensor, other: Tensor, outGrad: Tensor) => Tensor): Tensor;
|
|
@@ -32,6 +39,8 @@ export declare class Tensor {
|
|
|
32
39
|
squeeze(dims?: number[] | number): Tensor;
|
|
33
40
|
unsqueeze(dim: number): Tensor;
|
|
34
41
|
sum(dims?: number[] | number, keepDims?: boolean): Tensor;
|
|
42
|
+
prod(dims?: number[] | number, keepDims?: boolean): Tensor;
|
|
43
|
+
mean(dims?: number[] | number, keepDims?: boolean): Tensor;
|
|
35
44
|
add(other: TensorValue | Tensor): Tensor;
|
|
36
45
|
sub(other: TensorValue | Tensor): Tensor;
|
|
37
46
|
mul(other: TensorValue | Tensor): Tensor;
|
package/dist/core.js
CHANGED
|
@@ -93,7 +93,7 @@ class Tensor {
|
|
|
93
93
|
}
|
|
94
94
|
return newShape;
|
|
95
95
|
}
|
|
96
|
-
//
|
|
96
|
+
// Utility to convert flat index to array of coordinates
|
|
97
97
|
static indexToCoords(index, shape, strides) {
|
|
98
98
|
const coords = new Array(shape.length);
|
|
99
99
|
let remaining = index;
|
|
@@ -105,17 +105,33 @@ class Tensor {
|
|
|
105
105
|
}
|
|
106
106
|
return coords;
|
|
107
107
|
}
|
|
108
|
-
//
|
|
109
|
-
static
|
|
108
|
+
// Utility to convert array of coordinates to *unbroadcasted* flat index
|
|
109
|
+
static coordsToUnbroadcastedIndex(coords, shape, strides) {
|
|
110
110
|
let index = 0;
|
|
111
111
|
for (let i = 0; i < coords.length; i++) {
|
|
112
|
-
const coord = coords[i];
|
|
113
112
|
// Handle broadcasting
|
|
114
|
-
const actualCoord = shape[i] === 1 ? 0 :
|
|
113
|
+
const actualCoord = shape[i] === 1 ? 0 : coords[i];
|
|
115
114
|
index += actualCoord * strides[i];
|
|
116
115
|
}
|
|
117
116
|
return index;
|
|
118
117
|
}
|
|
118
|
+
// Utility to convert array of coordinates to flat index
|
|
119
|
+
static coordsToIndex(coords, strides) {
|
|
120
|
+
let index = 0;
|
|
121
|
+
for (let i = 0; i < coords.length; i++) {
|
|
122
|
+
index += coords[i] * strides[i];
|
|
123
|
+
}
|
|
124
|
+
return index;
|
|
125
|
+
}
|
|
126
|
+
// Utility to convert shape into 1D value array size
|
|
127
|
+
static shapeToSize(shape) {
|
|
128
|
+
let prod = 1;
|
|
129
|
+
for (let i = 0; i < shape.length; i++) {
|
|
130
|
+
prod *= shape[i];
|
|
131
|
+
}
|
|
132
|
+
return prod;
|
|
133
|
+
}
|
|
134
|
+
;
|
|
119
135
|
// Utility for binary (two operators involved) element-wise ops
|
|
120
136
|
static elementWiseAB(tA, tB, op) {
|
|
121
137
|
if (typeof tA.value === "number" && typeof tB.value === "number") {
|
|
@@ -132,15 +148,15 @@ class Tensor {
|
|
|
132
148
|
const outputShape = Tensor.broadcastShapes(paddedAShape, paddedBShape);
|
|
133
149
|
// Get other output info
|
|
134
150
|
const outputStrides = Tensor.getStrides(outputShape);
|
|
135
|
-
const outputSize =
|
|
151
|
+
const outputSize = Tensor.shapeToSize(outputShape);
|
|
136
152
|
const outputValue = new Array(outputSize);
|
|
137
153
|
for (let i = 0; i < outputSize; i++) {
|
|
138
154
|
// Get coordinates from 1D index
|
|
139
155
|
const coordsOutput = Tensor.indexToCoords(i, outputShape, outputStrides);
|
|
140
156
|
// Convert the coordinates to 1D index of flattened A with respect to A's shape
|
|
141
|
-
const indexA = Tensor.
|
|
157
|
+
const indexA = Tensor.coordsToUnbroadcastedIndex(coordsOutput, paddedAShape, paddedAStrides);
|
|
142
158
|
// Convert the coordinates to 1D index of flattened B with respect to B's shape
|
|
143
|
-
const indexB = Tensor.
|
|
159
|
+
const indexB = Tensor.coordsToUnbroadcastedIndex(coordsOutput, paddedBShape, paddedBStrides);
|
|
144
160
|
// Calculate with op
|
|
145
161
|
outputValue[i] = op(tA.value[indexA], tB.value[indexB]);
|
|
146
162
|
}
|
|
@@ -321,29 +337,33 @@ class Tensor {
|
|
|
321
337
|
if (typeof dims === "undefined") {
|
|
322
338
|
dims = Array.from({ length: this.shape.length }, (_, index) => index);
|
|
323
339
|
}
|
|
340
|
+
// Dims that are reduced now have size-1
|
|
324
341
|
const outputShape = this.shape.map((dim, i) => dims.includes(i) ? 1 : dim);
|
|
325
342
|
const outputStrides = Tensor.getStrides(outputShape);
|
|
326
|
-
const outputSize =
|
|
343
|
+
const outputSize = Tensor.shapeToSize(outputShape);
|
|
327
344
|
const outputValue = new Array(outputSize).fill(0);
|
|
328
|
-
const originalSize = this.shape
|
|
345
|
+
const originalSize = Tensor.shapeToSize(this.shape);
|
|
346
|
+
// Gradient data
|
|
329
347
|
let gradShape, gradStrides, gradValue = [];
|
|
348
|
+
// Allocate gradient data only when needed
|
|
330
349
|
if (this.requiresGrad) {
|
|
331
350
|
gradShape = this.shape;
|
|
332
351
|
gradStrides = this.strides;
|
|
333
352
|
gradValue = new Array(originalSize).fill(0);
|
|
334
353
|
}
|
|
354
|
+
// Calculate new value after sum
|
|
335
355
|
for (let index = 0; index < originalSize; index++) {
|
|
336
356
|
const coords = Tensor.indexToCoords(index, this.shape, this.strides);
|
|
337
357
|
// Force 0 on reduced axes to collapse into size-1 dims
|
|
338
358
|
const outCoords = coords.map((val, i) => dims.includes(i) ? 0 : val);
|
|
339
359
|
// Convert output coordinates to flat index
|
|
340
|
-
const outFlatIndex =
|
|
341
|
-
// Accumulate
|
|
342
|
-
const realFlatIndex =
|
|
360
|
+
const outFlatIndex = Tensor.coordsToIndex(outCoords, outputStrides);
|
|
361
|
+
// Accumulate, outFlatIndex should match multiple realFlatIndexes
|
|
362
|
+
const realFlatIndex = Tensor.coordsToIndex(coords, this.strides);
|
|
343
363
|
outputValue[outFlatIndex] += this.value[realFlatIndex];
|
|
344
|
-
// Mark for gradient
|
|
364
|
+
// Mark for gradient if needed
|
|
345
365
|
if (this.requiresGrad) {
|
|
346
|
-
|
|
366
|
+
gradValue[realFlatIndex] = 1;
|
|
347
367
|
}
|
|
348
368
|
}
|
|
349
369
|
const out = new Tensor(outputValue, {
|
|
@@ -361,6 +381,136 @@ class Tensor {
|
|
|
361
381
|
}
|
|
362
382
|
return keepDims ? out : out.squeeze(dims);
|
|
363
383
|
}
|
|
384
|
+
// Tensor product reduction
|
|
385
|
+
prod(dims, keepDims = false) {
|
|
386
|
+
if (typeof this.value === "number")
|
|
387
|
+
return new Tensor(this.value);
|
|
388
|
+
if (typeof dims === "number") {
|
|
389
|
+
dims = [dims];
|
|
390
|
+
}
|
|
391
|
+
if (typeof dims === "undefined") {
|
|
392
|
+
dims = Array.from({ length: this.shape.length }, (_, index) => index);
|
|
393
|
+
}
|
|
394
|
+
// Dims that are reduced now have size-1
|
|
395
|
+
const outputShape = this.shape.map((dim, i) => dims.includes(i) ? 1 : dim);
|
|
396
|
+
const outputStrides = Tensor.getStrides(outputShape);
|
|
397
|
+
const outputSize = Tensor.shapeToSize(outputShape);
|
|
398
|
+
const outputValue = new Array(outputSize).fill(1);
|
|
399
|
+
const originalSize = Tensor.shapeToSize(this.shape);
|
|
400
|
+
// Gradient data
|
|
401
|
+
let gradShape, gradStrides, gradValue = [];
|
|
402
|
+
// Allocate gradient data only when needed
|
|
403
|
+
if (this.requiresGrad) {
|
|
404
|
+
gradShape = this.shape;
|
|
405
|
+
gradStrides = this.strides;
|
|
406
|
+
gradValue = new Array(originalSize).fill(0);
|
|
407
|
+
}
|
|
408
|
+
// Calculate new value after multiplying
|
|
409
|
+
for (let index = 0; index < originalSize; index++) {
|
|
410
|
+
const coords = Tensor.indexToCoords(index, this.shape, this.strides);
|
|
411
|
+
// Force 0 on reduced axes to collapse into size-1 dims
|
|
412
|
+
const outCoords = coords.map((val, i) => dims.includes(i) ? 0 : val);
|
|
413
|
+
// Convert output coordinates to flat index
|
|
414
|
+
const outFlatIndex = Tensor.coordsToIndex(outCoords, outputStrides);
|
|
415
|
+
// Accumulate, outFlatIndex should match multiple realFlatIndexes
|
|
416
|
+
const realFlatIndex = Tensor.coordsToIndex(coords, this.strides);
|
|
417
|
+
outputValue[outFlatIndex] *= this.value[realFlatIndex];
|
|
418
|
+
}
|
|
419
|
+
const out = new Tensor(outputValue, {
|
|
420
|
+
shape: outputShape,
|
|
421
|
+
strides: outputStrides
|
|
422
|
+
});
|
|
423
|
+
// Set up gradient if needed
|
|
424
|
+
if (this.requiresGrad) {
|
|
425
|
+
// Grad is the product of other elements of the same axis, which is product of all els divided by the current value
|
|
426
|
+
for (let index = 0; index < originalSize; index++) {
|
|
427
|
+
const coords = Tensor.indexToCoords(index, this.shape, this.strides);
|
|
428
|
+
// Force 0 on reduced axes to collapse into size-1 dims
|
|
429
|
+
const outCoords = coords.map((val, i) => dims.includes(i) ? 0 : val);
|
|
430
|
+
// Convert output coordinates to flat index
|
|
431
|
+
const outFlatIndex = Tensor.coordsToIndex(outCoords, outputStrides);
|
|
432
|
+
// Accumulate, outFlatIndex should match multiple realFlatIndexes
|
|
433
|
+
const realFlatIndex = Tensor.coordsToIndex(coords, this.strides);
|
|
434
|
+
// Calculate gradient at position
|
|
435
|
+
gradValue[realFlatIndex] = outputValue[outFlatIndex] / this.value[realFlatIndex];
|
|
436
|
+
}
|
|
437
|
+
out.requiresGrad = true;
|
|
438
|
+
out.children.push(this);
|
|
439
|
+
out.gradFn = () => {
|
|
440
|
+
const localGrad = new Tensor(gradValue, { shape: gradShape, strides: gradStrides });
|
|
441
|
+
Tensor.addGrad(this, out.grad.withGrad(false).mul(localGrad));
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
return keepDims ? out : out.squeeze(dims);
|
|
445
|
+
}
|
|
446
|
+
// Tensor mean reduction
|
|
447
|
+
mean(dims, keepDims = false) {
|
|
448
|
+
if (typeof this.value === "number")
|
|
449
|
+
return new Tensor(this.value);
|
|
450
|
+
if (typeof dims === "number") {
|
|
451
|
+
dims = [dims];
|
|
452
|
+
}
|
|
453
|
+
if (typeof dims === "undefined") {
|
|
454
|
+
dims = Array.from({ length: this.shape.length }, (_, index) => index);
|
|
455
|
+
}
|
|
456
|
+
// Dims that are reduced now have size-1
|
|
457
|
+
const outputShape = this.shape.map((dim, i) => dims.includes(i) ? 1 : dim);
|
|
458
|
+
const outputStrides = Tensor.getStrides(outputShape);
|
|
459
|
+
const outputSize = Tensor.shapeToSize(outputShape);
|
|
460
|
+
const outputValue = new Array(outputSize).fill(0);
|
|
461
|
+
const outputFeeders = new Array(outputSize).fill(0);
|
|
462
|
+
const originalSize = Tensor.shapeToSize(this.shape);
|
|
463
|
+
// Gradient data
|
|
464
|
+
let gradShape, gradStrides, gradValue = [];
|
|
465
|
+
// Allocate gradient data only when needed
|
|
466
|
+
if (this.requiresGrad) {
|
|
467
|
+
gradShape = this.shape;
|
|
468
|
+
gradStrides = this.strides;
|
|
469
|
+
gradValue = new Array(originalSize).fill(0);
|
|
470
|
+
}
|
|
471
|
+
// Calculate sums and how many elements contribute to specific positions
|
|
472
|
+
for (let index = 0; index < originalSize; index++) {
|
|
473
|
+
const coords = Tensor.indexToCoords(index, this.shape, this.strides);
|
|
474
|
+
// Force 0 on reduced axes to collapse into size-1 dims
|
|
475
|
+
const outCoords = coords.map((val, i) => dims.includes(i) ? 0 : val);
|
|
476
|
+
// Convert output coordinates to flat index
|
|
477
|
+
const outFlatIndex = Tensor.coordsToIndex(outCoords, outputStrides);
|
|
478
|
+
// Accumulate, outFlatIndex should match multiple realFlatIndexes
|
|
479
|
+
const realFlatIndex = Tensor.coordsToIndex(coords, this.strides);
|
|
480
|
+
outputValue[outFlatIndex] += this.value[realFlatIndex];
|
|
481
|
+
outputFeeders[outFlatIndex]++;
|
|
482
|
+
}
|
|
483
|
+
// Calculate mean by dividing sum by the number of contributors to the position
|
|
484
|
+
for (let index = 0; index < outputSize; index++) {
|
|
485
|
+
outputValue[index] /= outputFeeders[index];
|
|
486
|
+
}
|
|
487
|
+
const out = new Tensor(outputValue, {
|
|
488
|
+
shape: outputShape,
|
|
489
|
+
strides: outputStrides
|
|
490
|
+
});
|
|
491
|
+
// Set up gradient if needed
|
|
492
|
+
if (this.requiresGrad) {
|
|
493
|
+
// Calculate grad by assiging 1 divide by the number of contributors to the position
|
|
494
|
+
for (let index = 0; index < originalSize; index++) {
|
|
495
|
+
const coords = Tensor.indexToCoords(index, this.shape, this.strides);
|
|
496
|
+
// Force 0 on reduced axes to collapse into size-1 dims
|
|
497
|
+
const outCoords = coords.map((val, i) => dims.includes(i) ? 0 : val);
|
|
498
|
+
// Convert output coordinates to flat index
|
|
499
|
+
const outFlatIndex = Tensor.coordsToIndex(outCoords, outputStrides);
|
|
500
|
+
// Accumulate, outFlatIndex should match multiple realFlatIndexes
|
|
501
|
+
const realFlatIndex = Tensor.coordsToIndex(coords, this.strides);
|
|
502
|
+
// Mean = 1/n * (el1 + el2 + ... + eln) so grad = 1/n
|
|
503
|
+
gradValue[realFlatIndex] = 1 / outputFeeders[outFlatIndex];
|
|
504
|
+
}
|
|
505
|
+
out.requiresGrad = true;
|
|
506
|
+
out.children.push(this);
|
|
507
|
+
out.gradFn = () => {
|
|
508
|
+
const localGrad = new Tensor(gradValue, { shape: gradShape, strides: gradStrides });
|
|
509
|
+
Tensor.addGrad(this, out.grad.withGrad(false).mul(localGrad));
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
return keepDims ? out : out.squeeze(dims);
|
|
513
|
+
}
|
|
364
514
|
// Tensor element-wise addition
|
|
365
515
|
add(other) {
|
|
366
516
|
return this.elementWiseABDAG(other, (a, b) => a + b, (self, other, outGrad) => outGrad, (self, other, outGrad) => outGrad);
|
|
@@ -578,6 +728,7 @@ class Tensor {
|
|
|
578
728
|
if (this.shape.length !== 1 || other.shape.length !== 1) {
|
|
579
729
|
throw new Error("Inputs are not 1D tensors");
|
|
580
730
|
}
|
|
731
|
+
// Simple vector dot product
|
|
581
732
|
const vectLen = this.shape[0];
|
|
582
733
|
const vectA = this.value;
|
|
583
734
|
const vectB = other.value;
|
|
@@ -615,6 +766,7 @@ class Tensor {
|
|
|
615
766
|
if (this.shape.length !== 2 || other.shape.length !== 2) {
|
|
616
767
|
throw new Error("Inputs are not matrices");
|
|
617
768
|
}
|
|
769
|
+
// Simple matrix multiplication
|
|
618
770
|
const matA = this.value;
|
|
619
771
|
const matB = other.value;
|
|
620
772
|
const matAStrides = this.strides;
|
|
@@ -627,11 +779,12 @@ class Tensor {
|
|
|
627
779
|
throw new Error("Invalid matrices shape for multiplication");
|
|
628
780
|
const matCShape = [matARows, matBCols];
|
|
629
781
|
const matCStrides = Tensor.getStrides(matCShape);
|
|
630
|
-
const matCSize =
|
|
782
|
+
const matCSize = Tensor.shapeToSize(matCShape);
|
|
631
783
|
const matC = new Array(matCSize).fill(0);
|
|
632
784
|
for (let i = 0; i < matARows; i++) {
|
|
633
785
|
for (let j = 0; j < matBCols; j++) {
|
|
634
786
|
for (let k = 0; k < matACols; k++) {
|
|
787
|
+
// Tensor values are 1D arrays so we have to get real index using strides
|
|
635
788
|
matC[i * matCStrides[0] + j * matCStrides[1]] +=
|
|
636
789
|
matA[i * matAStrides[0] + k * matAStrides[1]] *
|
|
637
790
|
matB[k * matBStrides[0] + j * matBStrides[1]];
|