@epfml/discojs-web 2.1.2-p20240506085037.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.
@@ -0,0 +1,5 @@
1
+ import * as tf from '@tensorflow/tfjs';
2
+ import { data } from '@epfml/discojs';
3
+ export declare class ImageLoader extends data.ImageLoader<File> {
4
+ readImageFrom(source: File): Promise<tf.Tensor3D>;
5
+ }
@@ -0,0 +1,7 @@
1
+ import * as tf from '@tensorflow/tfjs';
2
+ import { data } from '@epfml/discojs';
3
+ export class ImageLoader extends data.ImageLoader {
4
+ async readImageFrom(source) {
5
+ return tf.browser.fromPixels(await createImageBitmap(source));
6
+ }
7
+ }
@@ -0,0 +1,3 @@
1
+ export { ImageLoader as WebImageLoader } from './image_loader.js';
2
+ export { TabularLoader as WebTabularLoader } from './tabular_loader.js';
3
+ export { TextLoader as WebTextLoader } from './text_loader.js';
@@ -0,0 +1,3 @@
1
+ export { ImageLoader as WebImageLoader } from './image_loader.js';
2
+ export { TabularLoader as WebTabularLoader } from './tabular_loader.js';
3
+ export { TextLoader as WebTextLoader } from './text_loader.js';
@@ -0,0 +1,4 @@
1
+ import { data } from '@epfml/discojs';
2
+ export declare class TabularLoader extends data.TabularLoader<File> {
3
+ loadDatasetFrom(source: File, csvConfig: Record<string, unknown>): Promise<data.Dataset>;
4
+ }
@@ -0,0 +1,8 @@
1
+ import * as tf from '@tensorflow/tfjs';
2
+ import { data } from '@epfml/discojs';
3
+ export class TabularLoader extends data.TabularLoader {
4
+ async loadDatasetFrom(source, csvConfig) {
5
+ const file = new tf.data.FileDataSource(source);
6
+ return Promise.resolve(new tf.data.CSVDataset(file, csvConfig));
7
+ }
8
+ }
@@ -0,0 +1,4 @@
1
+ import { data } from '@epfml/discojs';
2
+ export declare class TextLoader extends data.TextLoader<File> {
3
+ loadDatasetFrom(source: File): Promise<data.Dataset>;
4
+ }
@@ -0,0 +1,9 @@
1
+ import * as tf from '@tensorflow/tfjs';
2
+ import { data } from '@epfml/discojs';
3
+ export class TextLoader extends data.TextLoader {
4
+ loadDatasetFrom(source) {
5
+ const file = new tf.data.FileDataSource(source);
6
+ const dataset = new tf.data.TextLineDataset(file).filter(s => s != ' '); // newline creates empty strings
7
+ return Promise.resolve(dataset);
8
+ }
9
+ }
@@ -0,0 +1,2 @@
1
+ export * from './data/index.js';
2
+ export * from './memory/index.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './data/index.js';
2
+ export * from './memory/index.js';
@@ -0,0 +1 @@
1
+ export { IndexedDB } from './memory.js';
@@ -0,0 +1 @@
1
+ export { IndexedDB } from './memory.js';
@@ -0,0 +1,31 @@
1
+ import * as tf from '@tensorflow/tfjs';
2
+ import type { Path, Model, ModelInfo, ModelSource } from '@epfml/discojs';
3
+ import { Memory } from '@epfml/discojs';
4
+ export declare class IndexedDB extends Memory {
5
+ pathFor(source: ModelSource): Path;
6
+ infoFor(source: ModelSource): ModelInfo;
7
+ getModelMetadata(source: ModelSource): Promise<tf.io.ModelArtifactsInfo | undefined>;
8
+ contains(source: ModelSource): Promise<boolean>;
9
+ getModel(source: ModelSource): Promise<Model>;
10
+ deleteModel(source: ModelSource): Promise<void>;
11
+ loadModel(source: ModelSource): Promise<void>;
12
+ /**
13
+ * Saves the working model to the source.
14
+ * @param source the destination
15
+ * @param model the model
16
+ */
17
+ updateWorkingModel(source: ModelSource, model: Model): Promise<void>;
18
+ /**
19
+ * Creates a saved copy of the working model corresponding to the source.
20
+ * @param source the source
21
+ */
22
+ saveWorkingModel(source: ModelSource): Promise<Path>;
23
+ saveModel(source: ModelSource, model: Model): Promise<Path>;
24
+ /**
25
+ * Downloads the model corresponding to the source.
26
+ * @param source the source
27
+ */
28
+ downloadModel(source: ModelSource): Promise<void>;
29
+ latestDuplicate(source: ModelSource): Promise<number | undefined>;
30
+ duplicateSource(source: ModelSource): Promise<ModelInfo>;
31
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Helper functions used to load and save TFJS models from IndexedDB. The
3
+ * working model is the model currently being trained for a task. Saved models
4
+ * are models that were explicitly saved to IndexedDB. The two working/ and saved/
5
+ * folders are invisible to the user. The user only interacts with the saved/
6
+ * folder via the model library. The working/ folder is only used by the backend.
7
+ * The working model is loaded from IndexedDB for training (model.fit) only.
8
+ */
9
+ import { Map } from 'immutable';
10
+ import * as tf from '@tensorflow/tfjs';
11
+ import { Memory, ModelType, models } from '@epfml/discojs';
12
+ export class IndexedDB extends Memory {
13
+ pathFor(source) {
14
+ if (typeof source === 'string') {
15
+ return source;
16
+ }
17
+ if (source.type === undefined || source.taskID === undefined || source.name === undefined) {
18
+ throw new TypeError('source incomplete');
19
+ }
20
+ const version = source.version ?? 0;
21
+ return `indexeddb://${source.type}/${source.taskID}/${source.name}@${version}`;
22
+ }
23
+ infoFor(source) {
24
+ if (typeof source !== 'string') {
25
+ return source;
26
+ }
27
+ const [stringType, taskID, fullName] = source.split('/').splice(2);
28
+ const type = stringType === 'working' ? ModelType.WORKING : ModelType.SAVED;
29
+ const [name, versionSuffix] = fullName.split('@');
30
+ const version = versionSuffix === undefined ? 0 : Number(versionSuffix);
31
+ return { type, taskID, name, version };
32
+ }
33
+ async getModelMetadata(source) {
34
+ const models = await tf.io.listModels();
35
+ return models[this.pathFor(source)];
36
+ }
37
+ async contains(source) {
38
+ return await this.getModelMetadata(source) !== undefined;
39
+ }
40
+ async getModel(source) {
41
+ return new models.TFJS(await tf.loadLayersModel(this.pathFor(source)));
42
+ }
43
+ async deleteModel(source) {
44
+ await tf.io.removeModel(this.pathFor(source));
45
+ }
46
+ async loadModel(source) {
47
+ const src = this.infoFor(source);
48
+ if (src.type === ModelType.WORKING) {
49
+ // Model is already loaded
50
+ return;
51
+ }
52
+ await tf.io.copyModel(this.pathFor(src), this.pathFor({ ...src, type: ModelType.WORKING, version: 0 }));
53
+ }
54
+ /**
55
+ * Saves the working model to the source.
56
+ * @param source the destination
57
+ * @param model the model
58
+ */
59
+ async updateWorkingModel(source, model) {
60
+ const src = this.infoFor(source);
61
+ if (src.type !== undefined && src.type !== ModelType.WORKING) {
62
+ throw new Error('expected working model');
63
+ }
64
+ if (model instanceof models.TFJS) {
65
+ await model.extract().save(this.pathFor({ ...src, type: ModelType.WORKING, version: 0 }), { includeOptimizer: true });
66
+ }
67
+ else {
68
+ throw new Error('unknown model type');
69
+ }
70
+ // Enforce version 0 to always keep a single working model at a time
71
+ }
72
+ /**
73
+ * Creates a saved copy of the working model corresponding to the source.
74
+ * @param source the source
75
+ */
76
+ async saveWorkingModel(source) {
77
+ const src = this.infoFor(source);
78
+ if (src.type !== undefined && src.type !== ModelType.WORKING) {
79
+ throw new Error('expected working model');
80
+ }
81
+ const dst = this.pathFor(await this.duplicateSource({ ...src, type: ModelType.SAVED }));
82
+ await tf.io.copyModel(this.pathFor({ ...src, type: ModelType.WORKING }), dst);
83
+ return dst;
84
+ }
85
+ async saveModel(source, model) {
86
+ const src = this.infoFor(source);
87
+ if (src.type !== undefined && src.type !== ModelType.SAVED) {
88
+ throw new Error('expected saved model');
89
+ }
90
+ const dst = this.pathFor(await this.duplicateSource({ ...src, type: ModelType.SAVED }));
91
+ if (model instanceof models.TFJS) {
92
+ await model.extract().save(dst, { includeOptimizer: true });
93
+ }
94
+ else {
95
+ throw new Error('unknown model type');
96
+ }
97
+ return dst;
98
+ }
99
+ /**
100
+ * Downloads the model corresponding to the source.
101
+ * @param source the source
102
+ */
103
+ async downloadModel(source) {
104
+ const src = this.infoFor(source);
105
+ await tf.io.copyModel(this.pathFor(source), `downloads://${src.taskID}_${src.name}`);
106
+ }
107
+ async latestDuplicate(source) {
108
+ if (typeof source !== 'string') {
109
+ source = this.pathFor({ ...source, version: 0 });
110
+ }
111
+ // perform a single memory read
112
+ const paths = Map(await tf.io.listModels());
113
+ if (!paths.has(source)) {
114
+ return undefined;
115
+ }
116
+ const latest = Map(paths)
117
+ .keySeq()
118
+ .toList()
119
+ .map((p) => this.infoFor(p).version)
120
+ .max();
121
+ if (latest === undefined) {
122
+ return 0;
123
+ }
124
+ return latest;
125
+ }
126
+ async duplicateSource(source) {
127
+ const latestDuplicate = await this.latestDuplicate(source);
128
+ source = this.infoFor(source);
129
+ if (latestDuplicate === undefined) {
130
+ return source;
131
+ }
132
+ return { ...source, version: latestDuplicate + 1 };
133
+ }
134
+ }
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@epfml/discojs-web",
3
+ "version": "2.1.2-p20240506085037.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "watch": "nodemon --ext ts --ignore dist --watch ../discojs/dist --watch . --exec npm run",
9
+ "build": "tsc",
10
+ "lint": "npx eslint .",
11
+ "test": ": nothing"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/epfml/disco.git"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/epfml/disco/issues"
19
+ },
20
+ "homepage": "https://github.com/epfml/disco#readme",
21
+ "dependencies": {
22
+ "@epfml/discojs": "*",
23
+ "@tensorflow/tfjs": "4"
24
+ },
25
+ "devDependencies": {
26
+ "nodemon": "3"
27
+ }
28
+ }