UNPKG

@tensorflow/tfjs-node

Version:

This repository provides native TensorFlow execution in backend JavaScript applications under the Node.js runtime, accelerated by the TensorFlow C binary under the hood. It provides the same API as [TensorFlow.js](https://js.tensorflow.org/api/latest/).

573 lines (572 loc) 29.2 kB
"use strict"; /** * @license * Copyright 2018 Google LLC. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============================================================================= */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); var tf = require("@tensorflow/tfjs"); var fs = require("fs"); var path = require("path"); var rimraf = require("rimraf"); var util_1 = require("util"); var tfn = require("../index"); var file_system_1 = require("./file_system"); describe('File system IOHandler', function () { var mkdtemp = (0, util_1.promisify)(fs.mkdtemp); var readFile = (0, util_1.promisify)(fs.readFile); var writeFile = (0, util_1.promisify)(fs.writeFile); var rimrafPromise = (0, util_1.promisify)(rimraf); var modelTopology1 = { 'class_name': 'Sequential', 'keras_version': '2.1.6', 'config': [{ 'class_name': 'Dense', 'config': { 'kernel_initializer': { 'class_name': 'VarianceScaling', 'config': { 'distribution': 'uniform', 'scale': 1.0, 'seed': null, 'mode': 'fan_avg' } }, 'name': 'dense', 'kernel_constraint': null, 'bias_regularizer': null, 'bias_constraint': null, 'dtype': 'float32', 'activation': 'linear', 'trainable': true, 'kernel_regularizer': null, 'bias_initializer': { 'class_name': 'Zeros', 'config': {} }, 'units': 1, 'batch_input_shape': [null, 3], 'use_bias': true, 'activity_regularizer': null } }], 'backend': 'tensorflow' }; var weightSpecs1 = [ { name: 'dense/kernel', shape: [3, 1], dtype: 'float32', }, { name: 'dense/bias', shape: [1], dtype: 'float32', } ]; var weightData1 = new ArrayBuffer(16); var trainingConfig1 = { loss: 'categorical_crossentropy', metrics: ['accuracy'], optimizer_config: { class_name: 'SGD', config: { learningRate: 0.1 } } }; var testDir; beforeEach(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, mkdtemp('tfjs_node_fs_test')]; case 1: testDir = _a.sent(); return [2 /*return*/]; } }); }); }); afterEach(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, rimrafPromise(testDir)]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }); it('save succeeds with newly created directory', function () { return __awaiter(void 0, void 0, void 0, function () { var t0, dir, handler, saveResult, modelJSONPath, weightsBinPath, modelJSON, _a, _b, weightData, _c; return __generator(this, function (_d) { switch (_d.label) { case 0: t0 = new Date(); dir = path.join(testDir, 'save-destination'); handler = tf.io.getSaveHandlers("file://".concat(dir))[0]; return [4 /*yield*/, handler.save({ modelTopology: modelTopology1, weightSpecs: weightSpecs1, weightData: weightData1, })]; case 1: saveResult = _d.sent(); expect(saveResult.modelArtifactsInfo.dateSaved.getTime()) .toBeGreaterThanOrEqual(t0.getTime()); expect(saveResult.modelArtifactsInfo.modelTopologyType) .toEqual('JSON'); modelJSONPath = path.join(dir, 'model.json'); weightsBinPath = path.join(dir, 'weights.bin'); _b = (_a = JSON).parse; return [4 /*yield*/, readFile(modelJSONPath, 'utf8')]; case 2: modelJSON = _b.apply(_a, [_d.sent()]); expect(modelJSON.modelTopology).toEqual(modelTopology1); expect(modelJSON.weightsManifest.length).toEqual(1); expect(modelJSON.weightsManifest[0].paths).toEqual(['weights.bin']); expect(modelJSON.weightsManifest[0].weights).toEqual(weightSpecs1); _c = Uint8Array.bind; return [4 /*yield*/, readFile(weightsBinPath)]; case 3: weightData = new (_c.apply(Uint8Array, [void 0, _d.sent()]))(); expect(weightData.length).toEqual(16); weightData.forEach(function (value) { return expect(value).toEqual(0); }); return [2 /*return*/]; } }); }); }); it('save fails if path exists as a file', function () { return __awaiter(void 0, void 0, void 0, function () { var dir, handler; return __generator(this, function (_a) { switch (_a.label) { case 0: dir = path.join(testDir, 'save-destination'); // Create a file at the locatin. return [4 /*yield*/, writeFile(dir, 'foo')]; case 1: // Create a file at the locatin. _a.sent(); handler = tf.io.getSaveHandlers("file://".concat(dir))[0]; return [4 /*yield*/, expectAsync(handler.save({ modelTopology: modelTopology1, weightSpecs: weightSpecs1, weightData: weightData1, })).toBeRejectedWithError(/.*exists as a file.*directory.*/)]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }); it('save-load round trip: one weight file', function (done) { var handler1 = tf.io.getSaveHandlers("file://".concat(testDir))[0]; handler1 .save({ modelTopology: modelTopology1, weightSpecs: weightSpecs1, weightData: weightData1, trainingConfig: trainingConfig1, }) .then(function (saveResult) { var modelJSONPath = path.join(testDir, 'model.json'); var handler2 = tf.io.getLoadHandlers("file://".concat(modelJSONPath))[0]; handler2.load() .then(function (modelArtifacts) { expect(modelArtifacts.modelTopology).toEqual(modelTopology1); expect(modelArtifacts.weightSpecs).toEqual(weightSpecs1); expect(modelArtifacts.trainingConfig).toEqual(trainingConfig1); expect(new Float32Array(tf.io.CompositeArrayBuffer.join(modelArtifacts.weightData))) .toEqual(new Float32Array([0, 0, 0, 0])); done(); }) .catch(function (err) { return done.fail(err.stack); }); }) .catch(function (err) { return done.fail(err.stack); }); }); describe('load json model', function () { it('load: two weight files', function () { return __awaiter(void 0, void 0, void 0, function () { var weightsManifest, modelJSON, modelJSONPath, weightsData1, weightsData2, handler, modelArtifacts; return __generator(this, function (_a) { switch (_a.label) { case 0: weightsManifest = [ { paths: ['weights.1.bin'], weights: [{ name: 'dense/kernel', shape: [3, 1], dtype: 'float32', }], }, { paths: ['weights.2.bin'], weights: [{ name: 'dense/bias', shape: [1], dtype: 'float32', }] } ]; modelJSON = { modelTopology: modelTopology1, weightsManifest: weightsManifest, }; modelJSONPath = path.join(testDir, 'model.json'); return [4 /*yield*/, writeFile(modelJSONPath, JSON.stringify(modelJSON), 'utf8')]; case 1: _a.sent(); weightsData1 = Buffer.from(new Float32Array([-1.1, -3.3, -3.3]).buffer); return [4 /*yield*/, writeFile(path.join(testDir, 'weights.1.bin'), weightsData1, 'binary')]; case 2: _a.sent(); weightsData2 = Buffer.from(new Float32Array([-7.7]).buffer); return [4 /*yield*/, writeFile(path.join(testDir, 'weights.2.bin'), weightsData2, 'binary')]; case 3: _a.sent(); handler = tf.io.getLoadHandlers("file://".concat(modelJSONPath))[0]; return [4 /*yield*/, handler.load()]; case 4: modelArtifacts = _a.sent(); expect(modelArtifacts.modelTopology).toEqual(modelTopology1); expect(modelArtifacts.weightSpecs).toEqual([ { name: 'dense/kernel', shape: [3, 1], dtype: 'float32', }, { name: 'dense/bias', shape: [1], dtype: 'float32', } ]); tf.test_util.expectArraysClose(new Float32Array(tf.io.CompositeArrayBuffer.join(modelArtifacts.weightData)), new Float32Array([-1.1, -3.3, -3.3, -7.7])); return [2 /*return*/]; } }); }); }); it('loading from nonexistent model.json path fails', function (done) { var handler = tf.io.getLoadHandlers("file://".concat(testDir, "/foo/model.json"))[0]; handler.load() .then(function (getModelArtifactsInfoForJSON) { done.fail('Loading from nonexisting model.json path succeeded ' + 'unexpectedly.'); }) .catch(function (err) { expect(err.message) .toMatch(/model\.json.*does not exist.*loading failed/); done(); }); }); it('loading from missing weights path fails', function () { return __awaiter(void 0, void 0, void 0, function () { var weightsManifest, modelJSON, modelJSONPath, weightsData1, handler; return __generator(this, function (_a) { switch (_a.label) { case 0: weightsManifest = [ { paths: ['weights.1.bin'], weights: [{ name: 'dense/kernel', shape: [3, 1], dtype: 'float32', }], }, { paths: ['weights.2.bin'], weights: [{ name: 'dense/bias', shape: [1], dtype: 'float32', }] } ]; modelJSON = { modelTopology: modelTopology1, weightsManifest: weightsManifest, }; modelJSONPath = path.join(testDir, 'model.json'); return [4 /*yield*/, writeFile(modelJSONPath, JSON.stringify(modelJSON), 'utf8')]; case 1: _a.sent(); weightsData1 = Buffer.from(new Float32Array([-1.1, -3.3, -3.3]).buffer); return [4 /*yield*/, writeFile(path.join(testDir, 'weights.1.bin'), weightsData1, 'binary')]; case 2: _a.sent(); handler = tf.io.getLoadHandlers("file://".concat(modelJSONPath))[0]; return [4 /*yield*/, expectAsync(handler.load()).toBeRejectedWithError(/Weight file .*weights\.2\.bin does not exist/)]; case 3: _a.sent(); return [2 /*return*/]; } }); }); }); }); describe('load binary model', function () { it('load: two weight files', function () { return __awaiter(void 0, void 0, void 0, function () { var weightsManifest, modelPath, modelData, modelManifestJSONPath, weightsData1, weightsData2, handler, modelArtifacts; return __generator(this, function (_a) { switch (_a.label) { case 0: weightsManifest = [ { paths: ['weights.1.bin'], weights: [{ name: 'dense/kernel', shape: [3, 1], dtype: 'float32', }], }, { paths: ['weights.2.bin'], weights: [{ name: 'dense/bias', shape: [1], dtype: 'float32', }] } ]; modelPath = path.join(testDir, 'model.pb'); modelData = Buffer.from(new Uint8Array([1, 2, 3]).buffer); return [4 /*yield*/, writeFile(modelPath, modelData, 'binary')]; case 1: _a.sent(); modelManifestJSONPath = path.join(testDir, 'manifest.json'); return [4 /*yield*/, writeFile(modelManifestJSONPath, JSON.stringify(weightsManifest), 'utf8')]; case 2: _a.sent(); weightsData1 = Buffer.from(new Float32Array([-1.1, -3.3, -3.3]).buffer); return [4 /*yield*/, writeFile(path.join(testDir, 'weights.1.bin'), weightsData1, 'binary')]; case 3: _a.sent(); weightsData2 = Buffer.from(new Float32Array([-7.7]).buffer); return [4 /*yield*/, writeFile(path.join(testDir, 'weights.2.bin'), weightsData2, 'binary')]; case 4: _a.sent(); handler = new file_system_1.NodeFileSystem(["".concat(modelPath), "".concat(modelManifestJSONPath)]); return [4 /*yield*/, handler.load()]; case 5: modelArtifacts = _a.sent(); tf.test_util.expectArraysClose(new Uint8Array(modelArtifacts.modelTopology), new Uint8Array(modelData)); expect(modelArtifacts.weightSpecs).toEqual([ { name: 'dense/kernel', shape: [3, 1], dtype: 'float32', }, { name: 'dense/bias', shape: [1], dtype: 'float32', } ]); tf.test_util.expectArraysClose(new Float32Array(tf.io.CompositeArrayBuffer.join(modelArtifacts.weightData)), new Float32Array([-1.1, -3.3, -3.3, -7.7])); return [2 /*return*/]; } }); }); }); it('path length does not equal 2 fails', function () { expect(function () { return new file_system_1.NodeFileSystem(["".concat(testDir, "/foo/model.pb")]); }) .toThrowError(/file paths must have a length of 2.*actual length is 1.*/); }); it('loading from nonexistent model.json path fails', function (done) { var handler = new file_system_1.NodeFileSystem(["".concat(testDir, "/foo/model.pb"), "".concat(testDir, "/foo/manifest.json")]); handler.load() .then(function (getModelArtifactsInfoForJSON) { done.fail('Loading from nonexisting model.pb path succeeded ' + 'unexpectedly.'); }) .catch(function (err) { expect(err.message) .toMatch(/model\.pb.*does not exist.*loading failed/); done(); }); }); it('loading from missing weights path fails', function () { return __awaiter(void 0, void 0, void 0, function () { var weightsManifest, modelPath, modelData, modelManifestJSONPath, weightsData1, handler; return __generator(this, function (_a) { switch (_a.label) { case 0: weightsManifest = [ { paths: ['weights.1.bin'], weights: [{ name: 'dense/kernel', shape: [3, 1], dtype: 'float32', }], }, { paths: ['weights.2.bin'], weights: [{ name: 'dense/bias', shape: [1], dtype: 'float32', }] } ]; modelPath = path.join(testDir, 'model.pb'); modelData = Buffer.from(new Uint8Array([1, 2, 3]).buffer); return [4 /*yield*/, writeFile(modelPath, modelData, 'binary')]; case 1: _a.sent(); modelManifestJSONPath = path.join(testDir, 'manifest.json'); return [4 /*yield*/, writeFile(modelManifestJSONPath, JSON.stringify(weightsManifest), 'utf8')]; case 2: _a.sent(); weightsData1 = Buffer.from(new Float32Array([-1.1, -3.3, -3.3]).buffer); return [4 /*yield*/, writeFile(path.join(testDir, 'weights.1.bin'), weightsData1, 'binary')]; case 3: _a.sent(); handler = new file_system_1.NodeFileSystem(["".concat(modelPath), "".concat(modelManifestJSONPath)]); return [4 /*yield*/, expectAsync(handler.load()).toBeRejectedWithError(/Weight file .*weights\.2\.bin does not exist/)]; case 4: _a.sent(); return [2 /*return*/]; } }); }); }); }); it('Exported file-system handler class exists', function () { var handler = tfn.io.fileSystem(testDir); expect(typeof handler.save).toEqual('function'); expect(typeof handler.load).toEqual('function'); }); it('Save and load model with loss and optimizer', function () { return __awaiter(void 0, void 0, void 0, function () { var model, xs, ys, saveURL, loadURL, model2, optimizerConfig, history2; return __generator(this, function (_a) { switch (_a.label) { case 0: model = tf.sequential(); model.add(tf.layers.dense({ units: 1, kernelInitializer: 'zeros', inputShape: [1] })); model.compile({ loss: 'meanSquaredError', optimizer: tf.train.adam(2.5e-2) }); xs = tf.tensor2d([1, 2, 3, 4], [4, 1]); ys = tf.tensor2d([-1, -3, -5, -7], [4, 1]); return [4 /*yield*/, model.fit(xs, ys, { epochs: 2, shuffle: false, verbose: 0 })]; case 1: _a.sent(); saveURL = "file://".concat(testDir); loadURL = "file://".concat(testDir, "/model.json"); return [4 /*yield*/, model.save(saveURL, { includeOptimizer: true })]; case 2: _a.sent(); return [4 /*yield*/, tf.loadLayersModel(loadURL)]; case 3: model2 = _a.sent(); optimizerConfig = model2.optimizer.getConfig(); expect(model2.optimizer.getClassName()).toEqual('Adam'); expect(optimizerConfig['learningRate']).toEqual(2.5e-2); return [4 /*yield*/, model2.fit(xs, ys, { epochs: 2, shuffle: false, verbose: 0 })]; case 4: history2 = _a.sent(); // The final loss value from training the model twice, 2 epochs // at a time, should be equal to the final loss of trainig the // model only once with 4 epochs. expect(history2.history.loss[1]).toBeCloseTo(18.603); return [2 /*return*/]; } }); }); }); it('Save and load model with user-defined metadata', function () { return __awaiter(void 0, void 0, void 0, function () { var model, saveURL, loadURL, model2; return __generator(this, function (_a) { switch (_a.label) { case 0: model = tf.sequential(); model.add(tf.layers.dense({ units: 3, inputShape: [4] })); model.setUserDefinedMetadata({ 'outputLabels': ['Label1', 'Label2', 'Label3'] }); saveURL = "file://".concat(testDir); loadURL = "file://".concat(testDir, "/model.json"); return [4 /*yield*/, model.save(saveURL)]; case 1: _a.sent(); return [4 /*yield*/, tf.loadLayersModel(loadURL)]; case 2: model2 = _a.sent(); expect(model2.getUserDefinedMetadata()).toEqual({ 'outputLabels': ['Label1', 'Label2', 'Label3'] }); return [2 /*return*/]; } }); }); }); it('Compile, save with includeOptimizer: true, load and fit model', function () { return __awaiter(void 0, void 0, void 0, function () { var model, saveURL, loadURL, model2; return __generator(this, function (_a) { switch (_a.label) { case 0: model = tf.sequential(); model.add(tf.layers.dense({ units: 3, inputShape: [4] })); model.compile({ optimizer: 'sgd', loss: 'meanSquaredError', metrics: ['accuracy'] }); saveURL = "file://".concat(testDir); loadURL = "file://".concat(testDir, "/model.json"); return [4 /*yield*/, model.save(saveURL, { includeOptimizer: true })]; case 1: _a.sent(); return [4 /*yield*/, tf.loadLayersModel(loadURL)]; case 2: model2 = _a.sent(); expect(function () { return model2.fit(tf.zeros([1, 4]), tf.zeros([1, 3])); }) .not.toThrow(); return [2 /*return*/]; } }); }); }); describe('nodeFileSystemRouter', function () { it('should handle single path', function () { expect((0, file_system_1.nodeFileSystemRouter)('file://model.json')).toBeDefined(); }); it('should handle multiple paths', function () { expect((0, file_system_1.nodeFileSystemRouter)([ 'file://model.json', 'file://weights.json' ])).toBeDefined(); }); it('should return null for non file path', function () { expect((0, file_system_1.nodeFileSystemRouter)('http://model.json')).toBeNull(); }); it('should return null for multiple paths with mismatched scheme', function () { expect((0, file_system_1.nodeFileSystemRouter)([ 'file://model.json', 'http://weights.json' ])).toBeNull(); }); }); });