catniff 0.2.3 → 0.2.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/dist/core.d.ts +17 -10
- package/dist/core.js +57 -26
- 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
|
-
shape: number[];
|
|
13
|
-
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;
|
package/dist/core.js
CHANGED
|
@@ -20,8 +20,13 @@ class Tensor {
|
|
|
20
20
|
}
|
|
21
21
|
// Utility to flatten an nD array to be 1D
|
|
22
22
|
static flatten(tensor) {
|
|
23
|
+
// Handle scalar tensors
|
|
23
24
|
if (typeof tensor === "number")
|
|
24
25
|
return tensor;
|
|
26
|
+
// If value is already 1D, we just need to return the value ('s reference)
|
|
27
|
+
if (typeof tensor[0] === "number")
|
|
28
|
+
return tensor;
|
|
29
|
+
// Or else recursively traverse through the nD array to flatten
|
|
25
30
|
const result = [];
|
|
26
31
|
function traverse(arr) {
|
|
27
32
|
if (typeof arr === "number") {
|
|
@@ -88,7 +93,7 @@ class Tensor {
|
|
|
88
93
|
}
|
|
89
94
|
return newShape;
|
|
90
95
|
}
|
|
91
|
-
//
|
|
96
|
+
// Utility to convert flat index to array of coordinates
|
|
92
97
|
static indexToCoords(index, shape, strides) {
|
|
93
98
|
const coords = new Array(shape.length);
|
|
94
99
|
let remaining = index;
|
|
@@ -100,17 +105,33 @@ class Tensor {
|
|
|
100
105
|
}
|
|
101
106
|
return coords;
|
|
102
107
|
}
|
|
103
|
-
//
|
|
104
|
-
static
|
|
108
|
+
// Utility to convert array of coordinates to *unbroadcasted* flat index
|
|
109
|
+
static coordsToUnbroadcastedIndex(coords, shape, strides) {
|
|
105
110
|
let index = 0;
|
|
106
111
|
for (let i = 0; i < coords.length; i++) {
|
|
107
|
-
const coord = coords[i];
|
|
108
112
|
// Handle broadcasting
|
|
109
|
-
const actualCoord = shape[i] === 1 ? 0 :
|
|
113
|
+
const actualCoord = shape[i] === 1 ? 0 : coords[i];
|
|
110
114
|
index += actualCoord * strides[i];
|
|
111
115
|
}
|
|
112
116
|
return index;
|
|
113
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
|
+
;
|
|
114
135
|
// Utility for binary (two operators involved) element-wise ops
|
|
115
136
|
static elementWiseAB(tA, tB, op) {
|
|
116
137
|
if (typeof tA.value === "number" && typeof tB.value === "number") {
|
|
@@ -127,15 +148,15 @@ class Tensor {
|
|
|
127
148
|
const outputShape = Tensor.broadcastShapes(paddedAShape, paddedBShape);
|
|
128
149
|
// Get other output info
|
|
129
150
|
const outputStrides = Tensor.getStrides(outputShape);
|
|
130
|
-
const outputSize =
|
|
151
|
+
const outputSize = Tensor.shapeToSize(outputShape);
|
|
131
152
|
const outputValue = new Array(outputSize);
|
|
132
153
|
for (let i = 0; i < outputSize; i++) {
|
|
133
154
|
// Get coordinates from 1D index
|
|
134
155
|
const coordsOutput = Tensor.indexToCoords(i, outputShape, outputStrides);
|
|
135
156
|
// Convert the coordinates to 1D index of flattened A with respect to A's shape
|
|
136
|
-
const indexA = Tensor.
|
|
157
|
+
const indexA = Tensor.coordsToUnbroadcastedIndex(coordsOutput, paddedAShape, paddedAStrides);
|
|
137
158
|
// Convert the coordinates to 1D index of flattened B with respect to B's shape
|
|
138
|
-
const indexB = Tensor.
|
|
159
|
+
const indexB = Tensor.coordsToUnbroadcastedIndex(coordsOutput, paddedBShape, paddedBStrides);
|
|
139
160
|
// Calculate with op
|
|
140
161
|
outputValue[i] = op(tA.value[indexA], tB.value[indexB]);
|
|
141
162
|
}
|
|
@@ -148,7 +169,11 @@ class Tensor {
|
|
|
148
169
|
static elementWiseSelf(tA, op) {
|
|
149
170
|
if (typeof tA.value === "number")
|
|
150
171
|
return new Tensor(op(tA.value));
|
|
151
|
-
|
|
172
|
+
const newValue = new Array(tA.value.length);
|
|
173
|
+
for (let index = 0; index < tA.value.length; index++) {
|
|
174
|
+
newValue[index] = op(tA.value[index]);
|
|
175
|
+
}
|
|
176
|
+
return new Tensor(newValue, { shape: tA.shape, strides: tA.strides });
|
|
152
177
|
}
|
|
153
178
|
// Utility to do element-wise operation and build a dag node with another tensor
|
|
154
179
|
elementWiseABDAG(other, op, thisGrad = () => new Tensor(0), otherGrad = () => new Tensor(0)) {
|
|
@@ -312,15 +337,18 @@ class Tensor {
|
|
|
312
337
|
if (typeof dims === "undefined") {
|
|
313
338
|
dims = Array.from({ length: this.shape.length }, (_, index) => index);
|
|
314
339
|
}
|
|
340
|
+
// Dims that are reduced now have size-1
|
|
315
341
|
const outputShape = this.shape.map((dim, i) => dims.includes(i) ? 1 : dim);
|
|
316
342
|
const outputStrides = Tensor.getStrides(outputShape);
|
|
317
|
-
const outputSize =
|
|
343
|
+
const outputSize = Tensor.shapeToSize(outputShape);
|
|
318
344
|
const outputValue = new Array(outputSize).fill(0);
|
|
319
|
-
const originalSize = this.shape
|
|
345
|
+
const originalSize = Tensor.shapeToSize(this.shape);
|
|
346
|
+
// Gradient data
|
|
320
347
|
let gradShape, gradStrides, gradValue = [];
|
|
348
|
+
// Allocate gradient data only when needed
|
|
321
349
|
if (this.requiresGrad) {
|
|
322
|
-
gradShape =
|
|
323
|
-
gradStrides =
|
|
350
|
+
gradShape = this.shape;
|
|
351
|
+
gradStrides = this.strides;
|
|
324
352
|
gradValue = new Array(originalSize).fill(0);
|
|
325
353
|
}
|
|
326
354
|
for (let index = 0; index < originalSize; index++) {
|
|
@@ -328,11 +356,11 @@ class Tensor {
|
|
|
328
356
|
// Force 0 on reduced axes to collapse into size-1 dims
|
|
329
357
|
const outCoords = coords.map((val, i) => dims.includes(i) ? 0 : val);
|
|
330
358
|
// Convert output coordinates to flat index
|
|
331
|
-
const outFlatIndex =
|
|
332
|
-
// Accumulate
|
|
333
|
-
const realFlatIndex =
|
|
359
|
+
const outFlatIndex = Tensor.coordsToIndex(outCoords, outputStrides);
|
|
360
|
+
// Accumulate, outFlatIndex should match multiple realFlatIndexes
|
|
361
|
+
const realFlatIndex = Tensor.coordsToIndex(coords, this.strides);
|
|
334
362
|
outputValue[outFlatIndex] += this.value[realFlatIndex];
|
|
335
|
-
// Mark for gradient
|
|
363
|
+
// Mark for gradient if needed
|
|
336
364
|
if (this.requiresGrad) {
|
|
337
365
|
(gradValue)[realFlatIndex] = 1;
|
|
338
366
|
}
|
|
@@ -535,7 +563,7 @@ class Tensor {
|
|
|
535
563
|
}
|
|
536
564
|
// If same dimension, return copy
|
|
537
565
|
if (dim1 === dim2) {
|
|
538
|
-
return new Tensor(this.value, { shape:
|
|
566
|
+
return new Tensor(this.value, { shape: this.shape, strides: this.strides });
|
|
539
567
|
}
|
|
540
568
|
// Create new shape and strides by swapping
|
|
541
569
|
const newShape = [...this.shape];
|
|
@@ -569,6 +597,7 @@ class Tensor {
|
|
|
569
597
|
if (this.shape.length !== 1 || other.shape.length !== 1) {
|
|
570
598
|
throw new Error("Inputs are not 1D tensors");
|
|
571
599
|
}
|
|
600
|
+
// Simple vector dot product
|
|
572
601
|
const vectLen = this.shape[0];
|
|
573
602
|
const vectA = this.value;
|
|
574
603
|
const vectB = other.value;
|
|
@@ -606,6 +635,7 @@ class Tensor {
|
|
|
606
635
|
if (this.shape.length !== 2 || other.shape.length !== 2) {
|
|
607
636
|
throw new Error("Inputs are not matrices");
|
|
608
637
|
}
|
|
638
|
+
// Simple matrix multiplication
|
|
609
639
|
const matA = this.value;
|
|
610
640
|
const matB = other.value;
|
|
611
641
|
const matAStrides = this.strides;
|
|
@@ -618,11 +648,12 @@ class Tensor {
|
|
|
618
648
|
throw new Error("Invalid matrices shape for multiplication");
|
|
619
649
|
const matCShape = [matARows, matBCols];
|
|
620
650
|
const matCStrides = Tensor.getStrides(matCShape);
|
|
621
|
-
const matCSize =
|
|
651
|
+
const matCSize = Tensor.shapeToSize(matCShape);
|
|
622
652
|
const matC = new Array(matCSize).fill(0);
|
|
623
653
|
for (let i = 0; i < matARows; i++) {
|
|
624
654
|
for (let j = 0; j < matBCols; j++) {
|
|
625
655
|
for (let k = 0; k < matACols; k++) {
|
|
656
|
+
// Tensor values are 1D arrays so we have to get real index using strides
|
|
626
657
|
matC[i * matCStrides[0] + j * matCStrides[1]] +=
|
|
627
658
|
matA[i * matAStrides[0] + k * matAStrides[1]] *
|
|
628
659
|
matB[k * matBStrides[0] + j * matBStrides[1]];
|
|
@@ -660,7 +691,7 @@ class Tensor {
|
|
|
660
691
|
throw new Error("Input is not a 2D and 1D tensor pair");
|
|
661
692
|
}
|
|
662
693
|
// MM with no grad
|
|
663
|
-
const thisMat = new Tensor(this.value, { shape:
|
|
694
|
+
const thisMat = new Tensor(this.value, { shape: this.shape, strides: this.strides });
|
|
664
695
|
const otherMat = new Tensor(other.value, { shape: [other.shape[0], 1], strides: [other.strides[0], 1] });
|
|
665
696
|
const out = thisMat.mm(otherMat).squeeze(1);
|
|
666
697
|
// Handle grad with original tensors
|
|
@@ -708,7 +739,7 @@ class Tensor {
|
|
|
708
739
|
static fullLike(tensor, num, options = {}) {
|
|
709
740
|
if (typeof tensor.value === "number")
|
|
710
741
|
return new Tensor(num, options);
|
|
711
|
-
return new Tensor(tensor.value.
|
|
742
|
+
return new Tensor(new Array(tensor.value.length).fill(num), { shape: tensor.shape, strides: tensor.strides, ...options });
|
|
712
743
|
}
|
|
713
744
|
// Reverse-mode autodiff call
|
|
714
745
|
backward() {
|
|
@@ -718,7 +749,7 @@ class Tensor {
|
|
|
718
749
|
function build(node) {
|
|
719
750
|
if (!visited.has(node) && node.requiresGrad) {
|
|
720
751
|
visited.add(node);
|
|
721
|
-
node.grad = Tensor.fullLike(node, 0);
|
|
752
|
+
node.grad = Tensor.fullLike(node, 0); // Reset grad with 0
|
|
722
753
|
for (let child of node.children)
|
|
723
754
|
build(child);
|
|
724
755
|
topo.push(node);
|
|
@@ -731,7 +762,7 @@ class Tensor {
|
|
|
731
762
|
topo[index].gradFn();
|
|
732
763
|
}
|
|
733
764
|
}
|
|
734
|
-
// Returns the number/nD array form of tensor
|
|
765
|
+
// Returns the raw number/nD array form of tensor
|
|
735
766
|
val() {
|
|
736
767
|
if (typeof this.value === "number")
|
|
737
768
|
return this.value;
|
|
@@ -753,11 +784,11 @@ class Tensor {
|
|
|
753
784
|
}
|
|
754
785
|
return buildNested(this.value, this.shape, this.strides);
|
|
755
786
|
}
|
|
756
|
-
// Returns a copy of the tensor with gradient turned on/off
|
|
787
|
+
// Returns a copy of the tensor with gradient turned on/off and detaches from autograd
|
|
757
788
|
withGrad(requiresGrad) {
|
|
758
789
|
return new Tensor(this.value, {
|
|
759
|
-
shape:
|
|
760
|
-
strides:
|
|
790
|
+
shape: this.shape,
|
|
791
|
+
strides: this.strides,
|
|
761
792
|
requiresGrad
|
|
762
793
|
});
|
|
763
794
|
}
|