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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Catniff
2
2
 
3
- Catniff is an experimental tensor ops library and autograd engine made to be Torch-like (its name is a play on "catnip" and "differentiation"). This project is heavily under development currently, so keep in mind that APIs can be completely unstable and backwards-incompatible.
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
- I'm mostly just learning and playing with this currently, so there are no concrete plans yet, but here is what I currently have in mind:
84
-
85
- * Fix whatever is the problem right now (there are a lot of problems right now lol).
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
- * Refactor code.
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.12",
4
- "description": "A cute autograd engine for Javascript",
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
  ],