catniff 0.2.12 → 0.2.14
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/README.md +5 -7
- package/dist/core.d.ts +12 -0
- package/dist/core.js +173 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +15 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Catniff
|
|
2
2
|
|
|
3
|
-
Catniff is
|
|
3
|
+
Catniff is a small deep learning framework for Javacript, built to be Torch-like, but more direct on tensors and autograd usage like Tinygrad. This project is under development currently, so keep in mind that APIs can be unstable and backwards-incompatible. On a side-note, the name is a play on "catnip" and "differentiation".
|
|
4
4
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
@@ -80,14 +80,12 @@ All available APIs are in [`./src/core.ts`](./src/core.ts) if you want to dig de
|
|
|
80
80
|
|
|
81
81
|
## Todos
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
*
|
|
86
|
-
* Add more tensor ops.
|
|
87
|
-
* Proper documentation.
|
|
83
|
+
* Bug fixes.
|
|
84
|
+
* More tensor ops.
|
|
85
|
+
* More detailed documentation.
|
|
88
86
|
* GPU acceleration.
|
|
89
87
|
* Some general neural net APIs.
|
|
90
|
-
*
|
|
88
|
+
* Code refactoring.
|
|
91
89
|
* Proper tests.
|
|
92
90
|
* Option to load more backends.
|
|
93
91
|
|
package/dist/core.d.ts
CHANGED
|
@@ -137,11 +137,23 @@ export declare class Tensor {
|
|
|
137
137
|
t(): Tensor;
|
|
138
138
|
dot(other: TensorValue | Tensor): Tensor;
|
|
139
139
|
mm(other: TensorValue | Tensor): Tensor;
|
|
140
|
+
bmm(other: TensorValue | Tensor): Tensor;
|
|
140
141
|
mv(other: TensorValue | Tensor): Tensor;
|
|
141
142
|
matmul(other: TensorValue | Tensor): Tensor;
|
|
143
|
+
static full(shape: number[], num: number, options?: TensorOptions): Tensor;
|
|
142
144
|
static fullLike(tensor: Tensor, num: number, options?: TensorOptions): Tensor;
|
|
145
|
+
static ones(shape?: number[], options?: TensorOptions): Tensor;
|
|
143
146
|
static onesLike(tensor: Tensor, options?: TensorOptions): Tensor;
|
|
147
|
+
static zeros(shape?: number[], options?: TensorOptions): Tensor;
|
|
144
148
|
static zerosLike(tensor: Tensor, options?: TensorOptions): Tensor;
|
|
149
|
+
static rand(shape?: number[], options?: TensorOptions): Tensor;
|
|
150
|
+
static randLike(tensor: Tensor, options?: TensorOptions): Tensor;
|
|
151
|
+
static randn(shape?: number[], options?: TensorOptions): Tensor;
|
|
152
|
+
static randnLike(tensor: Tensor, options?: TensorOptions): Tensor;
|
|
153
|
+
static randint(shape: number[], low: number, high: number, options?: TensorOptions): Tensor;
|
|
154
|
+
static randintLike(tensor: Tensor, low: number, high: number, options?: TensorOptions): Tensor;
|
|
155
|
+
static normal(shape: number[], mean: number, stdDev: number, options?: TensorOptions): Tensor;
|
|
156
|
+
static uniform(shape: number[], low: number, high: number, options?: TensorOptions): Tensor;
|
|
145
157
|
backward(): void;
|
|
146
158
|
val(): TensorValue;
|
|
147
159
|
withGrad(requiresGrad: boolean): Tensor;
|
package/dist/core.js
CHANGED
|
@@ -1049,6 +1049,64 @@ class Tensor {
|
|
|
1049
1049
|
}
|
|
1050
1050
|
return out;
|
|
1051
1051
|
}
|
|
1052
|
+
// Batched 3D tensor matmul
|
|
1053
|
+
bmm(other) {
|
|
1054
|
+
other = Tensor.forceTensor(other);
|
|
1055
|
+
// Verify 3D shape
|
|
1056
|
+
if (this.shape.length !== 3 || other.shape.length !== 3 || this.shape[0] !== other.shape[0]) {
|
|
1057
|
+
throw new Error("Inputs are not 3D tensors with the same first dim size");
|
|
1058
|
+
}
|
|
1059
|
+
// Simple matrix multiplication
|
|
1060
|
+
const batchA = this.value;
|
|
1061
|
+
const batchB = other.value;
|
|
1062
|
+
const batchAStrides = this.strides;
|
|
1063
|
+
const batchBStrides = other.strides;
|
|
1064
|
+
const batchSize = this.shape[0];
|
|
1065
|
+
const batchARows = this.shape[1];
|
|
1066
|
+
const batchACols = this.shape[2];
|
|
1067
|
+
const batchBRows = other.shape[1];
|
|
1068
|
+
const batchBCols = other.shape[2];
|
|
1069
|
+
if (batchACols !== batchBRows)
|
|
1070
|
+
throw new Error("Invalid matrices shape for multiplication");
|
|
1071
|
+
const batchCShape = [batchSize, batchARows, batchBCols];
|
|
1072
|
+
const batchCStrides = Tensor.getStrides(batchCShape);
|
|
1073
|
+
const batchCSize = Tensor.shapeToSize(batchCShape);
|
|
1074
|
+
const batchC = new Array(batchCSize).fill(0);
|
|
1075
|
+
for (let q = 0; q < batchSize; q++) {
|
|
1076
|
+
for (let i = 0; i < batchARows; i++) {
|
|
1077
|
+
for (let j = 0; j < batchBCols; j++) {
|
|
1078
|
+
for (let k = 0; k < batchACols; k++) {
|
|
1079
|
+
// Tensor values are 1D arrays so we have to get real index using strides
|
|
1080
|
+
batchC[q * batchCStrides[0] + i * batchCStrides[1] + j * batchCStrides[2]] +=
|
|
1081
|
+
batchA[q * batchAStrides[0] + i * batchAStrides[1] + k * batchAStrides[2]] *
|
|
1082
|
+
batchB[q * batchBStrides[0] + k * batchBStrides[1] + j * batchBStrides[2]];
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
const out = new Tensor(batchC, { shape: batchCShape, strides: batchCStrides });
|
|
1088
|
+
if (this.requiresGrad) {
|
|
1089
|
+
out.requiresGrad = true;
|
|
1090
|
+
out.children.push(this);
|
|
1091
|
+
}
|
|
1092
|
+
if (other.requiresGrad) {
|
|
1093
|
+
out.requiresGrad = true;
|
|
1094
|
+
out.children.push(other);
|
|
1095
|
+
}
|
|
1096
|
+
if (out.requiresGrad) {
|
|
1097
|
+
out.gradFn = () => {
|
|
1098
|
+
// Disable gradient collecting of gradients themselves
|
|
1099
|
+
const outGrad = out.grad.withGrad(false);
|
|
1100
|
+
const selfNoGrad = this.withGrad(false);
|
|
1101
|
+
const otherNoGrad = other.withGrad(false);
|
|
1102
|
+
if (this.requiresGrad)
|
|
1103
|
+
Tensor.addGrad(this, outGrad.bmm(otherNoGrad.transpose(1, 2)));
|
|
1104
|
+
if (other.requiresGrad)
|
|
1105
|
+
Tensor.addGrad(other, selfNoGrad.transpose(1, 2).bmm(outGrad));
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
return out;
|
|
1109
|
+
}
|
|
1052
1110
|
// Convert right-side 1D tensor to a vector (nx1 tensor) to do matmul
|
|
1053
1111
|
mv(other) {
|
|
1054
1112
|
other = Tensor.forceTensor(other);
|
|
@@ -1101,24 +1159,139 @@ class Tensor {
|
|
|
1101
1159
|
// Too lazy for batched matmul
|
|
1102
1160
|
throw new Error(`Shapes [${this.shape}] and [${other.shape}] are not supported`);
|
|
1103
1161
|
}
|
|
1162
|
+
// Utility to create a new tensor filled with a number
|
|
1163
|
+
static full(shape, num, options = {}) {
|
|
1164
|
+
if (shape.length === 0)
|
|
1165
|
+
return new Tensor(num, options);
|
|
1166
|
+
const outputSize = Tensor.shapeToSize(shape);
|
|
1167
|
+
const outputValue = new Array(outputSize).fill(num);
|
|
1168
|
+
return new Tensor(outputValue, { shape, ...options });
|
|
1169
|
+
}
|
|
1104
1170
|
// Utility to create a new tensor with shape of another tensor, filled with a number
|
|
1105
1171
|
static fullLike(tensor, num, options = {}) {
|
|
1106
1172
|
if (typeof tensor.value === "number")
|
|
1107
1173
|
return new Tensor(num, options);
|
|
1108
1174
|
return new Tensor(new Array(tensor.value.length).fill(num), { shape: tensor.shape, strides: tensor.strides, ...options });
|
|
1109
1175
|
}
|
|
1176
|
+
// Utility to create a new tensor filled with 1
|
|
1177
|
+
static ones(shape, options = {}) {
|
|
1178
|
+
if (typeof shape === "undefined" || shape.length === 0)
|
|
1179
|
+
return new Tensor(1, options);
|
|
1180
|
+
const outputSize = Tensor.shapeToSize(shape);
|
|
1181
|
+
const outputValue = new Array(outputSize).fill(1);
|
|
1182
|
+
return new Tensor(outputValue, { shape, ...options });
|
|
1183
|
+
}
|
|
1110
1184
|
// Utility to create a new tensor with shape of another tensor, filled with 1
|
|
1111
1185
|
static onesLike(tensor, options = {}) {
|
|
1112
1186
|
if (typeof tensor.value === "number")
|
|
1113
1187
|
return new Tensor(1, options);
|
|
1114
1188
|
return new Tensor(new Array(tensor.value.length).fill(1), { shape: tensor.shape, strides: tensor.strides, ...options });
|
|
1115
1189
|
}
|
|
1190
|
+
// Utility to create a new tensor filled with 0
|
|
1191
|
+
static zeros(shape, options = {}) {
|
|
1192
|
+
if (typeof shape === "undefined" || shape.length === 0)
|
|
1193
|
+
return new Tensor(0, options);
|
|
1194
|
+
const outputSize = Tensor.shapeToSize(shape);
|
|
1195
|
+
const outputValue = new Array(outputSize).fill(0);
|
|
1196
|
+
return new Tensor(outputValue, { shape, ...options });
|
|
1197
|
+
}
|
|
1116
1198
|
// Utility to create a new tensor with shape of another tensor, filled with 0
|
|
1117
1199
|
static zerosLike(tensor, options = {}) {
|
|
1118
1200
|
if (typeof tensor.value === "number")
|
|
1119
1201
|
return new Tensor(0, options);
|
|
1120
1202
|
return new Tensor(new Array(tensor.value.length).fill(0), { shape: tensor.shape, strides: tensor.strides, ...options });
|
|
1121
1203
|
}
|
|
1204
|
+
// Utility to create a new tensor filled with a random number with uniform distribution from 0 to 1
|
|
1205
|
+
static rand(shape, options = {}) {
|
|
1206
|
+
if (typeof shape === "undefined" || shape.length === 0)
|
|
1207
|
+
return new Tensor((0, utils_1.randUniform)(), options);
|
|
1208
|
+
const outputSize = Tensor.shapeToSize(shape);
|
|
1209
|
+
const outputValue = new Array(outputSize);
|
|
1210
|
+
for (let index = 0; index < outputValue.length; index++) {
|
|
1211
|
+
outputValue[index] = (0, utils_1.randUniform)();
|
|
1212
|
+
}
|
|
1213
|
+
return new Tensor(outputValue, { shape, ...options });
|
|
1214
|
+
}
|
|
1215
|
+
// Utility to create a new tensor with shape of another tensor, filled with a random number with uniform distribution from 0 to 1
|
|
1216
|
+
static randLike(tensor, options = {}) {
|
|
1217
|
+
if (typeof tensor.value === "number")
|
|
1218
|
+
return new Tensor((0, utils_1.randUniform)(), options);
|
|
1219
|
+
const outputValue = new Array(tensor.value.length);
|
|
1220
|
+
for (let index = 0; index < outputValue.length; index++) {
|
|
1221
|
+
outputValue[index] = (0, utils_1.randUniform)();
|
|
1222
|
+
}
|
|
1223
|
+
return new Tensor(outputValue, {
|
|
1224
|
+
shape: tensor.shape, strides: tensor.strides, ...options
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
// Utility to create a new tensor filled with a random number with normal distribution of mean=0 and stddev=1
|
|
1228
|
+
static randn(shape, options = {}) {
|
|
1229
|
+
if (typeof shape === "undefined" || shape.length === 0)
|
|
1230
|
+
return new Tensor((0, utils_1.randNormal)(), options);
|
|
1231
|
+
const outputSize = Tensor.shapeToSize(shape);
|
|
1232
|
+
const outputValue = new Array(outputSize);
|
|
1233
|
+
for (let index = 0; index < outputValue.length; index++) {
|
|
1234
|
+
outputValue[index] = (0, utils_1.randNormal)();
|
|
1235
|
+
}
|
|
1236
|
+
return new Tensor(outputValue, { shape, ...options });
|
|
1237
|
+
}
|
|
1238
|
+
// Utility to create a new tensor with shape of another tensor, filled with a random number with normal distribution of mean=0 and stddev=1
|
|
1239
|
+
static randnLike(tensor, options = {}) {
|
|
1240
|
+
if (typeof tensor.value === "number")
|
|
1241
|
+
return new Tensor((0, utils_1.randNormal)(), options);
|
|
1242
|
+
const outputValue = new Array(tensor.value.length);
|
|
1243
|
+
for (let index = 0; index < outputValue.length; index++) {
|
|
1244
|
+
outputValue[index] = (0, utils_1.randNormal)();
|
|
1245
|
+
}
|
|
1246
|
+
return new Tensor(outputValue, {
|
|
1247
|
+
shape: tensor.shape, strides: tensor.strides, ...options
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
// Utility to create a new tensor filled with a random integer between low and high
|
|
1251
|
+
static randint(shape, low, high, options = {}) {
|
|
1252
|
+
if (shape.length === 0)
|
|
1253
|
+
return new Tensor((0, utils_1.randInt)(low, high), options);
|
|
1254
|
+
const outputSize = Tensor.shapeToSize(shape);
|
|
1255
|
+
const outputValue = new Array(outputSize);
|
|
1256
|
+
for (let index = 0; index < outputValue.length; index++) {
|
|
1257
|
+
outputValue[index] = (0, utils_1.randInt)(low, high);
|
|
1258
|
+
}
|
|
1259
|
+
return new Tensor(outputValue, { shape, ...options });
|
|
1260
|
+
}
|
|
1261
|
+
// Utility to create a new tensor with shape of another tensor, filled with a random integer between low and high
|
|
1262
|
+
static randintLike(tensor, low, high, options = {}) {
|
|
1263
|
+
if (typeof tensor.value === "number")
|
|
1264
|
+
return new Tensor((0, utils_1.randInt)(low, high), options);
|
|
1265
|
+
const outputValue = new Array(tensor.value.length);
|
|
1266
|
+
for (let index = 0; index < outputValue.length; index++) {
|
|
1267
|
+
outputValue[index] = (0, utils_1.randInt)(low, high);
|
|
1268
|
+
}
|
|
1269
|
+
return new Tensor(outputValue, {
|
|
1270
|
+
shape: tensor.shape, strides: tensor.strides, ...options
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
// Utility to create a new tensor filled with a random number with normal distribution of custom mean and stddev
|
|
1274
|
+
static normal(shape, mean, stdDev, options = {}) {
|
|
1275
|
+
if (shape.length === 0)
|
|
1276
|
+
return new Tensor((0, utils_1.randNormal)(mean, stdDev), options);
|
|
1277
|
+
const outputSize = Tensor.shapeToSize(shape);
|
|
1278
|
+
const outputValue = new Array(outputSize);
|
|
1279
|
+
for (let index = 0; index < outputValue.length; index++) {
|
|
1280
|
+
outputValue[index] = (0, utils_1.randNormal)(mean, stdDev);
|
|
1281
|
+
}
|
|
1282
|
+
return new Tensor(outputValue, { shape, ...options });
|
|
1283
|
+
}
|
|
1284
|
+
// Utility to create a new tensor filled with a random number with uniform distribution from low to high
|
|
1285
|
+
static uniform(shape, low, high, options = {}) {
|
|
1286
|
+
if (shape.length === 0)
|
|
1287
|
+
return new Tensor((0, utils_1.randUniform)(low, high), options);
|
|
1288
|
+
const outputSize = Tensor.shapeToSize(shape);
|
|
1289
|
+
const outputValue = new Array(outputSize);
|
|
1290
|
+
for (let index = 0; index < outputValue.length; index++) {
|
|
1291
|
+
outputValue[index] = (0, utils_1.randUniform)(low, high);
|
|
1292
|
+
}
|
|
1293
|
+
return new Tensor(outputValue, { shape, ...options });
|
|
1294
|
+
}
|
|
1122
1295
|
// Reverse-mode autodiff call
|
|
1123
1296
|
backward() {
|
|
1124
1297
|
// Build topological order
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export declare function erf(x: number): number;
|
|
2
2
|
export declare function erfc(x: number): number;
|
|
3
3
|
export declare function erfinv(x: number): number;
|
|
4
|
+
export declare function randUniform(low?: number, high?: number): number;
|
|
5
|
+
export declare function randNormal(mean?: number, stdDev?: number): number;
|
|
6
|
+
export declare function randInt(low: number, high: number): number;
|
package/dist/utils.js
CHANGED
|
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.erf = erf;
|
|
4
4
|
exports.erfc = erfc;
|
|
5
5
|
exports.erfinv = erfinv;
|
|
6
|
+
exports.randUniform = randUniform;
|
|
7
|
+
exports.randNormal = randNormal;
|
|
8
|
+
exports.randInt = randInt;
|
|
6
9
|
// Error function using Abramowitz and Stegun approximation
|
|
7
10
|
function erf(x) {
|
|
8
11
|
const a1 = 0.254829592;
|
|
@@ -33,3 +36,15 @@ function erfinv(x) {
|
|
|
33
36
|
const sign = x >= 0 ? 1 : -1;
|
|
34
37
|
return sign * Math.sqrt(-part1 + Math.sqrt(part1 * part1 - part2));
|
|
35
38
|
}
|
|
39
|
+
function randUniform(low = 0, high = 1) {
|
|
40
|
+
return Math.random() * (high - low) + low;
|
|
41
|
+
}
|
|
42
|
+
function randNormal(mean = 0, stdDev = 1) {
|
|
43
|
+
const u = 1 - Math.random();
|
|
44
|
+
const v = 1 - Math.random();
|
|
45
|
+
const z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
|
|
46
|
+
return z * stdDev + mean;
|
|
47
|
+
}
|
|
48
|
+
function randInt(low, high) {
|
|
49
|
+
return Math.floor(Math.random() * (high - low) + low);
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "catniff",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "0.2.14",
|
|
4
|
+
"description": "A small Torch-like deep learning framework for Javascript with tensor and autograd support",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"url": "git+https://github.com/nguyenphuminh/catniff.git"
|
|
12
12
|
},
|
|
13
13
|
"keywords": [
|
|
14
|
-
"cats",
|
|
15
14
|
"catniff",
|
|
16
15
|
"autograd",
|
|
17
16
|
"autodiff",
|
|
@@ -27,6 +26,7 @@
|
|
|
27
26
|
"machine-learning",
|
|
28
27
|
"deep-learning",
|
|
29
28
|
"micrograd",
|
|
29
|
+
"tinygrad",
|
|
30
30
|
"torch",
|
|
31
31
|
"pytorch"
|
|
32
32
|
],
|