From 7562da244513f43e8e9d4ccbfb490c80f81f2704 Mon Sep 17 00:00:00 2001 From: Dan Crescimanno Date: Sat, 30 Apr 2022 00:18:54 -0700 Subject: [PATCH 1/6] feat: first pass at removing tensorflow from bundle --- package.json | 3 +- src/cluster/KMeans.test.ts | 5 +- src/cluster/KMeans.ts | 68 +++++++++------- src/compose/ColumnTransformer.test.ts | 11 ++- src/compose/ColumnTransformer.ts | 33 ++++---- src/datasets/makeRegression.test.ts | 5 +- src/datasets/makeRegression.ts | 15 ++-- src/dummy/DummyClassifier.test.ts | 4 +- src/dummy/DummyClassifier.ts | 18 +++-- src/dummy/DummyRegressor.test.ts | 4 +- src/dummy/DummyRegressor.ts | 10 ++- src/ensemble/VotingClassifier.test.ts | 13 ++- src/ensemble/VotingClassifier.ts | 26 +++--- src/ensemble/VotingRegressor.test.ts | 12 ++- src/ensemble/VotingRegressor.ts | 11 +-- src/ensemble/serializeEnsemble.ts | 6 +- src/impute/SimpleImputer.test.ts | 5 +- src/impute/SimpleImputer.ts | 46 ++++++----- src/index.ts | 5 ++ src/linear_model/ElasticNet.ts | 8 +- src/linear_model/LassoRegression.ts | 8 +- src/linear_model/LinearRegression.test.ts | 5 +- src/linear_model/LinearRegression.ts | 8 +- src/linear_model/LogisticRegression.test.ts | 5 +- src/linear_model/LogisticRegression.ts | 8 +- src/linear_model/RidgeRegression.ts | 8 +- src/linear_model/SgdClassifier.ts | 89 +++++++++++++-------- src/linear_model/SgdRegressor.ts | 68 ++++++++++------ src/linear_model/modelSerializer.ts | 13 +-- src/metrics/metrics.test.ts | 3 + src/metrics/metrics.ts | 14 +++- src/model_selection/CrossValidator.ts | 4 +- src/model_selection/KFold.test.ts | 7 +- src/model_selection/KFold.ts | 11 +-- src/model_selection/crossValScore.ts | 8 +- src/model_selection/scorers.ts | 9 ++- src/model_selection/trainTestSplit.test.ts | 11 +-- src/model_selection/trainTestSplit.ts | 5 +- src/naive_bayes/BaseNaiveBayes.ts | 74 ++++++++--------- src/naive_bayes/GaussianNB.test.ts | 4 +- src/naive_bayes/GaussianNB.ts | 15 ++-- src/neighbors/BruteNeighborhood.test.ts | 3 + src/neighbors/BruteNeighborhood.ts | 30 ++++--- src/neighbors/CappedMaxHeap.ts | 6 +- src/neighbors/KNeighborsBase.ts | 18 +++-- src/neighbors/KNeighborsClassifier.test.ts | 8 +- src/neighbors/KNeighborsClassifier.ts | 11 ++- src/neighbors/KNeighborsRegressor.test.ts | 9 +-- src/neighbors/KNeighborsRegressor.ts | 5 +- src/neighbors/KdTree.test.ts | 3 + src/neighbors/KdTree.ts | 30 ++++--- src/neighbors/Metric.test.ts | 4 +- src/neighbors/Metric.ts | 33 ++++---- src/neighbors/Neighborhood.ts | 8 +- src/neighbors/neighborhoodGenericTests.ts | 4 +- src/pipeline/Pipeline.test.ts | 15 ++-- src/pipeline/Pipeline.ts | 11 ++- src/preprocessing/LabelEncoder.test.ts | 4 +- src/preprocessing/LabelEncoder.ts | 18 +++-- src/preprocessing/MaxAbsScaler.test.ts | 5 +- src/preprocessing/MaxAbsScaler.ts | 21 ++--- src/preprocessing/MinMaxScaler.test.ts | 5 +- src/preprocessing/MinMaxScaler.ts | 43 +++++----- src/preprocessing/Normalizer.test.ts | 4 +- src/preprocessing/Normalizer.ts | 11 +-- src/preprocessing/OneHotEncoder.test.ts | 5 +- src/preprocessing/OneHotEncoder.ts | 39 ++++----- src/preprocessing/OrdinalEncoder.test.ts | 4 +- src/preprocessing/OrdinalEncoder.ts | 9 ++- src/preprocessing/RobustScaler.test.ts | 4 +- src/preprocessing/RobustScaler.ts | 29 +++---- src/preprocessing/StandardScaler.test.ts | 4 +- src/preprocessing/StandardScaler.ts | 19 ++--- src/serialize.ts | 5 +- src/svm/LinearSVC.test.ts | 4 +- src/svm/LinearSVC.ts | 8 +- src/svm/LinearSVR.test.ts | 6 +- src/svm/LinearSVR.ts | 8 +- src/svm/SVC.ts | 10 +-- src/svm/SVR.ts | 8 +- src/tf-singleton.ts | 23 ++++++ src/tree/DecisionTree.test.ts | 8 +- src/types.ts | 32 ++++++-- src/typesUtils.ts | 10 ++- 84 files changed, 725 insertions(+), 504 deletions(-) create mode 100644 src/tf-singleton.ts diff --git a/package.json b/package.json index 2360944b..f63776b3 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "doc": "docs" }, "browser": { - "danfojs-node": "danfojs" + "danfojs-node": "danfojs", + "@tensorflow/tfjs-node": "@tensorflow/tfjs" }, "scripts": { "test": "node_modules/.bin/jest src/**/*.test.ts src/*.test.ts --coverage && npm run prettier:check && npm run test:browser", diff --git a/src/cluster/KMeans.test.ts b/src/cluster/KMeans.test.ts index 6aa15ea9..7f77194a 100644 --- a/src/cluster/KMeans.test.ts +++ b/src/cluster/KMeans.test.ts @@ -1,5 +1,6 @@ -import { KMeans } from './KMeans' - +import * as tf from '@tensorflow/tfjs-node' +import { KMeans, setBackend } from '../index' +setBackend(tf) // Next steps: Improve on kmeans cluster testing describe('KMeans', () => { const X = [ diff --git a/src/cluster/KMeans.ts b/src/cluster/KMeans.ts index d5810cd1..5b51dff2 100644 --- a/src/cluster/KMeans.ts +++ b/src/cluster/KMeans.ts @@ -1,7 +1,7 @@ -import { Scikit2D } from '../types' +import { Scikit2D, Tensor1D, Tensor2D } from '../types' import { convertToNumericTensor2D, sampleWithoutReplacement } from '../utils' import Serialize from '../serialize' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' /* Next steps @@ -68,10 +68,11 @@ export class KMeans extends Serialize { // Attributes /** The actual cluster centers found by KMeans */ - clusterCenters: tf.Tensor2D + clusterCenters: Tensor2D /** Useful for pipelines and column transformers to have a default name for transforms */ name = 'KMeans' + tf: any constructor({ nClusters = 8, @@ -82,51 +83,58 @@ export class KMeans extends Serialize { randomState }: KMeansParams = {}) { super() + this.tf = getBackend() this.nClusters = nClusters this.init = init this.maxIter = maxIter this.tol = tol this.randomState = randomState this.nInit = nInit - this.clusterCenters = tf.tensor2d([[]]) + this.clusterCenters = this.tf.tensor2d([[]]) } - initCentroids(X: tf.Tensor2D) { + initCentroids(X: Tensor2D) { if (this.init === 'random') { let indices = sampleWithoutReplacement( X.shape[0], this.nClusters, this.randomState ) - this.clusterCenters = tf.gather(X, indices) + this.clusterCenters = this.tf.gather(X, indices) return } throw new Error(`init ${this.init} is not currently implemented`) } - closestCentroid(X: tf.Tensor2D): tf.Tensor1D { - return tf.tidy(() => { - const expandedX = tf.expandDims(X, 1) - const expandedClusters = tf.expandDims(this.clusterCenters, 0) - return tf.squaredDifference(expandedX, expandedClusters).sum(2).argMin(1) + closestCentroid(X: Tensor2D): Tensor1D { + return this.tf.tidy(() => { + const expandedX = this.tf.expandDims(X, 1) + const expandedClusters = this.tf.expandDims(this.clusterCenters, 0) + return this.tf + .squaredDifference(expandedX, expandedClusters) + .sum(2) + .argMin(1) }) } - updateCentroids(X: tf.Tensor2D, nearestIndices: tf.Tensor1D): tf.Tensor2D { - return tf.tidy(() => { + updateCentroids(X: Tensor2D, nearestIndices: Tensor1D): Tensor2D { + return this.tf.tidy(() => { const newCentroids = [] for (let i = 0; i < this.nClusters; i++) { - const mask = tf.equal(nearestIndices, tf.scalar(i).toInt()) - const currentCentroid = tf.div( + const mask = this.tf.equal(nearestIndices, this.tf.scalar(i).toInt()) + const currentCentroid = this.tf.div( // set all masked instances to 0 by multiplying the mask tensor, // then sum across all instances - tf.sum(tf.mul(tf.expandDims(mask.toFloat(), 1), X), 0), + this.tf.sum( + this.tf.mul(this.tf.expandDims(mask.toFloat(), 1), X), + 0 + ), // divided by number of instances - tf.sum(mask.toFloat()) + this.tf.sum(mask.toFloat()) ) newCentroids.push(currentCentroid) } - return tf.stack(newCentroids) as tf.Tensor2D + return this.tf.stack(newCentroids) as Tensor2D }) } @@ -148,20 +156,20 @@ export class KMeans extends Serialize { * Converts 2D input into a 1D Tensor which holds the KMeans cluster Class label * @param X The 2D Matrix that you wish to cluster */ - public predict(X: Scikit2D): tf.Tensor1D { + public predict(X: Scikit2D): Tensor1D { let XTensor2D = convertToNumericTensor2D(X) return this.closestCentroid(XTensor2D) } - public transform(X: Scikit2D): tf.Tensor2D { - return tf.tidy(() => { + public transform(X: Scikit2D): Tensor2D { + return this.tf.tidy(() => { const XTensor2D = convertToNumericTensor2D(X) - const expandedX = tf.expandDims(XTensor2D, 1) - const expandedClusters = tf.expandDims(this.clusterCenters, 0) - return tf + const expandedX = this.tf.expandDims(XTensor2D, 1) + const expandedClusters = this.tf.expandDims(this.clusterCenters, 0) + return this.tf .squaredDifference(expandedX, expandedClusters) .sum(2) - .sqrt() as tf.Tensor2D + .sqrt() as Tensor2D }) } @@ -173,12 +181,12 @@ export class KMeans extends Serialize { return this.fit(X).transform(X) } - public score(X: Scikit2D): tf.Tensor1D { - return tf.tidy(() => { + public score(X: Scikit2D): Tensor1D { + return this.tf.tidy(() => { const XTensor2D = convertToNumericTensor2D(X) - const expandedX = tf.expandDims(XTensor2D, 1) - const expandedClusters = tf.expandDims(this.clusterCenters, 0) - return tf + const expandedX = this.tf.expandDims(XTensor2D, 1) + const expandedClusters = this.tf.expandDims(this.clusterCenters, 0) + return this.tf .squaredDifference(expandedX, expandedClusters) .sum(2) .min(1) diff --git a/src/compose/ColumnTransformer.test.ts b/src/compose/ColumnTransformer.test.ts index 063da473..ec5fe093 100644 --- a/src/compose/ColumnTransformer.test.ts +++ b/src/compose/ColumnTransformer.test.ts @@ -1,7 +1,12 @@ -import { ColumnTransformer } from './ColumnTransformer' -import { MinMaxScaler } from '../preprocessing/MinMaxScaler' -import { SimpleImputer } from '../impute/SimpleImputer' +import { + ColumnTransformer, + MinMaxScaler, + SimpleImputer, + setBackend +} from '../index' import * as dfd from 'danfojs-node' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('ColumnTransformer', function () { it('ColumnTransformer simple test', function () { diff --git a/src/compose/ColumnTransformer.ts b/src/compose/ColumnTransformer.ts index 0f89059a..6558624a 100644 --- a/src/compose/ColumnTransformer.ts +++ b/src/compose/ColumnTransformer.ts @@ -1,6 +1,7 @@ -import { DataFrameInterface, Scikit1D, Scikit2D, Transformer } from '../types' -import { isDataFrameInterface, isScikitLike2D } from '../typesUtils' -import { tf } from '../shared/globals' +import { DataFrameInterface, Scikit1D, Transformer, Tensor2D } from '../types' +import { isDataFrameInterface } from '../typesUtils' +import { getBackend } from '../tf-singleton' + /* Next steps: 1. Support 'passthrough' and 'drop' and estimator for remainder (also in transformer list) @@ -70,16 +71,18 @@ export class ColumnTransformer { /** Useful for pipelines and column transformers to have a default name for transforms */ name = 'ColumnTransformer' + tf: any constructor({ transformers = [], remainder = 'drop' }: ColumnTransformerParams = {}) { + this.tf = getBackend() this.transformers = transformers this.remainder = remainder } - public fit(X: tf.Tensor2D | DataFrameInterface, y?: Scikit1D) { + public fit(X: Tensor2D | DataFrameInterface, y?: Scikit1D) { for (let i = 0; i < this.transformers.length; i++) { let [, curTransform, selection] = this.transformers[i] @@ -89,7 +92,7 @@ export class ColumnTransformer { return this } - public transform(X: tf.Tensor2D | DataFrameInterface, y?: Scikit1D) { + public transform(X: Tensor2D | DataFrameInterface, y?: Scikit1D) { let output = [] for (let i = 0; i < this.transformers.length; i++) { let [, curTransform, selection] = this.transformers[i] @@ -98,10 +101,10 @@ export class ColumnTransformer { output.push(curTransform.transform(subsetX, y)) } - return tf.concat(output, 1) + return this.tf.concat(output, 1) } - public fitTransform(X: tf.Tensor2D | DataFrameInterface, y?: Scikit1D) { + public fitTransform(X: Tensor2D | DataFrameInterface, y?: Scikit1D) { let output = [] for (let i = 0; i < this.transformers.length; i++) { let [, curTransform, selection] = this.transformers[i] @@ -110,27 +113,27 @@ export class ColumnTransformer { output.push(curTransform.fitTransform(subsetX, y)) } - return tf.concat(output, 1) + return this.tf.concat(output, 1) } getColumns( - X: DataFrameInterface | tf.Tensor2D, + X: DataFrameInterface | Tensor2D, selectedColumns: Selection - ): tf.Tensor2D { + ): Tensor2D { if (isDataFrameInterface(X)) { if (isStringArray(selectedColumns)) { return X.loc({ columns: selectedColumns }) - .tensor as unknown as tf.Tensor2D + .tensor as unknown as Tensor2D } if (Array.isArray(selectedColumns)) { return X.iloc({ columns: selectedColumns }) - .tensor as unknown as tf.Tensor2D + .tensor as unknown as Tensor2D } if (typeof selectedColumns === 'string') { return X[selectedColumns].tensor } return X.iloc({ columns: [selectedColumns] }) - .tensor as unknown as tf.Tensor2D + .tensor as unknown as Tensor2D } else { if ( isStringArray(selectedColumns) || @@ -141,10 +144,10 @@ export class ColumnTransformer { ) } if (typeof selectedColumns === 'number') { - let columns = tf.tensor1d([selectedColumns]) + let columns = this.tf.tensor1d([selectedColumns]) return X.gather(columns, 1) } else { - let columns = tf.tensor1d(selectedColumns) + let columns = this.tf.tensor1d(selectedColumns) return X.gather(columns, 1) } } diff --git a/src/datasets/makeRegression.test.ts b/src/datasets/makeRegression.test.ts index e5e216bf..2fa8d8f0 100644 --- a/src/datasets/makeRegression.test.ts +++ b/src/datasets/makeRegression.test.ts @@ -1,5 +1,6 @@ -import { makeLowRankMatrix, makeRegression } from './makeRegression' -import { tf } from '../shared/globals' +import { makeLowRankMatrix, makeRegression, setBackend } from '../index' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('makeRegression tests', () => { it('returns the right size', () => { diff --git a/src/datasets/makeRegression.ts b/src/datasets/makeRegression.ts index ab3178ba..babf51ef 100644 --- a/src/datasets/makeRegression.ts +++ b/src/datasets/makeRegression.ts @@ -1,6 +1,6 @@ -import { tf } from '../shared/globals' -type Tensor2D = tf.Tensor2D -type Tensor1D = tf.Tensor1D +import { Tensor1D, Tensor2D } from '../types' +import { getBackend } from '../tf-singleton' + interface MakeRegressionInput { nSamples?: number nFeatures?: number @@ -29,10 +29,11 @@ export const makeRegression = ({ | [Tensor2D, Tensor1D | Tensor2D] | [Tensor2D, Tensor1D, Tensor1D] | [Tensor2D, Tensor2D, Tensor2D] => { + let tf = getBackend() return tf.tidy(() => { const numberInformative = Math.min(nFeatures, nInformative) - let X: tf.Tensor2D + let X: Tensor2D if (effectiveRank === null) { // Randomly generate a well conditioned input set X = tf.randomNormal([nSamples, nFeatures]) @@ -87,7 +88,9 @@ export const makeLowRankMatrix = ({ nFeatures = 100, effectiveRank = 10, tailStrength = 0.5 -}: MakeLowRankMatrixInput = {}): tf.Tensor2D => { +}: MakeLowRankMatrixInput = {}): Tensor2D => { + let tf = getBackend() + return tf.tidy(() => { let n = Math.min(nSamples, nFeatures) @@ -107,6 +110,6 @@ export const makeLowRankMatrix = ({ let s = lowRank.add(tail) - return u.mul(s).dot(v.transpose()) as tf.Tensor2D + return u.mul(s).dot(v.transpose()) as Tensor2D }) } diff --git a/src/dummy/DummyClassifier.test.ts b/src/dummy/DummyClassifier.test.ts index 858c5adc..1a97fe35 100644 --- a/src/dummy/DummyClassifier.test.ts +++ b/src/dummy/DummyClassifier.test.ts @@ -1,4 +1,6 @@ -import { DummyClassifier } from './DummyClassifier' +import { DummyClassifier, setBackend } from '../index' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('DummyClassifier', function () { it('Use DummyClassifier on simple example (mostFrequent)', function () { diff --git a/src/dummy/DummyClassifier.ts b/src/dummy/DummyClassifier.ts index 04e12465..f574a210 100644 --- a/src/dummy/DummyClassifier.ts +++ b/src/dummy/DummyClassifier.ts @@ -14,13 +14,13 @@ */ import { convertToNumericTensor1D, convertToNumericTensor2D } from '../utils' -import { Scikit1D, Scikit2D } from '../types' +import { Scikit1D, Scikit2D, Tensor2D, Tensor1D } from '../types' import { isScikit2D, assert, isScikit1D } from '../typesUtils' import { modeFast } from 'simple-statistics' import uniq from 'lodash/uniq' import sample from 'lodash/sample' import { ClassifierMixin } from '../mixins' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' /* Next steps: @@ -88,11 +88,13 @@ export class DummyClassifier extends ClassifierMixin { /** Useful for pipelines and column transformers to have a default name for transforms */ name = 'DummyClassifier' + tf: any constructor({ strategy = 'mostFrequent', constant = 0 }: DummyClassifierParams = {}) { super() + this.tf = getBackend() this.constant = constant this.strategy = strategy this.classes = [] @@ -118,19 +120,19 @@ export class DummyClassifier extends ClassifierMixin { return this } - public predictProba(X: Scikit2D): tf.Tensor2D { + public predictProba(X: Scikit2D): Tensor2D { assert(isScikit2D(X), 'Data can not be converted to a 1D or 2D matrix.') assert( ['mostFrequent', 'uniform', 'constant'].includes(this.strategy), `Strategy ${this.strategy} not supported. We support 'mostFrequent', 'uniform', and 'constant'` ) - return tf.oneHot( + return this.tf.oneHot( this.predict(X).toInt(), this.classes.length - ) as tf.Tensor2D + ) as Tensor2D } - public predict(X: Scikit2D): tf.Tensor1D { + public predict(X: Scikit2D): Tensor1D { assert(isScikit2D(X), 'Data can not be converted to a 1D or 2D matrix.') assert( ['mostFrequent', 'uniform', 'constant'].includes(this.strategy), @@ -139,7 +141,7 @@ export class DummyClassifier extends ClassifierMixin { let newData = convertToNumericTensor2D(X) let length = newData.shape[0] if (this.strategy === 'mostFrequent' || this.strategy === 'constant') { - return tf.tensor1d(Array(length).fill(this.constant)) + return this.tf.tensor1d(Array(length).fill(this.constant)) } // "Uniform case" @@ -147,6 +149,6 @@ export class DummyClassifier extends ClassifierMixin { for (let i = 0; i < length; i++) { returnArr.push(sample(this.classes)) } - return tf.tensor1d(returnArr as number[]) + return this.tf.tensor1d(returnArr as number[]) } } diff --git a/src/dummy/DummyRegressor.test.ts b/src/dummy/DummyRegressor.test.ts index 04299b7e..10822b07 100644 --- a/src/dummy/DummyRegressor.test.ts +++ b/src/dummy/DummyRegressor.test.ts @@ -1,4 +1,6 @@ -import { DummyRegressor } from './DummyRegressor' +import { DummyRegressor, setBackend } from '../index' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('DummyRegressor', function () { it('Use DummyRegressor on simple example (mean)', function () { diff --git a/src/dummy/DummyRegressor.ts b/src/dummy/DummyRegressor.ts index 124b2249..f895fdb9 100644 --- a/src/dummy/DummyRegressor.ts +++ b/src/dummy/DummyRegressor.ts @@ -14,11 +14,11 @@ */ import { convertToNumericTensor1D, convertToNumericTensor2D } from '../utils' -import { Scikit1D, Scikit2D } from '../types' +import { Scikit1D, Scikit2D, Tensor1D } from '../types' import { assert, isScikit1D, isScikit2D } from '../typesUtils' import { median, quantileSeq } from 'mathjs' import { RegressorMixin } from '../mixins' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' /* Next steps: @@ -86,12 +86,14 @@ export class DummyRegressor extends RegressorMixin { /** Useful for pipelines and column transformers to have a default name for transforms */ name = 'DummyRegressor' + tf: any constructor({ strategy = 'mean', constant, quantile }: DummyRegressorParams = {}) { super() + this.tf = getBackend() this.strategy = strategy this.constant = constant this.quantile = quantile @@ -135,10 +137,10 @@ export class DummyRegressor extends RegressorMixin { return this } - public predict(X: Scikit2D): tf.Tensor1D { + public predict(X: Scikit2D): Tensor1D { assert(isScikit2D(X), 'Data can not be converted to a 2D matrix.') let newData = convertToNumericTensor2D(X) let length = newData.shape[0] - return tf.tensor1d(Array(length).fill(this.constant)) + return this.tf.tensor1d(Array(length).fill(this.constant)) } } diff --git a/src/ensemble/VotingClassifier.test.ts b/src/ensemble/VotingClassifier.test.ts index d741c00a..e962a5fd 100644 --- a/src/ensemble/VotingClassifier.test.ts +++ b/src/ensemble/VotingClassifier.test.ts @@ -1,7 +1,12 @@ -import { makeVotingClassifier, VotingClassifier } from './VotingClassifier' -import { DummyClassifier } from '../dummy/DummyClassifier' - -import { LogisticRegression } from '../linear_model/LogisticRegression' +import { + makeVotingClassifier, + VotingClassifier, + DummyClassifier, + LogisticRegression, + setBackend +} from '../index' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('VotingClassifier', function () { it('Use VotingClassifier on simple example (voting = hard)', async function () { diff --git a/src/ensemble/VotingClassifier.ts b/src/ensemble/VotingClassifier.ts index 5db1241e..9906680e 100644 --- a/src/ensemble/VotingClassifier.ts +++ b/src/ensemble/VotingClassifier.ts @@ -1,5 +1,5 @@ -import { Scikit1D, Scikit2D } from '../types' -import { tf } from '../shared/globals' +import { Scikit1D, Scikit2D, Tensor1D, Tensor2D } from '../types' +import { getBackend } from '../tf-singleton' import { ClassifierMixin } from '../mixins' import { LabelEncoder } from '../preprocessing/LabelEncoder' import { fromJson, toJson } from './serializeEnsemble' @@ -63,12 +63,14 @@ export class VotingClassifier extends ClassifierMixin { le: any name = 'VotingClassifier' + tf: any constructor({ estimators = [], weights = undefined, voting = 'hard' }: VotingClassifierParams = {}) { super() + this.tf = getBackend() this.estimators = estimators this.weights = weights this.voting = voting @@ -84,7 +86,7 @@ export class VotingClassifier extends ClassifierMixin { return this } - public predictProba(X: Scikit2D): tf.Tensor1D { + public predictProba(X: Scikit2D): Tensor1D { let responses = [] let numEstimators = this.estimators.length const weights = @@ -95,11 +97,11 @@ export class VotingClassifier extends ClassifierMixin { responses.push(curEstimator.predictProba(X).mul(curWeight)) } - return tf.addN(responses) + return this.tf.addN(responses) } // only hard case - public predict(X: Scikit2D): tf.Tensor1D { + public predict(X: Scikit2D): Tensor1D { let responses = [] let numEstimators = this.estimators.length const weights = @@ -110,11 +112,11 @@ export class VotingClassifier extends ClassifierMixin { let [_, curEstimator] = this.estimators[i] let curWeight = weights[i] let predictions = curEstimator.predict(X).toInt() - let oneHot = tf.oneHot(predictions, this.le.classes.length) + let oneHot = this.tf.oneHot(predictions, this.le.classes.length) responses.push(oneHot.mul(curWeight)) } - return tf.tensor1d( - this.le.inverseTransform(tf.addN(responses).argMax(1)) + return this.tf.tensor1d( + this.le.inverseTransform(this.tf.addN(responses).argMax(1)) ) } else { for (let i = 0; i < numEstimators; i++) { @@ -123,13 +125,13 @@ export class VotingClassifier extends ClassifierMixin { let predictions = curEstimator.predictProba(X) responses.push(predictions.mul(curWeight)) } - return tf.tensor1d( - this.le.inverseTransform(tf.addN(responses).argMax(1)) + return this.tf.tensor1d( + this.le.inverseTransform(this.tf.addN(responses).argMax(1)) ) } } - public transform(X: Scikit2D): Array | Array { + public transform(X: Scikit2D): Array | Array { let responses = [] let numEstimators = this.estimators.length @@ -151,7 +153,7 @@ export class VotingClassifier extends ClassifierMixin { public async fitTransform( X: Scikit2D, y: Scikit1D - ): Promise | Array> { + ): Promise | Array> { return (await this.fit(X, y)).transform(X) } diff --git a/src/ensemble/VotingRegressor.test.ts b/src/ensemble/VotingRegressor.test.ts index 06a69f97..f428fcbd 100644 --- a/src/ensemble/VotingRegressor.test.ts +++ b/src/ensemble/VotingRegressor.test.ts @@ -1,6 +1,12 @@ -import { makeVotingRegressor, VotingRegressor } from './VotingRegressor' -import { DummyRegressor } from '../dummy/DummyRegressor' -import { LinearRegression } from '../linear_model/LinearRegression' +import { + makeVotingRegressor, + VotingRegressor, + DummyRegressor, + LinearRegression, + setBackend +} from '../index' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('VotingRegressor', function () { it('Use VotingRegressor on simple example ', async function () { diff --git a/src/ensemble/VotingRegressor.ts b/src/ensemble/VotingRegressor.ts index db3d4973..87f9040d 100644 --- a/src/ensemble/VotingRegressor.ts +++ b/src/ensemble/VotingRegressor.ts @@ -1,7 +1,7 @@ -import { Scikit1D, Scikit2D } from '../types' -import { tf } from '../shared/globals' +import { Scikit1D, Scikit2D, Tensor1D } from '../types' import { RegressorMixin } from '../mixins' import { fromJson, toJson } from './serializeEnsemble' +import { getBackend } from '../tf-singleton' /* Next steps: 0. Write validation code to check Estimator inputs @@ -56,6 +56,7 @@ export class VotingRegressor extends RegressorMixin { weights = undefined }: VotingRegressorParams = {}) { super() + this.tf = getBackend() this.estimators = estimators this.weights = weights } @@ -68,7 +69,7 @@ export class VotingRegressor extends RegressorMixin { return this } - public predict(X: Scikit2D): tf.Tensor1D { + public predict(X: Scikit2D): Tensor1D { let responses = [] let numEstimators = this.estimators.length const weights = @@ -79,10 +80,10 @@ export class VotingRegressor extends RegressorMixin { responses.push(curEstimator.predict(X).mul(curWeight)) } - return tf.addN(responses) + return this.tf.addN(responses) } - public transform(X: Scikit2D): Array { + public transform(X: Scikit2D): Array { let responses = [] let numEstimators = this.estimators.length for (let i = 0; i < numEstimators; i++) { diff --git a/src/ensemble/serializeEnsemble.ts b/src/ensemble/serializeEnsemble.ts index 245c89df..2a702578 100644 --- a/src/ensemble/serializeEnsemble.ts +++ b/src/ensemble/serializeEnsemble.ts @@ -7,8 +7,9 @@ import { LassoRegression } from '../linear_model/LassoRegression' import { ElasticNet } from '../linear_model/ElasticNet' import { LabelEncoder } from '../preprocessing/LabelEncoder' import { SimpleImputer } from '../impute/SimpleImputer' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' import { MinMaxScaler } from '../preprocessing/MinMaxScaler' +import omit from 'lodash/omit' function getEstimator(name: string, serialJson: string) { switch (name) { @@ -36,6 +37,7 @@ function getEstimator(name: string, serialJson: string) { } export function fromJson(classConstructor: any, model: string) { + let tf = getBackend() let jsonClass = JSON.parse(model) if (jsonClass.name != classConstructor.name) { throw new Error( @@ -43,7 +45,7 @@ export function fromJson(classConstructor: any, model: string) { ) } - const copyThis: any = Object.assign({}, classConstructor) + const copyThis: any = Object.assign({}, omit(classConstructor, 'tf')) for (let key of Object.keys(classConstructor)) { let value = copyThis[key] if (value instanceof tf.Tensor) { diff --git a/src/impute/SimpleImputer.test.ts b/src/impute/SimpleImputer.test.ts index fb58af50..a1e54390 100644 --- a/src/impute/SimpleImputer.test.ts +++ b/src/impute/SimpleImputer.test.ts @@ -1,5 +1,6 @@ -import { tf } from '../shared/globals' -import { SimpleImputer } from './SimpleImputer' +import { SimpleImputer, setBackend } from '../index' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('SimpleImputer', function () { it('Imputes with "constant" strategy 2D one column. In this strategy, we give the fill value', function () { diff --git a/src/impute/SimpleImputer.ts b/src/impute/SimpleImputer.ts index 722591c7..cd18126e 100644 --- a/src/impute/SimpleImputer.ts +++ b/src/impute/SimpleImputer.ts @@ -14,12 +14,12 @@ */ import { convertToNumericTensor2D, convertToTensor2D } from '../utils' -import { Scikit2D } from '../types' +import { Scikit2D, Tensor2D, Tensor1D, TensorLike } from '../types' import { tensorMean } from '../math' import { median } from 'mathjs' import { modeFast } from 'simple-statistics' import { TransformerMixin } from '../mixins' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' /* Next steps: @@ -63,21 +63,23 @@ export class SimpleImputer extends TransformerMixin { fillValue: string | number | undefined strategy: 'mean' | 'median' | 'mostFrequent' | 'constant' - statistics: tf.Tensor1D + statistics: Tensor1D /** Useful for pipelines and column transformers to have a default name for transforms */ name = 'SimpleImputer' + tf: any constructor({ strategy = 'mean', fillValue = undefined, missingValues = NaN }: SimpleImputerParams = {}) { super() + this.tf = getBackend() this.missingValues = missingValues this.strategy = strategy this.fillValue = fillValue - this.statistics = tf.tensor1d([]) + this.statistics = this.tf.tensor1d([]) } public fit(X: Scikit2D): SimpleImputer { @@ -88,29 +90,29 @@ export class SimpleImputer extends TransformerMixin { if (this.strategy === 'mean') { const newTensor = convertToNumericTensor2D(X) const mean = tensorMean(newTensor, 0, true) - this.statistics = mean as tf.Tensor1D + this.statistics = mean as Tensor1D return this } if (this.strategy === 'mostFrequent') { const newTensor = convertToNumericTensor2D(X) const mostFrequents = newTensor - .transpose() + .transpose() .arraySync() .map((arr: number[] | string[]) => modeFast(removeMissingValuesFromArray(arr)) ) - this.statistics = tf.tensor1d(mostFrequents) + this.statistics = this.tf.tensor1d(mostFrequents) return this } if (this.strategy === 'median') { const newTensor = convertToNumericTensor2D(X) const medians = newTensor - .transpose() + .transpose() .arraySync() .map((arr: number[] | string[]) => median(removeMissingValuesFromArray(arr)) ) - this.statistics = tf.tensor1d(medians) + this.statistics = this.tf.tensor1d(medians) return this } throw new Error( @@ -118,37 +120,37 @@ export class SimpleImputer extends TransformerMixin { ) } - public transform(X: Scikit2D): tf.Tensor2D { + public transform(X: Scikit2D): Tensor2D { if (this.strategy === 'constant') { const newTensor = convertToTensor2D(X) if (this.fillValue === undefined) { if (newTensor.dtype !== 'string') { - return tf.where( + return this.tf.where( newTensor.isNaN(), 0, - newTensor as unknown as tf.TensorLike - ) as tf.Tensor2D + newTensor as unknown as TensorLike + ) as Tensor2D } else { - return tf.where( + return this.tf.where( newTensor.isNaN(), 'missing_value', - newTensor as unknown as tf.TensorLike - ) as tf.Tensor2D + newTensor as unknown as TensorLike + ) as Tensor2D } } - return tf.where( + return this.tf.where( newTensor.isNaN(), this.fillValue, - newTensor as unknown as tf.TensorLike - ) as tf.Tensor2D + newTensor as unknown as TensorLike + ) as Tensor2D } // Not strategy constant const newTensor = convertToNumericTensor2D(X) - return tf.where( + return this.tf.where( newTensor.isNaN(), - this.statistics.reshape([1, -1]) as tf.Tensor2D, + this.statistics.reshape([1, -1]) as Tensor2D, newTensor - ) as tf.Tensor2D + ) as Tensor2D } } diff --git a/src/index.ts b/src/index.ts index 6ab8fb07..31e9ff95 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,8 @@ * ========================================================================== */ export { KNeighborsRegressor } from './neighbors/KNeighborsRegressor' +export { KNeighborsClassifier } from './neighbors/KNeighborsClassifier' +export { makeRegression, makeLowRankMatrix } from './datasets/makeRegression' export { LinearRegression, LinearRegressionParams @@ -83,3 +85,6 @@ export { DecisionTreeRegressor, DecisionTreeRegressorParams } from './tree/DecisionTree' +export { trainTestSplit } from './model_selection/trainTestSplit' +export { KFold } from './model_selection/KFold' +export { setBackend, getBackend } from './tf-singleton' diff --git a/src/linear_model/ElasticNet.ts b/src/linear_model/ElasticNet.ts index a35f35b6..3cf406b4 100644 --- a/src/linear_model/ElasticNet.ts +++ b/src/linear_model/ElasticNet.ts @@ -14,7 +14,7 @@ */ import { SGDRegressor } from './SgdRegressor' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' // First pass at a ElasticNet implementation using gradient descent // Trying to mimic the API of https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.ElasticNet.html#sklearn.linear_model.ElasticNet @@ -33,14 +33,12 @@ export interface ElasticNetParams { * Linear regression with combined L1 and L2 priors as regularizer. */ export class ElasticNet extends SGDRegressor { - /** Useful for pipelines and column transformers to have a default name for transforms */ - name = 'ElasticNet' - constructor({ alpha = 1, l1Ratio = 0.5, fitIntercept = true }: ElasticNetParams = {}) { + let tf = getBackend() super({ modelCompileArgs: { optimizer: tf.train.adam(0.1), @@ -66,5 +64,7 @@ export class ElasticNet extends SGDRegressor { optimizerType: 'adam', lossType: 'meanSquaredError' }) + /** Useful for pipelines and column transformers to have a default name for transforms */ + this.name = 'ElasticNet' } } diff --git a/src/linear_model/LassoRegression.ts b/src/linear_model/LassoRegression.ts index 32f1438c..8ed933ba 100644 --- a/src/linear_model/LassoRegression.ts +++ b/src/linear_model/LassoRegression.ts @@ -14,7 +14,7 @@ */ import { SGDRegressor } from './SgdRegressor' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' // First pass at a LassoRegression implementation using gradient descent // Trying to mimic the API of https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Lasso.html#sklearn.linear_model.Lasso @@ -28,10 +28,8 @@ export interface LassoParams { /** Linear Model trained with L1 prior as regularizer (aka the Lasso). */ export class LassoRegression extends SGDRegressor { - /** Useful for pipelines and column transformers to have a default name for transforms */ - name = 'LassoRegression' - constructor({ fitIntercept = true, alpha = 1.0 }: LassoParams = {}) { + let tf = getBackend() super({ modelCompileArgs: { optimizer: tf.train.adam(0.1), @@ -54,5 +52,7 @@ export class LassoRegression extends SGDRegressor { optimizerType: 'adam', lossType: 'meanSquaredError' }) + /** Useful for pipelines and column transformers to have a default name for transforms */ + this.name = 'LassoRegression' } } diff --git a/src/linear_model/LinearRegression.test.ts b/src/linear_model/LinearRegression.test.ts index a4d67295..480890b0 100644 --- a/src/linear_model/LinearRegression.test.ts +++ b/src/linear_model/LinearRegression.test.ts @@ -1,6 +1,7 @@ -import { LinearRegression } from './LinearRegression' +import { LinearRegression, setBackend } from '../index' import { tensorEqual } from '../utils' -import { tf } from '../shared/globals' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) function roughlyEqual(a: number, b: number, tol = 0.1) { return Math.abs(a - b) < tol diff --git a/src/linear_model/LinearRegression.ts b/src/linear_model/LinearRegression.ts index 3c4e099c..1913ed2f 100644 --- a/src/linear_model/LinearRegression.ts +++ b/src/linear_model/LinearRegression.ts @@ -14,7 +14,7 @@ */ import { SGDRegressor } from './SgdRegressor' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' /** * LinearRegression implementation using gradient descent @@ -66,10 +66,8 @@ Next steps: * ``` */ export class LinearRegression extends SGDRegressor { - /** Useful for pipelines and column transformers to have a default name for transforms */ - name = 'LinearRegression' - constructor({ fitIntercept = true }: LinearRegressionParams = {}) { + let tf = getBackend() super({ modelCompileArgs: { optimizer: tf.train.adam(0.1), @@ -91,5 +89,7 @@ export class LinearRegression extends SGDRegressor { optimizerType: 'adam', lossType: 'meanSquaredError' }) + /** Useful for pipelines and column transformers to have a default name for transforms */ + this.name = 'LinearRegression' } } diff --git a/src/linear_model/LogisticRegression.test.ts b/src/linear_model/LogisticRegression.test.ts index c00db0fa..121e038d 100644 --- a/src/linear_model/LogisticRegression.test.ts +++ b/src/linear_model/LogisticRegression.test.ts @@ -1,5 +1,6 @@ -import { LogisticRegression } from './LogisticRegression' -import { tf } from '../shared/globals' +import { LogisticRegression, setBackend } from '../index' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('LogisticRegression', function () { it('Works on arrays (small example)', async function () { diff --git a/src/linear_model/LogisticRegression.ts b/src/linear_model/LogisticRegression.ts index 30651225..159cd366 100644 --- a/src/linear_model/LogisticRegression.ts +++ b/src/linear_model/LogisticRegression.ts @@ -14,7 +14,7 @@ // */ import { SGDClassifier } from './SgdClassifier' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' // First pass at a LogisticRegression implementation using gradient descent // Trying to mimic the API of scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html @@ -60,9 +60,6 @@ export interface LogisticRegressionParams { * ``` */ export class LogisticRegression extends SGDClassifier { - /** Useful for pipelines and column transformers to have a default name for transforms */ - name = 'LogisticRegression' - constructor({ penalty = 'l2', C = 1, @@ -70,6 +67,7 @@ export class LogisticRegression extends SGDClassifier { }: LogisticRegressionParams = {}) { // Assume Binary classification // If we call fit, and it isn't binary then update args + let tf = getBackend() super({ modelCompileArgs: { optimizer: tf.train.adam(0.1), @@ -101,5 +99,7 @@ export class LogisticRegression extends SGDClassifier { optimizerType: 'adam', lossType: 'softmaxCrossEntropy' }) + /** Useful for pipelines and column transformers to have a default name for transforms */ + this.name = 'LogisticRegression' } } diff --git a/src/linear_model/RidgeRegression.ts b/src/linear_model/RidgeRegression.ts index 1d3cbf72..d0ab33ed 100644 --- a/src/linear_model/RidgeRegression.ts +++ b/src/linear_model/RidgeRegression.ts @@ -14,7 +14,7 @@ */ import { SGDRegressor } from './SgdRegressor' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' // RidgeRegression implementation using gradient descent // This is a placeholder until we can do an analytic solution instead @@ -30,13 +30,11 @@ export interface RidgeRegressionParams { /** Linear least squares with l2 regularization. */ export class RidgeRegression extends SGDRegressor { - /** Useful for pipelines and column transformers to have a default name for transforms */ - name = 'RidgeRegression' - constructor({ fitIntercept = true, alpha = 0.01 }: RidgeRegressionParams = {}) { + let tf = getBackend() super({ modelCompileArgs: { optimizer: tf.train.adam(0.1), @@ -59,5 +57,7 @@ export class RidgeRegression extends SGDRegressor { optimizerType: 'adam', lossType: 'meanSquaredError' }) + /** Useful for pipelines and column transformers to have a default name for transforms */ + this.name = 'RidgeRegression' } } diff --git a/src/linear_model/SgdClassifier.ts b/src/linear_model/SgdClassifier.ts index b5900a39..bbdb8119 100644 --- a/src/linear_model/SgdClassifier.ts +++ b/src/linear_model/SgdClassifier.ts @@ -13,14 +13,25 @@ * ========================================================================== */ -import { tf } from '../shared/globals' // import { DenseLayerArgs } from '@tensorflow/tfjs-layers/dist/layers/core' import { convertToNumericTensor1D, convertToNumericTensor2D } from '../utils' -import { Scikit2D, Scikit1D, OptimizerTypes, LossTypes } from '../types' +import { + Scikit2D, + Scikit1D, + OptimizerTypes, + LossTypes, + Tensor1D, + Tensor2D, + Tensor, + ModelCompileArgs, + ModelFitArgs, + RecursiveArray +} from '../types' import { OneHotEncoder } from '../preprocessing/OneHotEncoder' import { assert } from '../typesUtils' import { ClassifierMixin } from '../mixins' import { fromJson, toJSON } from './modelSerializer' +import { getBackend } from '../tf-singleton' /** * SGD is a thin Wrapper around Tensorflow's model api with a single dense layer. * With this base class and different error functions / regularizers we can @@ -42,7 +53,7 @@ export interface SGDClassifierParams { metrics: ['mse'], }) */ - modelCompileArgs: tf.ModelCompileArgs + modelCompileArgs: ModelCompileArgs /** * The complete list of `model.fit` args from Tensorflow.js @@ -55,7 +66,7 @@ export interface SGDClassifierParams { callbacks: [callbacks.earlyStopping({ monitor: 'mse', patience: 50 })], }) */ - modelFitArgs: tf.ModelFitArgs + modelFitArgs: ModelFitArgs /** * The arguments for a single dense layer in tensorflow. This also defaults to @@ -85,14 +96,14 @@ export interface SGDClassifierParams { } export class SGDClassifier extends ClassifierMixin { - model: tf.Sequential - modelFitArgs: tf.ModelFitArgs - modelCompileArgs: tf.ModelCompileArgs + model: any //tf.Sequential + modelFitArgs: ModelFitArgs + modelCompileArgs: ModelCompileArgs denseLayerArgs: any //DenseLayerArgs optimizerType: OptimizerTypes lossType: LossTypes - oneHot: OneHotEncoder + tf: any constructor({ modelFitArgs, @@ -102,7 +113,8 @@ export class SGDClassifier extends ClassifierMixin { lossType }: SGDClassifierParams) { super() - this.model = tf.sequential() + this.tf = getBackend() + this.model = this.tf.sequential() this.modelFitArgs = modelFitArgs this.modelCompileArgs = modelCompileArgs this.denseLayerArgs = denseLayerArgs @@ -114,22 +126,22 @@ export class SGDClassifier extends ClassifierMixin { this.oneHot = new OneHotEncoder() } - initializeModelForClassification(y: tf.Tensor1D | tf.Tensor2D): tf.Tensor2D { + initializeModelForClassification(y: Tensor1D | Tensor2D): Tensor2D { let yToInt = y.toInt() // This covers the case of a dependent variable that is already one hot encoded. // There are other cases where you do "multi-variable output which isn't one hot encoded" // Like say you were predicting which diseases a person could have (hasCancer, hasMeningitis, etc) // Then you would have to run a sigmoid on each independent variable if (yToInt.shape.length === 2) { - this.modelCompileArgs.loss = tf.losses.softmaxCrossEntropy - return yToInt as tf.Tensor2D + this.modelCompileArgs.loss = this.tf.losses.softmaxCrossEntropy + return yToInt as Tensor2D } else { - const yTwoD = y.reshape([-1, 1]) as tf.Tensor2D + const yTwoD = y.reshape([-1, 1]) as Tensor2D const yTwoDOneHotEncoded = this.oneHot.fitTransform(yTwoD) if (this.oneHot.categories[0].length > 2) { - this.modelCompileArgs.loss = tf.losses.softmaxCrossEntropy + this.modelCompileArgs.loss = this.tf.losses.softmaxCrossEntropy } else { - this.modelCompileArgs.loss = tf.losses.sigmoidCrossEntropy + this.modelCompileArgs.loss = this.tf.losses.sigmoidCrossEntropy } return yTwoDOneHotEncoded } @@ -148,14 +160,17 @@ export class SGDClassifier extends ClassifierMixin { */ initializeModel( - X: tf.Tensor2D, - y: tf.Tensor1D | tf.Tensor2D, - weightsTensors: tf.Tensor[] = [] + X: Tensor2D, + y: Tensor1D | Tensor2D, + weightsTensors: Tensor[] = [] ): void { this.denseLayerArgs.units = y.shape.length === 1 ? 1 : y.shape[1] - const model = tf.sequential() + const model = this.tf.sequential() model.add( - tf.layers.dense({ inputShape: [X.shape[1]], ...this.denseLayerArgs }) + this.tf.layers.dense({ + inputShape: [X.shape[1]], + ...this.denseLayerArgs + }) ) model.compile(this.modelCompileArgs) if (weightsTensors?.length) { @@ -223,8 +238,12 @@ export class SGDClassifier extends ClassifierMixin { importModel(params: { coef: number[]; intercept: number }): SGDClassifier { // Next steps: Need to update for possible 2D coef case, and 1D intercept case - let myCoef = tf.tensor2d(params.coef, [params.coef.length, 1], 'float32') - let myIntercept = tf.tensor1d([params.intercept], 'float32') + let myCoef = this.tf.tensor2d( + params.coef, + [params.coef.length, 1], + 'float32' + ) + let myIntercept = this.tf.tensor1d([params.intercept], 'float32') this.initializeModel(myCoef, myIntercept, [myCoef, myIntercept]) return this } @@ -295,10 +314,10 @@ export class SGDClassifier extends ClassifierMixin { return this } - public predictProba(X: Scikit2D): tf.Tensor2D { + public predictProba(X: Scikit2D): Tensor2D { assert(this.model.layers.length > 0, 'Need to call "fit" before "predict"') let XTwoD = convertToNumericTensor2D(X) - return this.model.predict(XTwoD) as tf.Tensor2D + return this.model.predict(XTwoD) as Tensor2D } /** * Similar to scikit-learn, this returns a Tensor2D (2D Matrix) of predictions. @@ -323,10 +342,10 @@ export class SGDClassifier extends ClassifierMixin { * // => tensor2d([[ 4.5, 10.3, 19.1, 0.22 ]]) */ - public predict(X: Scikit2D): tf.Tensor1D { + public predict(X: Scikit2D): Tensor1D { assert(this.model.layers.length > 0, 'Need to call "fit" before "predict"') const y2D = this.predictProba(X) - return tf.tensor1d(this.oneHot.inverseTransform(y2D)) + return this.tf.tensor1d(this.oneHot.inverseTransform(y2D)) } /** @@ -352,16 +371,16 @@ export class SGDClassifier extends ClassifierMixin { */ - get coef(): tf.Tensor1D | tf.Tensor2D { + get coef(): Tensor1D | Tensor2D { const modelWeights = this.model.getWeights() if (modelWeights.length === 0) { - return tf.tensor2d([]) + return this.tf.tensor2d([]) } let coefficients = modelWeights[0] if (coefficients.shape[1] === 1) { - return coefficients.reshape([coefficients.shape[0]]) as tf.Tensor1D + return coefficients.reshape([coefficients.shape[0]]) as Tensor1D } - return coefficients as tf.Tensor2D + return coefficients as Tensor2D } /** @@ -388,12 +407,12 @@ export class SGDClassifier extends ClassifierMixin { * lr.intercept * // => tensor1d([1.2, 2.3]) */ - get intercept(): number | tf.Tensor1D { + get intercept(): number | Tensor1D { const modelWeights = this.model.getWeights() if (modelWeights.length < 2) { return 0.0 } - let intercept = modelWeights[1] as tf.Tensor1D + let intercept = modelWeights[1] as Tensor1D if (intercept.size === 1) { return intercept.arraySync()[0] } @@ -401,8 +420,10 @@ export class SGDClassifier extends ClassifierMixin { return intercept } - private getModelWeight(): Promise> { - return Promise.all(this.model.getWeights().map((weight) => weight.array())) + private getModelWeight(): Promise> { + return Promise.all( + this.model.getWeights().map((weight: any) => weight.array()) + ) } public async toJson(): Promise { diff --git a/src/linear_model/SgdRegressor.ts b/src/linear_model/SgdRegressor.ts index 917ac73c..416d4dd5 100644 --- a/src/linear_model/SgdRegressor.ts +++ b/src/linear_model/SgdRegressor.ts @@ -12,15 +12,25 @@ * limitations under the License. * ========================================================================== */ -import { tf } from '../shared/globals' // import { DenseLayerArgs } from '@tensorflow/tfjs-layers/dist/layers/core' import { convertToNumericTensor1D_2D, convertToNumericTensor2D } from '../utils' -import { Scikit2D, Scikit1D, OptimizerTypes, LossTypes } from '../types' +import { + Scikit2D, + Scikit1D, + OptimizerTypes, + LossTypes, + Tensor1D, + Tensor2D, + Tensor, + ModelCompileArgs, + ModelFitArgs +} from '../types' import { RegressorMixin } from '../mixins' import { fromJson, toJSON } from './modelSerializer' +import { getBackend } from '../tf-singleton' /** * SGD is a thin Wrapper around Tensorflow's model api with a single dense layer. * With this base class and different error functions / regularizers we can @@ -42,7 +52,7 @@ export interface SGDRegressorParams { metrics: ['mse'], }) */ - modelCompileArgs: tf.ModelCompileArgs + modelCompileArgs: ModelCompileArgs /** * The complete list of `model.fit` args from Tensorflow.js @@ -55,7 +65,7 @@ export interface SGDRegressorParams { callbacks: [callbacks.earlyStopping({ monitor: 'mse', patience: 50 })], }) */ - modelFitArgs: tf.ModelFitArgs + modelFitArgs: ModelFitArgs /** * The arguments for a single dense layer in tensorflow. This also defaults to @@ -85,9 +95,9 @@ export interface SGDRegressorParams { } export class SGDRegressor extends RegressorMixin { - model: tf.Sequential - modelFitArgs: tf.ModelFitArgs - modelCompileArgs: tf.ModelCompileArgs + model: any //this.tf.Sequential + modelFitArgs: ModelFitArgs + modelCompileArgs: ModelCompileArgs denseLayerArgs: any //DenseLayerArgs isMultiOutput: boolean optimizerType: OptimizerTypes @@ -101,7 +111,8 @@ export class SGDRegressor extends RegressorMixin { lossType }: SGDRegressorParams) { super() - this.model = tf.sequential() + this.tf = getBackend() + this.model = this.tf.sequential() this.modelFitArgs = modelFitArgs this.modelCompileArgs = modelCompileArgs this.denseLayerArgs = denseLayerArgs @@ -123,14 +134,17 @@ export class SGDRegressor extends RegressorMixin { */ initializeModel( - X: tf.Tensor2D, - y: tf.Tensor1D | tf.Tensor2D, - weightsTensors: tf.Tensor[] = [] + X: Tensor2D, + y: Tensor1D | Tensor2D, + weightsTensors: Tensor[] = [] ): void { this.denseLayerArgs.units = y.shape.length === 1 ? 1 : y.shape[1] - const model = tf.sequential() + const model = this.tf.sequential() model.add( - tf.layers.dense({ inputShape: [X.shape[1]], ...this.denseLayerArgs }) + this.tf.layers.dense({ + inputShape: [X.shape[1]], + ...this.denseLayerArgs + }) ) model.compile(this.modelCompileArgs) if (weightsTensors?.length) { @@ -202,8 +216,12 @@ export class SGDRegressor extends RegressorMixin { importModel(params: { coef: number[]; intercept: number }): SGDRegressor { // Next steps: Need to update for possible 2D coef case, and 1D intercept case - let myCoef = tf.tensor2d(params.coef, [params.coef.length, 1], 'float32') - let myIntercept = tf.tensor1d([params.intercept], 'float32') + let myCoef = this.tf.tensor2d( + params.coef, + [params.coef.length, 1], + 'float32' + ) + let myIntercept = this.tf.tensor1d([params.intercept], 'float32') this.initializeModel(myCoef, myIntercept, [myCoef, myIntercept]) return this } @@ -297,16 +315,16 @@ export class SGDRegressor extends RegressorMixin { * // => tensor2d([[ 4.5, 10.3, 19.1, 0.22 ]]) */ - public predict(X: Scikit2D): tf.Tensor1D | tf.Tensor2D { + public predict(X: Scikit2D): Tensor1D | Tensor2D { let XTwoD = convertToNumericTensor2D(X) if (this.model.layers.length === 0) { throw new RangeError('Need to call "fit" before "predict"') } - const predictions = this.model.predict(XTwoD) as tf.Tensor2D + const predictions = this.model.predict(XTwoD) as Tensor2D if (!this.isMultiOutput) { - return predictions.reshape([-1]) as tf.Tensor1D + return predictions.reshape([-1]) as Tensor1D } - return predictions as tf.Tensor2D + return predictions as Tensor2D } /** @@ -332,16 +350,16 @@ export class SGDRegressor extends RegressorMixin { */ - get coef(): tf.Tensor1D | tf.Tensor2D { + get coef(): Tensor1D | Tensor2D { const modelWeights = this.model.getWeights() if (modelWeights.length === 0) { - return tf.tensor2d([]) + return this.tf.tensor2d([]) } let coefficients = modelWeights[0] if (coefficients.shape[1] === 1) { - return coefficients.reshape([coefficients.shape[0]]) as tf.Tensor1D + return coefficients.reshape([coefficients.shape[0]]) as Tensor1D } - return coefficients as tf.Tensor2D + return coefficients as Tensor2D } /** @@ -368,12 +386,12 @@ export class SGDRegressor extends RegressorMixin { * lr.intercept * // => tensor1d([1.2, 2.3]) */ - get intercept(): number | tf.Tensor1D { + get intercept(): number | Tensor1D { const modelWeights = this.model.getWeights() if (modelWeights.length < 2) { return 0.0 } - let intercept = modelWeights[1] as tf.Tensor1D + let intercept = modelWeights[1] as Tensor1D if (intercept.size === 1) { return intercept.arraySync()[0] } diff --git a/src/linear_model/modelSerializer.ts b/src/linear_model/modelSerializer.ts index aac0aeba..799fe27f 100644 --- a/src/linear_model/modelSerializer.ts +++ b/src/linear_model/modelSerializer.ts @@ -1,6 +1,7 @@ import { optimizer, initializer, getLoss } from '../utils' import { tf } from '../shared/globals' import { OneHotEncoder } from '../preprocessing/OneHotEncoder' +import omit from 'lodash/omit' function getModelWeight( model: tf.Sequential @@ -12,9 +13,10 @@ export async function toJSON( classConstructor: any, classifierJson: any ): Promise { + let cj = omit(classifierJson, ['tf', 'oneHot.tf']) const modelConfig = classConstructor.model.getConfig() const modelWeight = await getModelWeight(classConstructor.model) - classifierJson.model = { + cj.model = { config: modelConfig, weight: modelWeight } @@ -22,17 +24,16 @@ export async function toJSON( if (classConstructor.denseLayerArgs.kernelInitializer) { const initializerName = classConstructor.denseLayerArgs.kernelInitializer.constructor.name - classifierJson.denseLayerArgs.kernelInitializer = initializerName + cj.denseLayerArgs.kernelInitializer = initializerName } if (classConstructor.denseLayerArgs.biasInitializer) { const biasName = classConstructor.denseLayerArgs.biasInitializer.constructor.name - classifierJson.denseLayerArgs.biasInitializer = biasName + cj.denseLayerArgs.biasInitializer = biasName } // set optimizer - classifierJson.modelCompileArgs.optimizer = - classConstructor.model.optimizer.getConfig() - return JSON.stringify(classifierJson) + cj.modelCompileArgs.optimizer = classConstructor.model.optimizer.getConfig() + return JSON.stringify(cj) } export function fromJson(classConstructor: any, model: string) { diff --git a/src/metrics/metrics.test.ts b/src/metrics/metrics.test.ts index bf637de5..0bede51f 100644 --- a/src/metrics/metrics.test.ts +++ b/src/metrics/metrics.test.ts @@ -1,4 +1,7 @@ import * as metrics from './metrics' +import { setBackend } from '../index' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('Metrics', function () { it('accuracyScore', function () { diff --git a/src/metrics/metrics.ts b/src/metrics/metrics.ts index 3c03575f..e944f9ae 100644 --- a/src/metrics/metrics.ts +++ b/src/metrics/metrics.ts @@ -17,8 +17,7 @@ import { convertToNumericTensor1D } from '../utils' import { Scikit1D } from '../types' import { assert, isScikit1D } from '../typesUtils' import uniq from 'lodash/uniq' - -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' function assertInputIsWellFormed(labels: Scikit1D, predictions: Scikit1D) { assert(isScikit1D(labels), "Labels can't be converted to a 1D Tensor") @@ -64,6 +63,7 @@ export function accuracyScore( } export function precisionScore(labels: Scikit1D, predictions: Scikit1D) { + let tf = getBackend() const { labelsT, predictionsT } = assertInputIsWellFormed( labels, predictions @@ -73,6 +73,7 @@ export function precisionScore(labels: Scikit1D, predictions: Scikit1D) { } export function recallScore(labels: Scikit1D, predictions: Scikit1D) { + let tf = getBackend() const { labelsT, predictionsT } = assertInputIsWellFormed( labels, predictions @@ -82,6 +83,7 @@ export function recallScore(labels: Scikit1D, predictions: Scikit1D) { } export function r2Score(labels: Scikit1D, predictions: Scikit1D) { + let tf = getBackend() const { labelsT, predictionsT } = assertInputIsWellFormed( labels, predictions @@ -102,6 +104,7 @@ export function meanAbsoluteError( labels: Scikit1D, predictions: Scikit1D ): number { + let tf = getBackend() const { labelsT, predictionsT } = assertInputIsWellFormed( labels, predictions @@ -111,6 +114,7 @@ export function meanAbsoluteError( } export function meanSquaredError(labels: Scikit1D, predictions: Scikit1D) { + let tf = getBackend() const { labelsT, predictionsT } = assertInputIsWellFormed( labels, predictions @@ -120,6 +124,7 @@ export function meanSquaredError(labels: Scikit1D, predictions: Scikit1D) { } export function meanSquaredLogError(labels: Scikit1D, predictions: Scikit1D) { + let tf = getBackend() const { labelsT, predictionsT } = assertInputIsWellFormed( labels, predictions @@ -132,6 +137,7 @@ export function meanSquaredLogError(labels: Scikit1D, predictions: Scikit1D) { } export function hingeLoss(labels: Scikit1D, predictions: Scikit1D) { + let tf = getBackend() const { labelsT, predictionsT } = assertInputIsWellFormed( labels, predictions @@ -141,6 +147,7 @@ export function hingeLoss(labels: Scikit1D, predictions: Scikit1D) { } export function huberLoss(labels: Scikit1D, predictions: Scikit1D) { + let tf = getBackend() const { labelsT, predictionsT } = assertInputIsWellFormed( labels, predictions @@ -150,6 +157,7 @@ export function huberLoss(labels: Scikit1D, predictions: Scikit1D) { } export function logLoss(labels: Scikit1D, predictions: Scikit1D) { + let tf = getBackend() const { labelsT, predictionsT } = assertInputIsWellFormed( labels, predictions @@ -159,6 +167,7 @@ export function logLoss(labels: Scikit1D, predictions: Scikit1D) { } export function zeroOneLoss(labels: Scikit1D, predictions: Scikit1D) { + let tf = getBackend() const { labelsT, predictionsT } = assertInputIsWellFormed( labels, predictions @@ -172,6 +181,7 @@ export function zeroOneLoss(labels: Scikit1D, predictions: Scikit1D) { ////////////////////////////////////// export function confusionMatrix(labels: Scikit1D, predictions: Scikit1D) { + let tf = getBackend() const { labelsT, predictionsT } = assertInputIsWellFormed( labels, predictions diff --git a/src/model_selection/CrossValidator.ts b/src/model_selection/CrossValidator.ts index ceba577c..a74a109c 100644 --- a/src/model_selection/CrossValidator.ts +++ b/src/model_selection/CrossValidator.ts @@ -13,9 +13,7 @@ * ========================================================================== */ -import { Scikit1D, Scikit2D } from '../types' -import { tf } from '../shared/globals' -type Tensor1D = tf.Tensor1D +import { Scikit1D, Scikit2D, Tensor1D } from '../types' /** * Interface for cross validation splitting strategies. diff --git a/src/model_selection/KFold.test.ts b/src/model_selection/KFold.test.ts index 625e0c0d..5bfb5c1a 100644 --- a/src/model_selection/KFold.test.ts +++ b/src/model_selection/KFold.test.ts @@ -14,11 +14,12 @@ */ import * as fc from 'fast-check' -import { KFold } from './KFold' +import { KFold, setBackend } from '../index' import { alea } from '../randUtils' +import { Tensor2D } from '../types' import '../jestTensorMatchers' -import { tf } from '../shared/globals' -type Tensor2D = tf.Tensor2D +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('KFold', () => { const numRuns = 128 diff --git a/src/model_selection/KFold.ts b/src/model_selection/KFold.ts index cf67abbb..86c338d9 100644 --- a/src/model_selection/KFold.ts +++ b/src/model_selection/KFold.ts @@ -16,10 +16,9 @@ import { assert } from '../typesUtils' import { CrossValidator } from './CrossValidator' import * as randUtils from '../randUtils' -import { Scikit1D, Scikit2D } from '../types' +import { Scikit1D, Scikit2D, Tensor1D } from '../types' import { getLength } from '../utils' -import { tf } from '../shared/globals' -type Tensor1D = tf.Tensor1D +import { getBackend } from '../tf-singleton' export interface KFoldParams { /** @@ -82,6 +81,7 @@ export class KFold implements CrossValidator { shuffle: boolean randomState?: number name: string + tf: any constructor({ nSplits = 5, shuffle = false, @@ -92,6 +92,7 @@ export class KFold implements CrossValidator { Number.isInteger(nSplits) && nSplits > 1, 'new KFold({nSplits}): nSplits must be an int greater than 1.' ) + this.tf = getBackend() this.nSplits = nSplits this.shuffle = Boolean(shuffle) this.randomState = randomState @@ -153,8 +154,8 @@ export class KFold implements CrossValidator { const test = range.slice(offset, offset + chunk) yield { - trainIndex: tf.tensor1d(train, 'int32'), - testIndex: tf.tensor1d(test, 'int32') + trainIndex: this.tf.tensor1d(train, 'int32'), + testIndex: this.tf.tensor1d(test, 'int32') } offset += chunk diff --git a/src/model_selection/crossValScore.ts b/src/model_selection/crossValScore.ts index 7d0b551c..c7cf00e1 100644 --- a/src/model_selection/crossValScore.ts +++ b/src/model_selection/crossValScore.ts @@ -16,13 +16,10 @@ import { assert } from '../typesUtils' import { CrossValidator } from './CrossValidator' import { KFold } from './KFold' -import { Scikit1D, Scikit2D } from '../types' +import { Scikit1D, Scikit2D, Scalar, Tensor1D, Tensor2D } from '../types' import { isScikit1D } from '../typesUtils' import { convertToTensor1D, convertToTensor2D } from '../utils' -import { tf } from '../shared/globals' -type Scalar = tf.Scalar -type Tensor1D = tf.Tensor1D -type Tensor2D = tf.Tensor2D +import { getBackend } from '../tf-singleton' /** * Evaluates a score by cross-validation. This particular overload @@ -176,6 +173,7 @@ export async function crossValScore( scoring?: any } ): Promise { + let tf = getBackend() let unsupervised = y == null || (params == null && !isScikit1D(y)) if (unsupervised) { params = params ?? y diff --git a/src/model_selection/scorers.ts b/src/model_selection/scorers.ts index 0342d1cc..b637b907 100644 --- a/src/model_selection/scorers.ts +++ b/src/model_selection/scorers.ts @@ -13,11 +13,9 @@ * ========================================================================== */ -import { Scikit1D, Scikit2D } from '../types' +import { getBackend } from '../tf-singleton' +import { Scikit1D, Scikit2D, Scalar, Tensor1D } from '../types' import { convertToTensor1D } from '../utils' -import { tf } from '../shared/globals' -type Scalar = tf.Scalar -type Tensor1D = tf.Tensor1D export function negMeanAbsoluteError( this: { @@ -26,6 +24,7 @@ export function negMeanAbsoluteError( X: Scikit2D, y: Scikit1D ): Scalar { + let tf = getBackend() return tf.tidy(() => { y = convertToTensor1D(y) const yPred = this.predict(X) @@ -40,6 +39,7 @@ export function negMeanSquaredError( X: Scikit2D, y: Scikit1D ): Scalar { + let tf = getBackend() return tf.tidy(() => { y = convertToTensor1D(y) const yPred = this.predict(X) @@ -54,6 +54,7 @@ export function accuracy( X: Scikit2D, y: Scikit1D ): Scalar { + let tf = getBackend() return tf.tidy(() => { y = convertToTensor1D(y) const yPred = this.predict(X) diff --git a/src/model_selection/trainTestSplit.test.ts b/src/model_selection/trainTestSplit.test.ts index 3673b9f8..57a65c01 100644 --- a/src/model_selection/trainTestSplit.test.ts +++ b/src/model_selection/trainTestSplit.test.ts @@ -1,12 +1,9 @@ -import { - trainTestSplit, - validateShuffleSplit, - getIndices -} from './trainTestSplit' - +import { validateShuffleSplit, getIndices } from './trainTestSplit' +import { trainTestSplit, setBackend } from '../index' import * as dfd from 'danfojs-node' -import { tf } from '../shared/globals' +import * as tf from '@tensorflow/tfjs-node' import { DataFrameInterface } from '../types' +setBackend(tf) describe('Testing trainTestSplit', function () { it('Testing train/test validation logic', () => { diff --git a/src/model_selection/trainTestSplit.ts b/src/model_selection/trainTestSplit.ts index b99bf85e..c3de01f6 100644 --- a/src/model_selection/trainTestSplit.ts +++ b/src/model_selection/trainTestSplit.ts @@ -1,7 +1,7 @@ import { Scikit1D, Scikit2D } from '../types' import { assert, isDataFrameInterface, isSeriesInterface } from '../typesUtils' import { getLength, sampleWithoutReplacement } from '../utils' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' /** * Validation helper to check if the test/test sizes are meaningful wrt to the @@ -111,6 +111,7 @@ export function validateShuffleSplit( } export function getIndices(X: Scikit2D | Scikit1D, indices: number[]) { + let tf = getBackend() if (X instanceof tf.Tensor) { return tf.gather(X, indices) } @@ -120,7 +121,7 @@ export function getIndices(X: Scikit2D | Scikit1D, indices: number[]) { if (isSeriesInterface(X)) { return X.iloc(indices) } - return indices.map((i) => X[i]) + return indices.map((i) => (X as any)[i]) } /** * diff --git a/src/naive_bayes/BaseNaiveBayes.ts b/src/naive_bayes/BaseNaiveBayes.ts index 563c8fa3..d8c4d4e3 100644 --- a/src/naive_bayes/BaseNaiveBayes.ts +++ b/src/naive_bayes/BaseNaiveBayes.ts @@ -13,10 +13,10 @@ * ========================================================================== */ import { polyfillUnique } from '../tfUtils' -import { tf } from '../shared/globals' -import { Scikit1D, Scikit2D } from '../types' +import { Scikit1D, Scikit2D, Tensor1D, Tensor2D, Tensor } from '../types' import { convertToNumericTensor2D, convertToTensor1D } from '../utils' import Serialize from '../serialize' +import { getBackend } from '../tf-singleton' export interface NaiveBayesParams { /** @@ -32,16 +32,18 @@ export interface NaiveBayesParams { } export abstract class BaseNaiveBayes extends Serialize { - priors?: tf.Tensor1D + priors?: Tensor1D varSmoothing: number - public classes: tf.Tensor1D - public means: tf.Tensor1D[] - public variances: tf.Tensor1D[] + public classes: Tensor1D + public means: Tensor1D[] + public variances: Tensor1D[] + tf: any constructor(params: NaiveBayesParams = {}) { super() - this.classes = tf.tensor1d([]) + this.tf = getBackend() + this.classes = this.tf.tensor1d([]) this.means = [] this.variances = [] if (params.priors) { @@ -60,25 +62,25 @@ export abstract class BaseNaiveBayes extends Serialize { const features = convertToNumericTensor2D(X) const labels = convertToTensor1D(y) - const { values, meansByLabel, variancesByLabel } = tf.tidy(() => { - polyfillUnique(tf) - const meansByLabel: tf.Tensor1D[] = [] - const variancesByLabel: tf.Tensor1D[] = [] + const { values, meansByLabel, variancesByLabel } = this.tf.tidy(() => { + polyfillUnique(this.tf) + const meansByLabel: Tensor1D[] = [] + const variancesByLabel: Tensor1D[] = [] // Get the list of unique labels - const { values } = tf.unique(labels) + const { values } = this.tf.unique(labels) - const { variance } = tf.moments(features, 0) + const { variance } = this.tf.moments(features, 0) const epsilon = variance.max().mul(this.varSmoothing) - tf.unstack(values).forEach((c: tf.Tensor) => { - const mask = tf.equal(labels, c).toFloat() - const numInstances = tf.sum(mask) - const mean = tf + this.tf.unstack(values).forEach((c: Tensor) => { + const mask = this.tf.equal(labels, c).toFloat() + const numInstances = this.tf.sum(mask) + const mean = this.tf .mul(features, mask.expandDims(1)) .sum(0) .div(numInstances) - const variance = tf + const variance = this.tf .sub(features, mean) .mul(mask.expandDims(1)) .pow(2) @@ -86,8 +88,8 @@ export abstract class BaseNaiveBayes extends Serialize { .div(numInstances) .add(epsilon) - meansByLabel.push(mean as tf.Tensor1D) - variancesByLabel.push(variance as tf.Tensor1D) + meansByLabel.push(mean as Tensor1D) + variancesByLabel.push(variance as Tensor1D) }) return { values, meansByLabel, variancesByLabel } @@ -104,23 +106,23 @@ export abstract class BaseNaiveBayes extends Serialize { /** * Predict the probability of samples assigned to each observed label. * @param X - * @returns {tf.Tensor} Probabilities + * @returns {this.tf.Tensor} Probabilities */ public predictProba(X: Scikit2D) { const features = convertToNumericTensor2D(X) - const probabilities = tf.tidy(() => { - let probs: tf.Tensor1D[] = [] + const probabilities = this.tf.tidy(() => { + let probs: Tensor1D[] = [] this.classes.unstack().forEach((_, idx) => { // Get the mean for this label const mean = this.means[idx] const variance = this.variances[idx] const prob = this.kernel(features, mean, variance) - probs.push(prob as tf.Tensor1D) + probs.push(prob as Tensor1D) }) - const withoutPriors = tf.stack(probs, 1) as tf.Tensor2D + const withoutPriors = this.tf.stack(probs, 1) as Tensor2D if (this.priors) { return withoutPriors.mul(this.priors) } else { @@ -134,7 +136,7 @@ export abstract class BaseNaiveBayes extends Serialize { /** * Predict the labels assigned to each sample * @param X - * @returns {tf.Tensor} Labels + * @returns {this.tf.Tensor} Labels */ public predict(X: Scikit2D) { const probs = this.predictProba(X) @@ -148,10 +150,10 @@ export abstract class BaseNaiveBayes extends Serialize { * @param variance */ protected abstract kernel( - features: tf.Tensor2D, - mean: tf.Tensor1D, - variance: tf.Tensor1D - ): tf.Tensor1D + features: Tensor2D, + mean: Tensor1D, + variance: Tensor1D + ): Tensor1D public toJson(): string { const jsonClass = JSON.parse(super.toJson() as string) @@ -160,8 +162,8 @@ export abstract class BaseNaiveBayes extends Serialize { jsonClass.priors = this.priors.arraySync() } jsonClass.classes = this.classes.arraySync() - jsonClass.means = this.means.map((t: tf.Tensor1D) => t.arraySync()) - jsonClass.variances = this.variances.map((v: tf.Tensor1D) => v.arraySync()) + jsonClass.means = this.means.map((t: Tensor1D) => t.arraySync()) + jsonClass.variances = this.variances.map((v: Tensor1D) => v.arraySync()) return JSON.stringify(jsonClass) } @@ -169,17 +171,17 @@ export abstract class BaseNaiveBayes extends Serialize { const jsonModel = JSON.parse(model) if (jsonModel.priors) { - jsonModel.priors = tf.tensor(jsonModel.priors) + jsonModel.priors = this.tf.tensor(jsonModel.priors) } - jsonModel.classes = tf.tensor(jsonModel.classes) + jsonModel.classes = this.tf.tensor(jsonModel.classes) const means = [] for (const wMeans of jsonModel.means) { - means.push(tf.tensor(wMeans)) + means.push(this.tf.tensor(wMeans)) } const variances = [] for (const variance of jsonModel.variances) { - variances.push(tf.tensor(variance)) + variances.push(this.tf.tensor(variance)) } jsonModel.means = means jsonModel.variances = variances diff --git a/src/naive_bayes/GaussianNB.test.ts b/src/naive_bayes/GaussianNB.test.ts index 627018e8..a87ae1fb 100644 --- a/src/naive_bayes/GaussianNB.test.ts +++ b/src/naive_bayes/GaussianNB.test.ts @@ -12,7 +12,9 @@ * limitations under the License. * ========================================================================== */ -import { GaussianNB } from './GaussianNB' +import { GaussianNB, setBackend } from '../index' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('GaussianNB', function () { it('without priors', async () => { diff --git a/src/naive_bayes/GaussianNB.ts b/src/naive_bayes/GaussianNB.ts index 6801b841..303f44d8 100644 --- a/src/naive_bayes/GaussianNB.ts +++ b/src/naive_bayes/GaussianNB.ts @@ -12,9 +12,9 @@ * limitations under the License. * ========================================================================== */ -import { tf } from '../shared/globals' import { BaseNaiveBayes } from './BaseNaiveBayes' - +import { getBackend } from '../tf-singleton' +import { Tensor1D, Tensor2D } from '../types' /** * Gaussian Naive Bayes classifier * @@ -47,10 +47,11 @@ import { BaseNaiveBayes } from './BaseNaiveBayes' export class GaussianNB extends BaseNaiveBayes { name = 'GaussianNB' protected kernel( - features: tf.Tensor2D, - mean: tf.Tensor1D, - variance: tf.Tensor1D - ): tf.Tensor1D { + features: Tensor2D, + mean: Tensor1D, + variance: Tensor1D + ): Tensor1D { + let tf = getBackend() return tf.tidy(() => { return tf .sub(features, mean.expandDims(0)) @@ -63,7 +64,7 @@ export class GaussianNB extends BaseNaiveBayes { .expandDims(0) .sqrt() ) - .prod(1) as tf.Tensor1D + .prod(1) as Tensor1D }) } } diff --git a/src/neighbors/BruteNeighborhood.test.ts b/src/neighbors/BruteNeighborhood.test.ts index b263ff51..313ba3fb 100644 --- a/src/neighbors/BruteNeighborhood.test.ts +++ b/src/neighbors/BruteNeighborhood.test.ts @@ -15,6 +15,9 @@ import { neighborhoodGenericTests } from './neighborhoodGenericTests' import { BruteNeighborhood } from './BruteNeighborhood' +import { setBackend } from '../tf-singleton' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) neighborhoodGenericTests( 'BruteNeighborhood', diff --git a/src/neighbors/BruteNeighborhood.ts b/src/neighbors/BruteNeighborhood.ts index 720d7a1d..b1d326f4 100644 --- a/src/neighbors/BruteNeighborhood.ts +++ b/src/neighbors/BruteNeighborhood.ts @@ -15,24 +15,26 @@ import { Neighborhood, NeighborhoodParams } from './Neighborhood' import { Metric } from './Metric' -import { tf } from '../shared/globals' import { assert } from '../typesUtils' - +import { Tensor2D } from '../types' +import { getBackend } from '../tf-singleton' /** * A {@link Neighborhood} implementation that uses a brute force approach * to nearest neighbor search. During a {@link BruteNeighborhood#kNearest} * query, the distance between every entry and the query point is computed. */ export class BruteNeighborhood implements Neighborhood { - private _metric: Metric - private _entries: tf.Tensor2D + _metric: Metric + _entries: Tensor2D + tf: any constructor({ metric, entries }: NeighborhoodParams) { this._metric = metric this._entries = entries + this.tf = getBackend() } - kNearest(k: number, queryPoints: tf.Tensor2D) { + kNearest(k: number, queryPoints: Tensor2D) { const { _metric, _entries } = this assert( @@ -42,27 +44,29 @@ export class BruteNeighborhood implements Neighborhood { // // batched version // const [m, n] = queryPoints.shape - // return tf.tidy(() => { + // return this.tf.tidy(() => { // const negDist = _metric.tensorDistance( // queryPoints.reshape([m, 1, n]), // _entries // ).neg() as Tensor2D - // const { values, indices } = tf.topk(negDist, k) + // const { values, indices } = this.tf.topk(negDist, k) // return { distances: values.neg(), indices } // }) // unbatched version - return tf.tidy(() => { - const result = tf.unstack(queryPoints).map((queryPoint) => { - return tf.tidy(() => { + return this.tf.tidy(() => { + const result = this.tf.unstack(queryPoints).map((queryPoint: any) => { + return this.tf.tidy(() => { const dist = _metric.tensorDistance(queryPoint, _entries).neg() - const { values, indices } = tf.topk(dist, k) + const { values, indices } = this.tf.topk(dist, k) return [values, indices] }) }) return { - distances: tf.stack(result.map((x) => x[0])).neg() as tf.Tensor2D, - indices: tf.stack(result.map((x) => x[1])) as tf.Tensor2D + distances: this.tf + .stack(result.map((x: any) => x[0])) + .neg() as Tensor2D, + indices: this.tf.stack(result.map((x: any) => x[1])) as Tensor2D } }) } diff --git a/src/neighbors/CappedMaxHeap.ts b/src/neighbors/CappedMaxHeap.ts index c77f25db..3031f121 100644 --- a/src/neighbors/CappedMaxHeap.ts +++ b/src/neighbors/CappedMaxHeap.ts @@ -24,8 +24,8 @@ import { assert } from '../typesUtils' * tree traversal. */ export class CappedMaxHeap { - private _keys: Float32Array - private _vals: Int32Array + _keys: Float32Array + _vals: Int32Array /** * Index of the currently first entry. * The entries are added from right to @@ -33,7 +33,7 @@ export class CappedMaxHeap { * adding further elements results in * replacement. */ - private _pos: number + _pos: number /** * Creates a new heap using the given key diff --git a/src/neighbors/KNeighborsBase.ts b/src/neighbors/KNeighborsBase.ts index 3b4dd715..73e7963b 100644 --- a/src/neighbors/KNeighborsBase.ts +++ b/src/neighbors/KNeighborsBase.ts @@ -16,19 +16,21 @@ import { Neighborhood, NeighborhoodParams } from './Neighborhood' import { BruteNeighborhood } from './BruteNeighborhood' import { minkowskiMetric } from './Metric' -import { Scikit1D, Scikit2D } from '../types' +import { Scikit1D, Scikit2D, Tensor2D, Tensor1D } from '../types' import { convertToNumericTensor1D, convertToNumericTensor2D } from '../utils' import { assert } from '../typesUtils' -import { tf } from '../shared/globals' import { KdTree } from './KdTree' import Serialize from '../serialize' +import { getBackend } from '../tf-singleton' const WEIGHTS_FUNCTIONS = { - uniform(distances: tf.Tensor2D) { + uniform(distances: Tensor2D) { + let tf = getBackend() const { shape } = distances - return tf.fill(shape, 1 / shape[1]) as tf.Tensor2D + return tf.fill(shape, 1 / shape[1]) as Tensor2D }, - distance(distances: tf.Tensor2D) { + distance(distances: Tensor2D) { + let tf = getBackend() return tf.tidy(() => { // scale inverse distances by min. to avoid `1/tinyVal == Infinity` const min = distances.min(1, /*keepDims=*/ true) @@ -111,8 +113,8 @@ export class KNeighborsBase extends Serialize implements KNeighborsParams { Object.keys(ALGORITHMS) ) as (keyof typeof ALGORITHMS)[] - private _neighborhood: Neighborhood | undefined - private _y: tf.Tensor1D | undefined + _neighborhood: Neighborhood | undefined + _y: Tensor1D | undefined weights: KNeighborsParams['weights'] algorithm: KNeighborsParams['algorithm'] @@ -148,7 +150,7 @@ export class KNeighborsBase extends Serialize implements KNeighborsParams { nNeighbors, weightsFn, neighborhood: _neighborhood as Neighborhood, - y: _y as tf.Tensor1D + y: _y as Tensor1D } } diff --git a/src/neighbors/KNeighborsClassifier.test.ts b/src/neighbors/KNeighborsClassifier.test.ts index 559a72df..e9895529 100644 --- a/src/neighbors/KNeighborsClassifier.test.ts +++ b/src/neighbors/KNeighborsClassifier.test.ts @@ -13,17 +13,17 @@ * ========================================================================== */ -import { KNeighborsClassifier } from './KNeighborsClassifier' +import { KNeighborsClassifier, setBackend } from '../index' import { KNeighborsParams } from './KNeighborsBase' import { dataUrls } from '../datasets/datasets' import { crossValScore } from '../model_selection/crossValScore' import { KFold } from '../model_selection/KFold' import { arrayEqual } from '../utils' +import { Tensor1D, Tensor2D } from '../types' import '../jestTensorMatchers' import * as dfd from 'danfojs-node' -import { tf } from '../shared/globals' -type Tensor1D = tf.Tensor1D -type Tensor2D = tf.Tensor2D +import * as tf from '@tensorflow/tfjs' +setBackend(tf) function testWithDataset( loadDataUrl: string, diff --git a/src/neighbors/KNeighborsClassifier.ts b/src/neighbors/KNeighborsClassifier.ts index cc38d9a3..6687e537 100644 --- a/src/neighbors/KNeighborsClassifier.ts +++ b/src/neighbors/KNeighborsClassifier.ts @@ -13,14 +13,12 @@ * ========================================================================== */ -import { Scikit1D, Scikit2D } from '../types' +import { Scikit1D, Scikit2D, Tensor1D, Tensor2D } from '../types' import { KNeighborsBase } from './KNeighborsBase' import { convertToNumericTensor2D, convertToTensor1D } from '../utils' import { polyfillUnique } from '../tfUtils' import { accuracy } from '../model_selection/scorers' -import { tf } from '../shared/globals' -type Tensor1D = tf.Tensor1D -type Tensor2D = tf.Tensor2D +import { getBackend } from '../tf-singleton' /** * K-Nearest neighbor regressor. @@ -58,7 +56,7 @@ export class KNeighborsClassifier extends KNeighborsBase { public predictProba(X: Scikit2D): Tensor2D { const { neighborhood, y, nNeighbors, weightsFn } = this._getFitParams() const [nClasses] = this.classes_?.shape as [number] - + let tf = getBackend() return tf.tidy(() => { const _X = convertToNumericTensor2D(X) const nSamples = _X.shape[0] @@ -88,7 +86,7 @@ export class KNeighborsClassifier extends KNeighborsBase { */ public predict(X: Scikit2D): Tensor1D { const classes = this.classes_ as Tensor1D - + let tf = getBackend() return tf.tidy(() => { const probs = this.predictProba(X) const labels = probs.argMax(1) @@ -97,6 +95,7 @@ export class KNeighborsClassifier extends KNeighborsBase { } public async fit(X: Scikit2D, labels: Scikit1D): Promise { + let tf = getBackend() const { values, indices } = tf.tidy(() => { const _labels = convertToTensor1D(labels) polyfillUnique(tf) diff --git a/src/neighbors/KNeighborsRegressor.test.ts b/src/neighbors/KNeighborsRegressor.test.ts index 50c5c418..3782a2b6 100644 --- a/src/neighbors/KNeighborsRegressor.test.ts +++ b/src/neighbors/KNeighborsRegressor.test.ts @@ -13,7 +13,7 @@ * ========================================================================== */ -import { KNeighborsRegressor } from './KNeighborsRegressor' +import { KNeighborsRegressor, setBackend } from '../index' import { KNeighborsParams } from './KNeighborsBase' import { dataUrls } from '../datasets/datasets' import { arrayEqual } from '../utils' @@ -22,10 +22,9 @@ import { KFold } from '../model_selection/KFold' import { negMeanSquaredError } from '../model_selection/scorers' import '../jestTensorMatchers' import * as dfd from 'danfojs-node' -import { tf } from '../shared/globals' - -type Tensor1D = tf.Tensor1D -type Tensor2D = tf.Tensor2D +import { Tensor1D, Tensor2D } from '../types' +import * as tf from '@tensorflow/tfjs' +setBackend(tf) function testWithDataset( loadDataUrl: string, diff --git a/src/neighbors/KNeighborsRegressor.ts b/src/neighbors/KNeighborsRegressor.ts index 8bda66f7..cc66f1e8 100644 --- a/src/neighbors/KNeighborsRegressor.ts +++ b/src/neighbors/KNeighborsRegressor.ts @@ -16,7 +16,7 @@ import { Scikit2D } from '../types' import { KNeighborsBase } from './KNeighborsBase' import { convertToNumericTensor2D } from '../utils' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' /** * K-Nearest neighbor regressor. @@ -46,7 +46,9 @@ export class KNeighborsRegressor extends KNeighborsBase { * for sample `X[i,:]` */ name = 'KNeighborsRegressor' + public predict(X: Scikit2D) { + let tf = getBackend() const { neighborhood, y, nNeighbors, weightsFn } = this._getFitParams() return tf.tidy(() => { @@ -56,7 +58,6 @@ export class KNeighborsRegressor extends KNeighborsBase { const targets = y.gather(indices) const weights = weightsFn(distances) - // return tf.einsum('ij,ij->i', targets, weights) as Tensor1D return tf .matMul( targets.reshape([-1, 1, nNeighbors]), diff --git a/src/neighbors/KdTree.test.ts b/src/neighbors/KdTree.test.ts index 87736ed3..ac468d70 100644 --- a/src/neighbors/KdTree.test.ts +++ b/src/neighbors/KdTree.test.ts @@ -15,5 +15,8 @@ import { neighborhoodGenericTests } from './neighborhoodGenericTests' import { KdTree } from './KdTree' +import { setBackend } from '../index' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) neighborhoodGenericTests('KdTree', KdTree.build) diff --git a/src/neighbors/KdTree.ts b/src/neighbors/KdTree.ts index 2adefcd3..c33a7b1b 100644 --- a/src/neighbors/KdTree.ts +++ b/src/neighbors/KdTree.ts @@ -14,11 +14,13 @@ */ import { assert } from '../typesUtils' -import { tf } from '../shared/globals' + import { Neighborhood, NeighborhoodParams } from './Neighborhood' import * as randUtils from '../randUtils' import { alea } from '../randUtils' import { CappedMaxHeap } from './CappedMaxHeap' +import { Tensor2D } from '../types' +import { getBackend } from '../tf-singleton' const child = (parent: number) => (parent << 1) + 1 const parent = (child: number) => (child - 1) >> 1 @@ -62,35 +64,36 @@ interface KdMetric { * set of points. */ export class KdTree implements Neighborhood { - private _nSamples: number - private _nFeatures: number + _nSamples: number + _nFeatures: number - private _metric: KdMetric + _metric: KdMetric /** * Coordinates of the points contained in this kdTree, not in the order * as they were passed to {@link KdTree.build}. */ - private _points: Vec[] + _points: Vec[] /** * Keeps track of the order, in which the points were originally passed * to {@link KdTree.build}. The `i+1`-th point in `_points` was originally * passed as `_indices[i]+1`-th point to {@link KdTree.build}. */ - private _indices: Int32Array + _indices: Int32Array /** * The bounding box of each tree node. */ - private _bBoxes: Float32Array[] + _bBoxes: Float32Array[] /** * The (i+1)-th leaf of this tree contains the points * `_points[_offsets[i]]` to `_points[_offsets[i+1]-1]`. */ - private _offsets: Int32Array + _offsets: Int32Array - private constructor( + tf: any + constructor( nSamples: number, nFeatures: number, metric: KdMetric, @@ -99,6 +102,7 @@ export class KdTree implements Neighborhood { offsets: Int32Array, indices: Int32Array ) { + this.tf = getBackend() this._nSamples = nSamples this._nFeatures = nFeatures @@ -280,8 +284,8 @@ export class KdTree implements Neighborhood { kNearest( k: number, - queryPoints: tf.Tensor2D - ): { distances: tf.Tensor2D; indices: tf.Tensor2D } { + queryPoints: Tensor2D + ): { distances: Tensor2D; indices: Tensor2D } { const { _nSamples, _nFeatures, @@ -364,8 +368,8 @@ export class KdTree implements Neighborhood { // KNeighborsBaseParams and add backpropagation support // to KdTree. return { - distances: tf.tensor2d(dists, [nQueries, k], 'float32'), - indices: tf.tensor2d(indxs, [nQueries, k], 'int32') + distances: this.tf.tensor2d(dists, [nQueries, k], 'float32'), + indices: this.tf.tensor2d(indxs, [nQueries, k], 'int32') } } } diff --git a/src/neighbors/Metric.test.ts b/src/neighbors/Metric.test.ts index c05c5125..c438a66b 100644 --- a/src/neighbors/Metric.test.ts +++ b/src/neighbors/Metric.test.ts @@ -14,8 +14,10 @@ */ import * as fc from 'fast-check' - +import { setBackend } from '../index' import { Metric, minkowskiMetric } from './Metric' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) const NDIMS = Object.freeze([1, 2, 7]) diff --git a/src/neighbors/Metric.ts b/src/neighbors/Metric.ts index f671d546..cb4dc074 100644 --- a/src/neighbors/Metric.ts +++ b/src/neighbors/Metric.ts @@ -14,7 +14,8 @@ */ import { assert } from '../typesUtils' -import { tf } from '../shared/globals' +import { Tensor, Tensor2D } from '../types' +import { getBackend } from '../tf-singleton' /** * Abstract type of neighbohood distance metrics. @@ -32,7 +33,7 @@ export interface Metric { * @returns A broadcasted distance tensor `D`, where `D[..., i, j]` represents * the distance between point `X[..., i, j, k]` and point `Y[..., i, j, k]`. */ - tensorDistance(u: tf.Tensor, v: tf.Tensor): tf.Tensor + tensorDistance(u: Tensor, v: Tensor): Tensor /** * Returns the distance between two points `u` and `v`. @@ -66,20 +67,20 @@ export interface Metric { toString(): string } -const minkowskiTensorDistance = - (p: number) => (u: tf.Tensor, v: tf.Tensor) => { - // FIXME: tf.norm still underflows and overflows, - // see: https://github.com/tensorflow/tfjs/issues/895 - const m = u.shape[u.rank - 1] ?? NaN - const n = v.shape[v.rank - 1] ?? NaN - assert( - m === n, - `minkowskiDistance(${p}).tensorDistance(u,v): u.shape[-1] must equal v.shape[-1].` - ) - return tf.tidy(() => { - return tf.norm(tf.sub(u, v), p, -1) - }) as tf.Tensor2D - } +const minkowskiTensorDistance = (p: number) => (u: Tensor, v: Tensor) => { + let tf = getBackend() + // FIXME: tf.norm still underflows and overflows, + // see: https://github.com/tensorflow/tfjs/issues/895 + const m = u.shape[u.rank - 1] ?? NaN + const n = v.shape[v.rank - 1] ?? NaN + assert( + m === n, + `minkowskiDistance(${p}).tensorDistance(u,v): u.shape[-1] must equal v.shape[-1].` + ) + return tf.tidy(() => { + return tf.norm(tf.sub(u, v), p, -1) + }) as Tensor2D +} /** * Returns the Minkowski distance metric with the given power `p`. diff --git a/src/neighbors/Neighborhood.ts b/src/neighbors/Neighborhood.ts index 266822f2..504460f0 100644 --- a/src/neighbors/Neighborhood.ts +++ b/src/neighbors/Neighborhood.ts @@ -13,7 +13,7 @@ * ========================================================================== */ -import { tf } from '../shared/globals' +import { Tensor2D } from '../types' import { Metric } from './Metric' /** @@ -29,7 +29,7 @@ export interface NeighborhoodParams { * The row `entries[i,:]` represents the (i+1)-th point. * The nearest neighbors are searched for in these points. */ - entries: tf.Tensor2D + entries: Tensor2D /** * For tree-based neighborhood data structures, this is a * hint as to how many points are to be stored in a single @@ -58,6 +58,6 @@ export interface Neighborhood { */ kNearest( k: number, - queryPoints: tf.Tensor2D - ): { distances: tf.Tensor2D; indices: tf.Tensor2D } + queryPoints: Tensor2D + ): { distances: Tensor2D; indices: Tensor2D } } diff --git a/src/neighbors/neighborhoodGenericTests.ts b/src/neighbors/neighborhoodGenericTests.ts index 275c8583..d3aa7c7d 100644 --- a/src/neighbors/neighborhoodGenericTests.ts +++ b/src/neighbors/neighborhoodGenericTests.ts @@ -14,13 +14,15 @@ */ import * as fc from 'fast-check' -import { tf } from '../shared/globals' import { alea } from '../randUtils' +import { setBackend } from '../tf-singleton' +import * as tf from '@tensorflow/tfjs-node' import { Neighborhood, NeighborhoodParams } from './Neighborhood' import { lhs, shuffle } from '../randUtils' import { minkowskiMetric } from './Metric' import { polyfillUnique } from '../tfUtils' import '../jestTensorMatchers' +setBackend(tf) export function neighborhoodGenericTests( name: string, diff --git a/src/pipeline/Pipeline.test.ts b/src/pipeline/Pipeline.test.ts index 52ead335..52dde96a 100644 --- a/src/pipeline/Pipeline.test.ts +++ b/src/pipeline/Pipeline.test.ts @@ -1,9 +1,14 @@ -import { Pipeline, makePipeline } from './Pipeline' -import { tf } from '../shared/globals' +import { + Pipeline, + makePipeline, + LinearRegression, + SimpleImputer, + MinMaxScaler, + setBackend +} from '../index' +import * as tf from '@tensorflow/tfjs-node' import { tensorEqual } from '../utils' -import { LinearRegression } from '../linear_model/LinearRegression' -import { SimpleImputer } from '../impute/SimpleImputer' -import { MinMaxScaler } from '../preprocessing/MinMaxScaler' +setBackend(tf) describe('Pipeline', function () { it('Use a Pipeline (min-max scaler, and linear regression)', async function () { diff --git a/src/pipeline/Pipeline.ts b/src/pipeline/Pipeline.ts index 6cf5e6c4..dae3850d 100644 --- a/src/pipeline/Pipeline.ts +++ b/src/pipeline/Pipeline.ts @@ -1,9 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { assert } from '../typesUtils' -import { Scikit1D, Scikit2D } from '../types' +import { Scikit1D, Scikit2D, Tensor2D } from '../types' import Serialize from '../serialize' import { toJson, fromJson } from '../ensemble/serializeEnsemble' -import { tf } from '../shared/globals' /* Next steps: @@ -171,22 +170,22 @@ export class Pipeline extends Serialize { return this } - public transform(X: Scikit2D): tf.Tensor2D { + public transform(X: Scikit2D): Tensor2D { this.validateSteps(this.steps) const lastEstimator = this.getLastEstimator() this.assertEstimatorHasFunction(lastEstimator, 'transform') let XT = this.transformExceptLast(X) - return lastEstimator.transform(XT) as tf.Tensor2D + return lastEstimator.transform(XT) as Tensor2D } - public fitTransform(X: Scikit2D, y: Scikit1D): tf.Tensor2D { + public fitTransform(X: Scikit2D, y: Scikit1D): Tensor2D { this.validateSteps(this.steps) const lastEstimator = this.getLastEstimator() this.assertEstimatorHasFunction(lastEstimator, 'fitTransform') let XT = this.fitTransformExceptLast(X) - return lastEstimator.fitTransform(XT) as tf.Tensor2D + return lastEstimator.fitTransform(XT) as Tensor2D } public predict(X: Scikit2D) { diff --git a/src/preprocessing/LabelEncoder.test.ts b/src/preprocessing/LabelEncoder.test.ts index 062929c8..7353a94a 100644 --- a/src/preprocessing/LabelEncoder.test.ts +++ b/src/preprocessing/LabelEncoder.test.ts @@ -1,5 +1,7 @@ -import { LabelEncoder } from './LabelEncoder' +import { LabelEncoder, setBackend } from '../index' import * as dfd from 'danfojs-node' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('LabelEncoder', function () { it('LabelEncoder works for Series', function () { diff --git a/src/preprocessing/LabelEncoder.ts b/src/preprocessing/LabelEncoder.ts index 9067e707..76df0590 100644 --- a/src/preprocessing/LabelEncoder.ts +++ b/src/preprocessing/LabelEncoder.ts @@ -13,11 +13,10 @@ * ========================================================================== */ -import { Scikit1D } from '../types' -import { tf } from '../shared/globals' -import { isSeriesInterface } from '../typesUtils' +import { Scikit1D, Tensor1D } from '../types' +import { isSeriesInterface, isTensor } from '../typesUtils' import Serialize from '../serialize' - +import { getBackend } from '../tf-singleton' /* Next steps: 1. Pass the next 5 tests @@ -43,8 +42,11 @@ export class LabelEncoder extends Serialize { /** Useful for pipelines and column transformers to have a default name for transforms */ name = 'LabelEncoder' + tf: any + constructor() { super() + this.tf = getBackend() this.classes = [] } @@ -52,7 +54,7 @@ export class LabelEncoder extends Serialize { if (isSeriesInterface(X)) { return X.values as any[] } - if (X instanceof tf.Tensor) { + if (isTensor(X)) { return X.arraySync() } return X @@ -93,7 +95,7 @@ export class LabelEncoder extends Serialize { * // [0, 1, 2, 3] * ``` */ - public transform(X: Scikit1D): tf.Tensor1D { + public transform(X: Scikit1D): Tensor1D { const arr = this.convertTo1DArray(X) const labels = this.classesToMapping(this.classes) @@ -101,10 +103,10 @@ export class LabelEncoder extends Serialize { let val = labels.get(value) return val === undefined ? -1 : val }) - return tf.tensor1d(encodedData) + return this.tf.tensor1d(encodedData) } - public fitTransform(X: Scikit1D): tf.Tensor1D { + public fitTransform(X: Scikit1D): Tensor1D { return this.fit(X).transform(X) } diff --git a/src/preprocessing/MaxAbsScaler.test.ts b/src/preprocessing/MaxAbsScaler.test.ts index b69f584a..1ee88766 100644 --- a/src/preprocessing/MaxAbsScaler.test.ts +++ b/src/preprocessing/MaxAbsScaler.test.ts @@ -1,6 +1,7 @@ -import { MaxAbsScaler } from './MaxAbsScaler' +import { MaxAbsScaler, setBackend } from '../index' import * as dfd from 'danfojs-node' -import { tf } from '../shared/globals' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) import { arrayEqual } from '../utils' describe('MaxAbsScaler', function () { diff --git a/src/preprocessing/MaxAbsScaler.ts b/src/preprocessing/MaxAbsScaler.ts index a0c7a818..e9f52bac 100644 --- a/src/preprocessing/MaxAbsScaler.ts +++ b/src/preprocessing/MaxAbsScaler.ts @@ -17,8 +17,8 @@ import { convertToNumericTensor2D } from '../utils' import { assert, isDataFrameInterface, isScikit2D } from '../typesUtils' import { tensorMax, turnZerosToOnes } from '../math' import { TransformerMixin } from '../mixins' -import { Scikit2D } from '../types' -import { tf } from '../shared/globals' +import { Scikit2D, Tensor1D, Tensor2D } from '../types' +import { getBackend } from '../tf-singleton' /* Next steps: @@ -55,7 +55,7 @@ Next steps: */ export class MaxAbsScaler extends TransformerMixin { /** The per-feature scale that we see in the dataset. We divide by this number. */ - scale: tf.Tensor1D + scale: Tensor1D /** The number of features seen during fit */ nFeaturesIn: number @@ -71,7 +71,8 @@ export class MaxAbsScaler extends TransformerMixin { constructor() { super() - this.scale = tf.tensor1d([]) + this.tf = getBackend() + this.scale = this.tf.tensor1d([]) this.nFeaturesIn = 0 this.nSamplesSeen = 0 this.featureNamesIn = [] @@ -83,10 +84,10 @@ export class MaxAbsScaler extends TransformerMixin { public fit(X: Scikit2D): MaxAbsScaler { assert(isScikit2D(X), 'Data can not be converted to a 2D matrix.') const tensorArray = convertToNumericTensor2D(X) - const scale = tensorMax(tensorArray.abs(), 0, true) as tf.Tensor1D + const scale = tensorMax(tensorArray.abs(), 0, true) as Tensor1D // Deal with 0 scale values - this.scale = turnZerosToOnes(scale) as tf.Tensor1D + this.scale = turnZerosToOnes(scale) as Tensor1D this.nSamplesSeen = tensorArray.shape[0] this.nFeaturesIn = tensorArray.shape[1] @@ -99,20 +100,20 @@ export class MaxAbsScaler extends TransformerMixin { /** * Transform the data using the fitted scaler */ - public transform(X: Scikit2D): tf.Tensor2D { + public transform(X: Scikit2D): Tensor2D { assert(isScikit2D(X), 'Data can not be converted to a 2D matrix.') const tensorArray = convertToNumericTensor2D(X) - const outputData = tensorArray.div(this.scale) + const outputData = tensorArray.div(this.scale) return outputData } /** * Inverse transform the data using the fitted scaler */ - public inverseTransform(X: Scikit2D): tf.Tensor2D { + public inverseTransform(X: Scikit2D): Tensor2D { assert(isScikit2D(X), 'Data can not be converted to a 2D matrix.') const tensorArray = convertToNumericTensor2D(X) - const outputData = tensorArray.mul(this.scale) + const outputData = tensorArray.mul(this.scale) return outputData } } diff --git a/src/preprocessing/MinMaxScaler.test.ts b/src/preprocessing/MinMaxScaler.test.ts index 7b15cc47..19f89644 100644 --- a/src/preprocessing/MinMaxScaler.test.ts +++ b/src/preprocessing/MinMaxScaler.test.ts @@ -1,8 +1,9 @@ -import { MinMaxScaler } from './MinMaxScaler' +import { MinMaxScaler, setBackend } from '../index' import * as dfd from 'danfojs-node' import { isDataFrameInterface, isSeriesInterface } from '../typesUtils' import { ScikitVecOrMatrix } from '../types' -import { tf } from '../shared/globals' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) export function convertTensorToInputType( tensor: tf.Tensor, diff --git a/src/preprocessing/MinMaxScaler.ts b/src/preprocessing/MinMaxScaler.ts index fe37e04d..64d655ed 100644 --- a/src/preprocessing/MinMaxScaler.ts +++ b/src/preprocessing/MinMaxScaler.ts @@ -14,11 +14,11 @@ */ import { convertToNumericTensor2D } from '../utils' -import { Scikit2D, Transformer } from '../types' +import { Scikit2D, Tensor1D, Tensor2D, Transformer } from '../types' import { isScikit2D, assert, isDataFrameInterface } from '../typesUtils' import { tensorMin, tensorMax, turnZerosToOnes } from '../math' import { TransformerMixin } from '../mixins' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' /* Next steps: @@ -63,16 +63,16 @@ export class MinMaxScaler extends TransformerMixin implements Transformer { featureRange: [number, number] /** The per-feature scale that we see in the dataset. */ - scale: tf.Tensor1D + scale: Tensor1D - min: tf.Tensor1D + min: Tensor1D /** The per-feature minimum that we see in the dataset. */ - dataMin: tf.Tensor1D + dataMin: Tensor1D /** The per-feature maximum that we see in the dataset. */ - dataMax: tf.Tensor1D + dataMax: Tensor1D /** The per-feature range that we see in the dataset. */ - dataRange: tf.Tensor1D + dataRange: Tensor1D /** The number of features seen during fit */ nFeaturesIn: number @@ -87,13 +87,14 @@ export class MinMaxScaler extends TransformerMixin implements Transformer { constructor({ featureRange = [0, 1] }: MinMaxScalerParams = {}) { super() + this.tf = getBackend() this.featureRange = featureRange - this.scale = tf.tensor1d([]) - this.min = tf.tensor1d([]) - this.dataMin = tf.tensor1d([]) - this.dataMax = tf.tensor1d([]) - this.dataRange = tf.tensor1d([]) + this.scale = this.tf.tensor1d([]) + this.min = this.tf.tensor1d([]) + this.dataMin = this.tf.tensor1d([]) + this.dataMax = this.tf.tensor1d([]) + this.dataRange = this.tf.tensor1d([]) this.nFeaturesIn = 0 this.nSamplesSeen = 0 @@ -115,14 +116,14 @@ export class MinMaxScaler extends TransformerMixin implements Transformer { 'featureRange needs to contain exactly two numbers where the first is less than the second' ) const tensorArray = convertToNumericTensor2D(X) - const max = tensorMax(tensorArray, 0, true) as tf.Tensor1D - const min = tensorMin(tensorArray, 0, true) as tf.Tensor1D - const range = max.sub(min) - this.scale = tf.div( + const max = tensorMax(tensorArray, 0, true) as Tensor1D + const min = tensorMin(tensorArray, 0, true) as Tensor1D + const range = max.sub(min) + this.scale = this.tf.div( this.featureRange[1] - this.featureRange[0], turnZerosToOnes(range) ) - this.min = tf.sub(this.featureRange[0], min.mul(this.scale)) + this.min = this.tf.sub(this.featureRange[0], min.mul(this.scale)) this.dataMin = min this.dataMax = max this.dataRange = range @@ -137,20 +138,20 @@ export class MinMaxScaler extends TransformerMixin implements Transformer { /** * Transform the data using the fitted scaler * */ - public transform(X: Scikit2D): tf.Tensor2D { + public transform(X: Scikit2D): Tensor2D { assert(isScikit2D(X), 'Data can not be converted to a 2D matrix.') const tensorArray = convertToNumericTensor2D(X) - const outputData = tensorArray.mul(this.scale).add(this.min) + const outputData = tensorArray.mul(this.scale).add(this.min) return outputData } /** * Inverse transform the data using the fitted scaler * */ - public inverseTransform(X: Scikit2D): tf.Tensor2D { + public inverseTransform(X: Scikit2D): Tensor2D { assert(isScikit2D(X), 'Data can not be converted to a 2D matrix.') const tensorArray = convertToNumericTensor2D(X) - const outputData = tensorArray.sub(this.min).div(this.scale) + const outputData = tensorArray.sub(this.min).div(this.scale) return outputData } } diff --git a/src/preprocessing/Normalizer.test.ts b/src/preprocessing/Normalizer.test.ts index 65d20877..44919205 100644 --- a/src/preprocessing/Normalizer.test.ts +++ b/src/preprocessing/Normalizer.test.ts @@ -1,6 +1,8 @@ -import { Normalizer } from './Normalizer' +import { Normalizer, setBackend } from '../index' import * as dfd from 'danfojs-node' import { arrayEqual } from '../utils' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('Normalizer', function () { it('Standardize values in a DataFrame using a Normalizer (l1 case)', function () { diff --git a/src/preprocessing/Normalizer.ts b/src/preprocessing/Normalizer.ts index e02cc70a..73cecc52 100644 --- a/src/preprocessing/Normalizer.ts +++ b/src/preprocessing/Normalizer.ts @@ -14,10 +14,10 @@ */ import { convertToNumericTensor2D } from '../utils' -import { Scikit2D } from '../types' +import { Scikit2D, Tensor2D } from '../types' import { isScikit2D, assert, isDataFrameInterface } from '../typesUtils' import { TransformerMixin } from '../mixins' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' /* Next steps: @@ -69,6 +69,7 @@ export class Normalizer extends TransformerMixin { constructor({ norm = 'l2' }: NormalizerParams = {}) { super() + this.tf = getBackend() this.norm = norm this.nFeaturesIn = 0 this.featureNamesIn = [] @@ -90,11 +91,11 @@ export class Normalizer extends TransformerMixin { /** * Transform the data using the Normalizer * */ - public transform(X: Scikit2D): tf.Tensor2D { + public transform(X: Scikit2D): Tensor2D { assert(isScikit2D(X), 'Data can not be converted to a 2D matrix.') const tensorArray = convertToNumericTensor2D(X) if (this.norm === 'l1') { - const means = tf.abs(tensorArray).sum(1).reshape([-1, 1]) + const means = this.tf.abs(tensorArray).sum(1).reshape([-1, 1]) return tensorArray.divNoNan(means) } if (this.norm === 'l2') { @@ -102,7 +103,7 @@ export class Normalizer extends TransformerMixin { return tensorArray.divNoNan(means) } // max case - const means = tf.abs(tensorArray).max(1).reshape([-1, 1]) + const means = this.tf.abs(tensorArray).max(1).reshape([-1, 1]) return tensorArray.divNoNan(means) } } diff --git a/src/preprocessing/OneHotEncoder.test.ts b/src/preprocessing/OneHotEncoder.test.ts index 35100413..3064809d 100644 --- a/src/preprocessing/OneHotEncoder.test.ts +++ b/src/preprocessing/OneHotEncoder.test.ts @@ -1,6 +1,7 @@ -import { tf } from '../shared/globals' -import { OneHotEncoder } from './OneHotEncoder' +import { OneHotEncoder, setBackend } from '../index' import { arrayTo2DColumn } from '../utils' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('OneHotEncoder', function () { it('OneHotEncoder works on array', function () { diff --git a/src/preprocessing/OneHotEncoder.ts b/src/preprocessing/OneHotEncoder.ts index ea5f4f93..f08685f4 100644 --- a/src/preprocessing/OneHotEncoder.ts +++ b/src/preprocessing/OneHotEncoder.ts @@ -14,9 +14,10 @@ */ import { convertScikit2DToArray } from '../utils' -import { Scikit1D, Scikit2D } from '../types' +import { Scikit1D, Scikit2D, Tensor1D, Tensor2D } from '../types' import { TransformerMixin } from '../mixins' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' + import { isDataFrameInterface } from '../typesUtils' /* @@ -106,6 +107,7 @@ export class OneHotEncoder extends TransformerMixin { drop }: OneHotEncoderParams = {}) { super() + this.tf = getBackend() this.categoriesParam = categories this.categories = [] this.handleUnknown = handleUnknown @@ -192,26 +194,23 @@ export class OneHotEncoder extends TransformerMixin { /** Generalization of the tf.oneHot that can handle "one-hotting" with a single column * output. */ - convertToOneHot( - tensor: tf.Tensor1D, - numberOfOneHotColumns: number - ): tf.Tensor2D { + convertToOneHot(tensor: Tensor1D, numberOfOneHotColumns: number): Tensor2D { if (numberOfOneHotColumns >= 2) { - return tf.oneHot(tensor, numberOfOneHotColumns) as tf.Tensor2D + return this.tf.oneHot(tensor, numberOfOneHotColumns) as Tensor2D } if (numberOfOneHotColumns === 1) { // Every integer that isn't 0 becomes 0 - tensor = tf.where( + tensor = this.tf.where( tensor.equal(0), - tf.ones(tensor.shape, 'int32'), - tf.zeros(tensor.shape, 'int32') + this.tf.ones(tensor.shape, 'int32'), + this.tf.zeros(tensor.shape, 'int32') ) return tensor.reshape([-1, 1]) } // Case where numberOfOneHotColumns = 0 - return tf.tensor2d([]) + return this.tf.tensor2d([]) } /** @@ -225,12 +224,16 @@ export class OneHotEncoder extends TransformerMixin { * ``` */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - public transform(X: Scikit2D, y?: Scikit1D): tf.Tensor2D { + public transform(X: Scikit2D, y?: Scikit1D): Tensor2D { const array2D = convertScikit2DToArray(X) const result2D = this.loopOver2DArrayToUseLabels(array2D) - const newTensor = tf.tensor2d(result2D as number[][], undefined, 'int32') - return tf.concat( - newTensor.unstack(1).map((el, i) => { + const newTensor = this.tf.tensor2d( + result2D as number[][], + undefined, + 'int32' + ) + return this.tf.concat( + newTensor.unstack(1).map((el: any, i: any) => { let categoryNumber = this.categories[i].length let numberOfOneHotColumns = this.drop === 'first' ? categoryNumber - 1 : categoryNumber @@ -238,13 +241,13 @@ export class OneHotEncoder extends TransformerMixin { return val }), 1 - ) as tf.Tensor2D + ) as Tensor2D } /** Only works for single column OneHotEncoding */ - public inverseTransform(X: tf.Tensor2D): any[] { + public inverseTransform(X: Tensor2D): any[] { let labels = this.classesToMapping(this.categories[0]) - const tensorLabels = X.argMax(1) as tf.Tensor1D + const tensorLabels = X.argMax(1) as Tensor1D const invMap = new Map(Array.from(labels, (a) => a.reverse()) as any) const tempData = tensorLabels.arraySync().map((value) => { diff --git a/src/preprocessing/OrdinalEncoder.test.ts b/src/preprocessing/OrdinalEncoder.test.ts index 57666262..a30edcb5 100644 --- a/src/preprocessing/OrdinalEncoder.test.ts +++ b/src/preprocessing/OrdinalEncoder.test.ts @@ -1,5 +1,7 @@ -import { OrdinalEncoder } from './OrdinalEncoder' +import { OrdinalEncoder, setBackend } from '../index' import { arrayTo2DColumn } from '../utils' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('OrdinalEncoder', function () { it('OrdinalEncoder works on array', function () { diff --git a/src/preprocessing/OrdinalEncoder.ts b/src/preprocessing/OrdinalEncoder.ts index 4eb78752..d5372652 100644 --- a/src/preprocessing/OrdinalEncoder.ts +++ b/src/preprocessing/OrdinalEncoder.ts @@ -14,9 +14,9 @@ */ import { convertScikit2DToArray } from '../utils' -import { Scikit1D, Scikit2D } from '../types' +import { Scikit1D, Scikit2D, Tensor2D } from '../types' import { TransformerMixin } from '../mixins' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' import { isDataFrameInterface } from '../typesUtils' /* @@ -97,6 +97,7 @@ export class OrdinalEncoder extends TransformerMixin { unknownValue = NaN }: OrdinalEncoderParams = {}) { super() + this.tf = getBackend() this.categoriesParam = categories this.categories = [] this.handleUnknown = handleUnknown @@ -172,9 +173,9 @@ export class OrdinalEncoder extends TransformerMixin { * Encodes the data using the fitted OrdinalEncoder. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - public transform(X: Scikit2D, y?: Scikit1D): tf.Tensor2D { + public transform(X: Scikit2D, y?: Scikit1D): Tensor2D { const array2D = convertScikit2DToArray(X) const result2D = this.loopOver2DArrayToUseLabels(array2D) - return tf.tensor2d(result2D as number[][], undefined, 'int32') + return this.tf.tensor2d(result2D as number[][], undefined, 'int32') } } diff --git a/src/preprocessing/RobustScaler.test.ts b/src/preprocessing/RobustScaler.test.ts index 3a31475e..8899dd93 100644 --- a/src/preprocessing/RobustScaler.test.ts +++ b/src/preprocessing/RobustScaler.test.ts @@ -1,6 +1,8 @@ -import { RobustScaler } from './RobustScaler' +import { RobustScaler, setBackend } from '../index' import * as dfd from 'danfojs-node' import { arrayEqual } from '../utils' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('RobustScaler', function () { it('Standardize values in a DataFrame using a RobustScaler', function () { diff --git a/src/preprocessing/RobustScaler.ts b/src/preprocessing/RobustScaler.ts index 9a78f129..33ea6d72 100644 --- a/src/preprocessing/RobustScaler.ts +++ b/src/preprocessing/RobustScaler.ts @@ -14,12 +14,12 @@ */ import { convertToNumericTensor2D } from '../utils' -import { Scikit2D } from '../types' +import { Scikit2D, Tensor1D, Tensor2D } from '../types' import { isScikit2D, assert, isDataFrameInterface } from '../typesUtils' import { turnZerosToOnes } from '../math' import { TransformerMixin } from '../mixins' import { quantileSeq } from 'mathjs' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' /* Next steps: @@ -91,10 +91,10 @@ function removeMissingValuesFromArray(arr: any[]) { */ export class RobustScaler extends TransformerMixin { /** The per-feature scale that we see in the dataset. We divide by this number. */ - scale: tf.Tensor1D + scale: Tensor1D /** The per-feature median that we see in the dataset. We subtrace this number. */ - center: tf.Tensor1D + center: Tensor1D /** The number of features seen during fit */ nFeaturesIn: number @@ -115,8 +115,9 @@ export class RobustScaler extends TransformerMixin { withScaling = true }: RobustScalerParams = {}) { super() - this.scale = tf.tensor1d([]) - this.center = tf.tensor1d([]) + this.tf = getBackend() + this.scale = this.tf.tensor1d([]) + this.center = this.tf.tensor1d([]) this.quantileRange = quantileRange this.withScaling = withScaling this.withCentering = withCentering @@ -146,13 +147,13 @@ export class RobustScaler extends TransformerMixin { ) const tensorArray = convertToNumericTensor2D(X) - const rowOrientedArray = tensorArray.transpose().arraySync() + const rowOrientedArray = tensorArray.transpose().arraySync() if (this.withCentering) { const quantiles = rowOrientedArray.map((arr: number[] | string[]) => quantileSeq(removeMissingValuesFromArray(arr), 0.5) ) - this.center = tf.tensor1d(quantiles as number[]) + this.center = this.tf.tensor1d(quantiles as number[]) } if (this.withScaling) { const quantiles = rowOrientedArray.map((arr: number[] | string[]) => @@ -161,13 +162,13 @@ export class RobustScaler extends TransformerMixin { highPercentile / 100 ]) ) - const scale = tf.tensor1d(quantiles.map((el: any) => el[1] - el[0])) + const scale = this.tf.tensor1d(quantiles.map((el: any) => el[1] - el[0])) // But what happens if max = min, ie.. we are dealing with a constant vector? // In the case above, scale = max - min = 0 and we'll divide by 0 which is no bueno. // The common practice in cases where the vector is constant is to change the 0 elements // in scale to 1, so that the division doesn't fail. We do that below - this.scale = turnZerosToOnes(scale) as tf.Tensor1D + this.scale = turnZerosToOnes(scale) as Tensor1D } this.nFeaturesIn = tensorArray.shape[1] @@ -177,7 +178,7 @@ export class RobustScaler extends TransformerMixin { return this } - public transform(X: Scikit2D): tf.Tensor2D { + public transform(X: Scikit2D): Tensor2D { assert(isScikit2D(X), 'Data can not be converted to a 2D matrix.') let tensorArray = convertToNumericTensor2D(X) @@ -185,17 +186,17 @@ export class RobustScaler extends TransformerMixin { tensorArray = tensorArray.sub(this.center) } if (this.withScaling) { - tensorArray = tensorArray.div(this.scale) + tensorArray = tensorArray.div(this.scale) } return tensorArray } - public inverseTransform(X: Scikit2D): tf.Tensor2D { + public inverseTransform(X: Scikit2D): Tensor2D { assert(isScikit2D(X), 'Data can not be converted to a 2D matrix.') let tensorArray = convertToNumericTensor2D(X) if (this.withScaling) { - tensorArray = tensorArray.mul(this.scale) + tensorArray = tensorArray.mul(this.scale) } if (this.withCentering) { tensorArray = tensorArray.add(this.center) diff --git a/src/preprocessing/StandardScaler.test.ts b/src/preprocessing/StandardScaler.test.ts index e3830ef7..0ca232ba 100644 --- a/src/preprocessing/StandardScaler.test.ts +++ b/src/preprocessing/StandardScaler.test.ts @@ -1,5 +1,7 @@ -import { StandardScaler } from './StandardScaler' +import { StandardScaler, setBackend } from '../index' import * as dfd from 'danfojs-node' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('StandardScaler', function () { it('StandardScaler works for DataFrame', function () { diff --git a/src/preprocessing/StandardScaler.ts b/src/preprocessing/StandardScaler.ts index 90cd9dde..43b4d40d 100644 --- a/src/preprocessing/StandardScaler.ts +++ b/src/preprocessing/StandardScaler.ts @@ -14,11 +14,11 @@ */ import { convertToNumericTensor2D } from '../utils' -import { Scikit2D } from '../types' +import { Scikit2D, Tensor1D, Tensor2D, Tensor } from '../types' import { isScikit2D, assert, isDataFrameInterface } from '../typesUtils' import { tensorMean, tensorStd, turnZerosToOnes } from '../math' import { TransformerMixin } from '../mixins' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' /* Next steps: @@ -65,10 +65,10 @@ export interface StandardScalerParams { */ export class StandardScaler extends TransformerMixin { /** The per-feature scale that we see in the dataset. We divide by this number. */ - scale: tf.Tensor + scale: Tensor /** The per-feature mean that we see in the dataset. We subtract by this number. */ - mean: tf.Tensor + mean: Tensor /** Whether or not we should subtract the mean */ withMean: boolean @@ -90,10 +90,11 @@ export class StandardScaler extends TransformerMixin { constructor({ withMean = true, withStd = true }: StandardScalerParams = {}) { super() + this.tf = getBackend() this.withMean = withMean this.withStd = withStd - this.scale = tf.tensor1d([]) - this.mean = tf.tensor1d([]) + this.scale = this.tf.tensor1d([]) + this.mean = this.tf.tensor1d([]) this.nFeaturesIn = 0 this.nSamplesSeen = 0 this.featureNamesIn = [] @@ -111,7 +112,7 @@ export class StandardScaler extends TransformerMixin { if (this.withStd) { const std = tensorStd(tensorArray, 0, true) // Deal with zero variance issues - this.scale = turnZerosToOnes(std) as tf.Tensor1D + this.scale = turnZerosToOnes(std) as Tensor1D } this.nSamplesSeen = tensorArray.shape[0] @@ -125,7 +126,7 @@ export class StandardScaler extends TransformerMixin { /** * Transform the data using the fitted scaler */ - public transform(X: Scikit2D): tf.Tensor2D { + public transform(X: Scikit2D): Tensor2D { assert(isScikit2D(X), 'Data can not be converted to a 2D matrix.') let tensorArray = convertToNumericTensor2D(X) if (this.withMean) { @@ -140,7 +141,7 @@ export class StandardScaler extends TransformerMixin { /** * Inverse transform the data using the fitted scaler */ - public inverseTransform(X: Scikit2D): tf.Tensor2D { + public inverseTransform(X: Scikit2D): Tensor2D { assert(isScikit2D(X), 'Data can not be converted to a 2D matrix.') let tensorArray = convertToNumericTensor2D(X) if (this.withStd) { diff --git a/src/serialize.ts b/src/serialize.ts index 2ef36c94..edb94748 100644 --- a/src/serialize.ts +++ b/src/serialize.ts @@ -4,6 +4,7 @@ */ import { tf } from './shared/globals' +import omit from 'lodash/omit' export default class Serialize { public name = 'Serialize' // default name for all inherited class @@ -13,9 +14,11 @@ export default class Serialize { * @returns Json string */ public toJson(): string | Promise { - const thisCopy: any = Object.assign({}, this) + const thisCopy: any = Object.assign({}, omit(this, 'tf')) + console.log(thisCopy) for (const key of Object.keys(thisCopy)) { let value = thisCopy[key] + if (value instanceof tf.Tensor) { thisCopy[key] = { type: 'Tensor', diff --git a/src/svm/LinearSVC.test.ts b/src/svm/LinearSVC.test.ts index 142a7034..cbae56fc 100644 --- a/src/svm/LinearSVC.test.ts +++ b/src/svm/LinearSVC.test.ts @@ -1,4 +1,6 @@ -import { LinearSVC } from './LinearSVC' +import { LinearSVC, setBackend } from '../index' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('LinearSVC', function () { it('Works on arrays (small example)', async function () { diff --git a/src/svm/LinearSVC.ts b/src/svm/LinearSVC.ts index bedd3c64..ee6ff40f 100644 --- a/src/svm/LinearSVC.ts +++ b/src/svm/LinearSVC.ts @@ -14,7 +14,7 @@ // */ import { SGDClassifier } from '../linear_model/SgdClassifier' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' // First pass at a LinearSVC implementation using gradient descent // Trying to mimic the API of scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearSVC.html @@ -60,9 +60,6 @@ export interface LinearSVCParams { * ``` */ export class LinearSVC extends SGDClassifier { - /** Useful for pipelines and column transformers to have a default name for transforms */ - name = 'LinearSVC' - constructor({ penalty = 'l2', C = 1, @@ -70,6 +67,7 @@ export class LinearSVC extends SGDClassifier { }: LinearSVCParams = {}) { // Assume Binary classification // If we call fit, and it isn't binary then update args + let tf = getBackend() super({ modelCompileArgs: { optimizer: tf.train.adam(0.1), @@ -101,5 +99,7 @@ export class LinearSVC extends SGDClassifier { optimizerType: 'adam', lossType: 'hingeLoss' }) + /** Useful for pipelines and column transformers to have a default name for transforms */ + this.name = 'LinearSVC' } } diff --git a/src/svm/LinearSVR.test.ts b/src/svm/LinearSVR.test.ts index 2e325983..87bfd08e 100644 --- a/src/svm/LinearSVR.test.ts +++ b/src/svm/LinearSVR.test.ts @@ -1,7 +1,7 @@ -import { LinearSVR } from './LinearSVR' - +import { LinearSVR, setBackend } from '../index' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) import { tensorEqual } from '../utils' -import { tf } from '../shared/globals' describe('LinearSVR', function () { it('Works on arrays (small example)', async function () { diff --git a/src/svm/LinearSVR.ts b/src/svm/LinearSVR.ts index f7efe1e0..8891243b 100644 --- a/src/svm/LinearSVR.ts +++ b/src/svm/LinearSVR.ts @@ -14,7 +14,7 @@ // */ import { SGDRegressor } from '../linear_model/SgdRegressor' -import { tf } from '../shared/globals' +import { getBackend } from '../tf-singleton' // First pass at a LinearSVC implementation using gradient descent // Trying to mimic the API of scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearSVC.html @@ -64,9 +64,6 @@ export interface LinearSVRParams { * ``` */ export class LinearSVR extends SGDRegressor { - /** Useful for pipelines and column transformers to have a default name for transforms */ - name = 'LinearSVR' - constructor({ epsilon = 0, C = 1, @@ -74,6 +71,7 @@ export class LinearSVR extends SGDRegressor { }: LinearSVRParams = {}) { // Assume Binary classification // If we call fit, and it isn't binary then update args + let tf = getBackend() super({ modelCompileArgs: { optimizer: tf.train.adam(0.1), @@ -98,5 +96,7 @@ export class LinearSVR extends SGDRegressor { optimizerType: 'adam', lossType: 'custom' }) + /** Useful for pipelines and column transformers to have a default name for transforms */ + this.name = 'LinearSVR' } } diff --git a/src/svm/SVC.ts b/src/svm/SVC.ts index b8d0ef8a..1c67f6a4 100644 --- a/src/svm/SVC.ts +++ b/src/svm/SVC.ts @@ -1,4 +1,4 @@ -// import { tf } from '../shared/globals' +// import { getBackend } from '../tf-singleton' // import { Scikit1D, Scikit2D } from '../index' // import { SVM, SVMParam, KERNEL_TYPE, ISVMParam, SVM_TYPE } from 'libsvm-wasm' // import { convertToNumericTensor1D, convertToNumericTensor2D } from '../utils' @@ -19,10 +19,10 @@ // } // export class SVC { -// private svm?: SVM -// private svmParam: SVMParam -// private gammaMode = 'scale' -// private classWeight: { [key: number]: number } | 'balanced' | undefined +// svm?: SVM +// svmParam: SVMParam +// gammaMode = 'scale' +// classWeight: { [key: number]: number } | 'balanced' | undefined // constructor({ // kernel = 'RBF', diff --git a/src/svm/SVR.ts b/src/svm/SVR.ts index bf817f79..47dc6b60 100644 --- a/src/svm/SVR.ts +++ b/src/svm/SVR.ts @@ -1,4 +1,4 @@ -// import { tf } from '../shared/globals' +// import { getBackend } from '../tf-singleton' // import { Scikit1D, Scikit2D } from '../index' // import { SVM, SVMParam, KERNEL_TYPE, ISVMParam, SVM_TYPE } from 'libsvm-wasm' // import { convertToNumericTensor1D, convertToNumericTensor2D } from '../utils' @@ -18,9 +18,9 @@ // } // export class SVR { -// private svm?: SVM -// private svmParam: SVMParam -// private gammaMode = 'scale' +// svm?: SVM +// svmParam: SVMParam +// gammaMode = 'scale' // constructor({ // kernel = 'RBF', diff --git a/src/tf-singleton.ts b/src/tf-singleton.ts new file mode 100644 index 00000000..acb5ffb8 --- /dev/null +++ b/src/tf-singleton.ts @@ -0,0 +1,23 @@ +let tf: any = null + +export function setBackend(tfInput: any) { + tf = tfInput +} + +export function getBackend() { + if (tf === null) { + throw Error(` +============================ +Howdy 👋👋. Looks like you are running scikit but you haven't set a Tensorflow backend. +To do so, simply import (or require) your tensorflow library, and call setBackend like so, + +import * as tf from '@tensorflow/tfjs' +import * as sk from 'scikitjs' +sk.setBackend(tf) + +That will let scikit know you wish to use a tensorflow library to perform your calculations. +============================ + `) + } + return tf +} diff --git a/src/tree/DecisionTree.test.ts b/src/tree/DecisionTree.test.ts index 7e1a5207..5fc6a743 100644 --- a/src/tree/DecisionTree.test.ts +++ b/src/tree/DecisionTree.test.ts @@ -1,6 +1,12 @@ -import { DecisionTreeClassifier, DecisionTreeRegressor } from './DecisionTree' +import { + DecisionTreeClassifier, + DecisionTreeRegressor, + setBackend +} from '../index' import { dataUrls } from '../datasets/datasets' import * as dfd from 'danfojs-node' +import * as tf from '@tensorflow/tfjs-node' +setBackend(tf) describe('DecisionTree', function () { it('Use the DecisionTree (toy)', async function () { diff --git a/src/types.ts b/src/types.ts index e7a74658..048e915c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,7 +13,19 @@ * ========================================================================== */ -import { tf } from './shared/globals' +import { + Scalar, + Tensor1D, + Tensor2D, + TensorLike, + Tensor, + RecursiveArray +} from '@tensorflow/tfjs-core/dist/index.d' + +import { + ModelCompileArgs, + ModelFitArgs +} from '@tensorflow/tfjs-layers/dist/index.d' /////////////////////////Danfo types export interface NDframeInterface { @@ -298,11 +310,21 @@ export interface DataFrameInterface extends NDframeInterface { //////////////////////////////////// // The Types that Scikit uses +export { + Tensor1D, + Tensor2D, + TensorLike, + Tensor, + ModelCompileArgs, + ModelFitArgs, + RecursiveArray, + Scalar +} export type TypedArray = Float32Array | Int32Array | Uint8Array export type ScikitLike1D = TypedArray | number[] | boolean[] | string[] export type ScikitLike2D = TypedArray[] | number[][] | boolean[][] | string[][] -export type Scikit1D = ScikitLike1D | tf.Tensor1D | SeriesInterface -export type Scikit2D = ScikitLike2D | tf.Tensor2D | DataFrameInterface +export type Scikit1D = ScikitLike1D | Tensor1D | SeriesInterface +export type Scikit2D = ScikitLike2D | Tensor2D | DataFrameInterface export type ScikitVecOrMatrix = Scikit1D | Scikit2D export type OptimizerTypes = | 'sgd' @@ -342,6 +364,6 @@ export type int = number export interface Transformer { fit(X: Scikit2D, y?: Scikit1D): any - transform(X: Scikit2D, y?: Scikit1D): tf.Tensor2D - fitTransform(X: Scikit2D, y?: Scikit1D): tf.Tensor2D + transform(X: Scikit2D, y?: Scikit1D): Tensor2D + fitTransform(X: Scikit2D, y?: Scikit1D): Tensor2D } diff --git a/src/typesUtils.ts b/src/typesUtils.ts index b18e7ac6..7cf5059a 100644 --- a/src/typesUtils.ts +++ b/src/typesUtils.ts @@ -20,9 +20,10 @@ import { ScikitLike1D, ScikitLike2D, ScikitVecOrMatrix, - SeriesInterface + SeriesInterface, + Tensor } from './types' - +import { getBackend } from './tf-singleton' import { tf } from './shared/globals' export function isString(value: unknown): value is string { @@ -136,6 +137,11 @@ export function isScikitLike2D(arr: any): arr is ScikitLike2D { return shape.length === 2 && dtype !== null } +export function isTensor(arr: any): arr is Tensor { + let tf = getBackend() + return arr instanceof tf.Tensor +} + export function isSeriesInterface(arr: any): arr is SeriesInterface { if (typeof arr !== 'object') { return false From 76509d8cc34628cf8f9e1ec2eeb076ecc332e191 Mon Sep 17 00:00:00 2001 From: Dan Crescimanno Date: Sat, 30 Apr 2022 22:34:20 -0700 Subject: [PATCH 2/6] feat: more tests moved over --- src/index.ts | 9 ++++++--- src/neighbors/KNeighborsRegressor.test.ts | 7 +++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index 31e9ff95..1957bf91 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,9 +12,7 @@ * limitations under the License. * ========================================================================== */ -export { KNeighborsRegressor } from './neighbors/KNeighborsRegressor' -export { KNeighborsClassifier } from './neighbors/KNeighborsClassifier' -export { makeRegression, makeLowRankMatrix } from './datasets/makeRegression' + export { LinearRegression, LinearRegressionParams @@ -71,6 +69,9 @@ export { VotingClassifier, VotingClassifierParams } from './ensemble/VotingClassifier' +export { KNeighborsRegressor } from './neighbors/KNeighborsRegressor' +export { KNeighborsClassifier } from './neighbors/KNeighborsClassifier' + export { LinearSVC, LinearSVCParams } from './svm/LinearSVC' export { LinearSVR, LinearSVRParams } from './svm/LinearSVR' @@ -87,4 +88,6 @@ export { } from './tree/DecisionTree' export { trainTestSplit } from './model_selection/trainTestSplit' export { KFold } from './model_selection/KFold' +export { crossValScore } from './model_selection/crossValScore' +export { makeRegression, makeLowRankMatrix } from './datasets/makeRegression' export { setBackend, getBackend } from './tf-singleton' diff --git a/src/neighbors/KNeighborsRegressor.test.ts b/src/neighbors/KNeighborsRegressor.test.ts index 3782a2b6..d32085f6 100644 --- a/src/neighbors/KNeighborsRegressor.test.ts +++ b/src/neighbors/KNeighborsRegressor.test.ts @@ -13,12 +13,11 @@ * ========================================================================== */ -import { KNeighborsRegressor, setBackend } from '../index' +import { KNeighborsRegressor, setBackend, KFold } from '../index' +import { crossValScore } from '../model_selection/crossValScore' import { KNeighborsParams } from './KNeighborsBase' import { dataUrls } from '../datasets/datasets' import { arrayEqual } from '../utils' -import { crossValScore } from '../model_selection/crossValScore' -import { KFold } from '../model_selection/KFold' import { negMeanSquaredError } from '../model_selection/scorers' import '../jestTensorMatchers' import * as dfd from 'danfojs-node' @@ -45,7 +44,7 @@ function testWithDataset( const X = Xy.slice([0, 0], [nSamples, nFeatures]) const y = Xy.slice([0, nFeatures]).reshape([nSamples]) as Tensor1D - const scores = await crossValScore( + const scores = await (crossValScore as any)( new KNeighborsRegressor(params), X, y, From 0f2736ef7abdfa47f535b396cc9cdac2ee40de47 Mon Sep 17 00:00:00 2001 From: Dan Crescimanno Date: Mon, 9 May 2022 20:09:07 -0700 Subject: [PATCH 3/6] feat: removed hard dependency on tensorflow --- package-lock.json | 374 +++++++++++++++++++++++++++++++++++++--------- package.json | 6 +- 2 files changed, 306 insertions(+), 74 deletions(-) diff --git a/package-lock.json b/package-lock.json index 131322a0..f91c0cb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,8 @@ "hasInstallScript": true, "license": "ISC", "dependencies": { - "@tensorflow/tfjs": "^3.16.0", - "@tensorflow/tfjs-node": "^3.16.0", + "@tensorflow/tfjs-core": "^3.16.0", + "@tensorflow/tfjs-layers": "^3.16.0", "base64-arraybuffer": "^1.0.2", "lodash": "^4.17.21", "mathjs": "^10.0.0", @@ -31,6 +31,8 @@ "@semantic-release/git": "9.0.0", "@semantic-release/npm": "^7.1.0", "@semantic-release/release-notes-generator": "9.0.3", + "@tensorflow/tfjs": "^3.16.0", + "@tensorflow/tfjs-node": "^3.16.0", "@types/chai": "^4.2.22", "@types/jest": "^27.4.0", "@types/lodash": "^4.14.177", @@ -2580,6 +2582,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.4.tgz", "integrity": "sha512-M669Qo4nRT7iDmQEjQYC7RU8Z6dpz9UmSbkJ1OFEja3uevCdLKh7IZZki7L1TZj02kRyl82snXFY8QqkyfowrQ==", + "dev": true, "dependencies": { "detect-libc": "^1.0.3", "https-proxy-agent": "^5.0.0", @@ -2599,6 +2602,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "dependencies": { "debug": "4" }, @@ -2610,6 +2614,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, "engines": { "node": ">=10" } @@ -2618,6 +2623,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -2629,6 +2635,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -2641,6 +2648,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, "dependencies": { "semver": "^6.0.0" }, @@ -2655,6 +2663,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, "bin": { "semver": "bin/semver.js" } @@ -2663,6 +2672,7 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -2674,6 +2684,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -2686,6 +2697,7 @@ "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "dev": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -3408,6 +3420,7 @@ "version": "3.16.0", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-3.16.0.tgz", "integrity": "sha512-RgsNaG/+krMMiKiG/uGPAjWM6KpT+z2wWQ2aLYSTTuQqQIksFJSUzhZncWAXykHCKgg64Pr14wyAT6gLV1amng==", + "dev": true, "dependencies": { "@tensorflow/tfjs-backend-cpu": "3.16.0", "@tensorflow/tfjs-backend-webgl": "3.16.0", @@ -3429,6 +3442,7 @@ "version": "3.16.0", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.16.0.tgz", "integrity": "sha512-8hpk/FSbx0TGV58E3qIOqNYrNXYGAgZwap3i/7A+rDuKZYtYb+EX9a+aEFBomeMSetp4xqaEYBuhOOImw4/CaA==", + "dev": true, "dependencies": { "@types/seedrandom": "2.4.27", "seedrandom": "2.4.3" @@ -3443,12 +3457,14 @@ "node_modules/@tensorflow/tfjs-backend-cpu/node_modules/seedrandom": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz", - "integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw=" + "integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw=", + "dev": true }, "node_modules/@tensorflow/tfjs-backend-webgl": { "version": "3.16.0", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.16.0.tgz", "integrity": "sha512-PrZ4//pbsP5DCz25huC3YYG6bq4+KotepPrt81pA6zCVE401qPe5CvzG/5vq0/GjVqB8uNtR1BdRL0Yonu+Urw==", + "dev": true, "dependencies": { "@tensorflow/tfjs-backend-cpu": "3.16.0", "@types/offscreencanvas": "~2019.3.0", @@ -3467,12 +3483,14 @@ "node_modules/@tensorflow/tfjs-backend-webgl/node_modules/seedrandom": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz", - "integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw=" + "integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw=", + "dev": true }, "node_modules/@tensorflow/tfjs-converter": { "version": "3.16.0", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-3.16.0.tgz", "integrity": "sha512-qPzI0BvPa//YTyk704RhlshMjM++FWaery1ns/lhGJBXD50HQazUpjP+bzJ4OIOmPSKB85BOQuzZo58JPpZSug==", + "dev": true, "peerDependencies": { "@tensorflow/tfjs-core": "3.16.0" } @@ -3511,6 +3529,7 @@ "version": "3.16.0", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-node/-/tfjs-node-3.16.0.tgz", "integrity": "sha512-v1hS5F5gnhit7haDer75qnE8OnrRkhpcYktRKJkhIvvRc3XviuZMsKcLaqUp+lSOhb438cgJdzbENrB445FSoA==", + "dev": true, "hasInstallScript": true, "dependencies": { "@mapbox/node-pre-gyp": "1.0.4", @@ -3528,6 +3547,7 @@ }, "node_modules/@tensorflow/tfjs-node/node_modules/rimraf": { "version": "2.7.1", + "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -3540,6 +3560,7 @@ "version": "3.16.0", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-3.16.0.tgz", "integrity": "sha512-VuHG0b1obO5vQsnl4atDYxroMmnKqThYnXiHccr+KNkNcZbo4MgDxeRczTsRqIDTnA0cMYn3WQjgYaYX3NxftA==", + "dev": true, "dependencies": { "@types/node-fetch": "^2.1.2", "node-fetch": "~2.6.1" @@ -3553,6 +3574,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -3561,12 +3583,14 @@ "version": "2.4.4", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.4.tgz", "integrity": "sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==", + "dev": true, "peer": true }, "node_modules/@tensorflow/tfjs/node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -3774,12 +3798,14 @@ }, "node_modules/@types/node": { "version": "16.11.7", + "dev": true, "license": "MIT" }, "node_modules/@types/node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz", "integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==", + "dev": true, "dependencies": { "@types/node": "*", "form-data": "^3.0.0" @@ -3789,6 +3815,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3853,7 +3880,8 @@ "node_modules/@types/webgl2": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.6.tgz", - "integrity": "sha512-50GQhDVTq/herLMiqSQkdtRu+d5q/cWHn4VvKJtrj4DJAjo1MNkWYa2MA41BaBO1q1HgsUjuQvEOk0QHvlnAaQ==" + "integrity": "sha512-50GQhDVTq/herLMiqSQkdtRu+d5q/cWHn4VvKJtrj4DJAjo1MNkWYa2MA41BaBO1q1HgsUjuQvEOk0QHvlnAaQ==", + "dev": true }, "node_modules/@types/yargs": { "version": "16.0.4", @@ -4215,7 +4243,8 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true }, "node_modules/accepts": { "version": "1.3.8", @@ -4317,12 +4346,14 @@ "version": "0.5.9", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", "integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==", + "dev": true, "engines": { "node": ">=6.0" } }, "node_modules/agent-base": { "version": "4.3.0", + "dev": true, "license": "MIT", "dependencies": { "es6-promisify": "^5.0.0" @@ -4404,6 +4435,7 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4411,6 +4443,7 @@ }, "node_modules/ansi-styles": { "version": "4.3.0", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4443,12 +4476,14 @@ "node_modules/aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true }, "node_modules/are-we-there-yet": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "dev": true, "dependencies": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -4458,6 +4493,7 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4471,12 +4507,14 @@ "node_modules/are-we-there-yet/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "node_modules/are-we-there-yet/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -4544,6 +4582,7 @@ }, "node_modules/asynckit": { "version": "0.4.0", + "dev": true, "license": "MIT" }, "node_modules/at-least-node": { @@ -4756,6 +4795,7 @@ }, "node_modules/balanced-match": { "version": "1.0.2", + "dev": true, "license": "MIT" }, "node_modules/base64-arraybuffer": { @@ -4864,6 +4904,7 @@ }, "node_modules/brace-expansion": { "version": "1.1.11", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -5051,6 +5092,7 @@ }, "node_modules/chalk": { "version": "4.1.2", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -5107,7 +5149,8 @@ "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true }, "node_modules/chrome-trace-event": { "version": "1.0.3", @@ -5157,6 +5200,7 @@ }, "node_modules/cliui": { "version": "7.0.4", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -5192,6 +5236,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -5214,6 +5259,7 @@ }, "node_modules/color-convert": { "version": "2.0.1", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -5224,6 +5270,7 @@ }, "node_modules/color-name": { "version": "1.1.4", + "dev": true, "license": "MIT" }, "node_modules/colors": { @@ -5238,6 +5285,7 @@ }, "node_modules/combined-stream": { "version": "1.0.8", + "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -5285,6 +5333,7 @@ }, "node_modules/concat-map": { "version": "0.0.1", + "dev": true, "license": "MIT" }, "node_modules/connect": { @@ -5320,7 +5369,8 @@ "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true }, "node_modules/content-type": { "version": "1.0.4", @@ -5447,6 +5497,7 @@ "version": "3.20.2", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.2.tgz", "integrity": "sha512-nuqhq11DcOAbFBV4zCbKeGbKQsUDRqTX0oqx7AttUBuqe3h20ixsE039QHelbL6P4h+9kytVqyEtyZ6gsiwEYw==", + "dev": true, "hasInstallScript": true, "funding": { "type": "opencollective", @@ -5476,6 +5527,7 @@ }, "node_modules/core-util-is": { "version": "1.0.2", + "dev": true, "license": "MIT" }, "node_modules/cors": { @@ -5861,6 +5913,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -5994,6 +6047,7 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -6002,7 +6056,8 @@ "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true }, "node_modules/depd": { "version": "2.0.0", @@ -6041,6 +6096,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true, "bin": { "detect-libc": "bin/detect-libc.js" }, @@ -6221,6 +6277,7 @@ }, "node_modules/emoji-regex": { "version": "8.0.0", + "dev": true, "license": "MIT" }, "node_modules/encodeurl": { @@ -6360,10 +6417,12 @@ }, "node_modules/es6-promise": { "version": "4.2.8", + "dev": true, "license": "MIT" }, "node_modules/es6-promisify": { "version": "5.0.0", + "dev": true, "license": "MIT", "dependencies": { "es6-promise": "^4.0.3" @@ -6726,6 +6785,7 @@ }, "node_modules/escalade": { "version": "3.1.1", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -7486,6 +7546,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dev": true, "dependencies": { "minipass": "^2.6.0" } @@ -7497,6 +7558,7 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", + "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -7525,6 +7587,7 @@ "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, "dependencies": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -7540,6 +7603,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -7548,6 +7612,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, "dependencies": { "number-is-nan": "^1.0.0" }, @@ -7559,6 +7624,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, "dependencies": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -7572,6 +7638,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "dependencies": { "ansi-regex": "^2.0.0" }, @@ -7589,6 +7656,7 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -7727,6 +7795,7 @@ }, "node_modules/glob": { "version": "7.2.0", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -7861,6 +7930,7 @@ }, "node_modules/google-protobuf": { "version": "3.19.1", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/graceful-fs": { @@ -7940,6 +8010,7 @@ }, "node_modules/has-flag": { "version": "4.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7959,7 +8030,8 @@ "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true }, "node_modules/homedir-polyfill": { "version": "1.0.3", @@ -8091,6 +8163,7 @@ }, "node_modules/https-proxy-agent": { "version": "2.2.4", + "dev": true, "license": "MIT", "dependencies": { "agent-base": "^4.3.0", @@ -8102,6 +8175,7 @@ }, "node_modules/https-proxy-agent/node_modules/debug": { "version": "3.2.7", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.1" @@ -8289,6 +8363,7 @@ }, "node_modules/inflight": { "version": "1.0.6", + "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -8297,6 +8372,7 @@ }, "node_modules/inherits": { "version": "2.0.4", + "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -8357,6 +8433,7 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8479,6 +8556,7 @@ }, "node_modules/isarray": { "version": "1.0.0", + "dev": true, "license": "MIT" }, "node_modules/isbinaryfile": { @@ -10080,6 +10158,7 @@ }, "node_modules/lru-cache": { "version": "6.0.0", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -10295,6 +10374,7 @@ }, "node_modules/mime-db": { "version": "1.51.0", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -10302,6 +10382,7 @@ }, "node_modules/mime-types": { "version": "2.1.34", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.51.0" @@ -10328,6 +10409,7 @@ }, "node_modules/minimatch": { "version": "3.0.4", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -10339,7 +10421,8 @@ "node_modules/minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, "node_modules/minimist-options": { "version": "4.1.0", @@ -10358,6 +10441,7 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, "dependencies": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -10366,18 +10450,21 @@ "node_modules/minipass/node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true }, "node_modules/minizlib": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dev": true, "dependencies": { "minipass": "^2.9.0" } }, "node_modules/mkdirp": { "version": "1.0.4", + "dev": true, "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -10397,6 +10484,7 @@ }, "node_modules/ms": { "version": "2.1.2", + "dev": true, "license": "MIT" }, "node_modules/mylas": { @@ -10481,6 +10569,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, "dependencies": { "abbrev": "1" }, @@ -13448,6 +13537,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, "dependencies": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -13459,6 +13549,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -13479,6 +13570,7 @@ }, "node_modules/object-assign": { "version": "4.1.1", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13532,6 +13624,7 @@ }, "node_modules/once": { "version": "1.4.0", + "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -13754,6 +13847,7 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14029,10 +14123,12 @@ }, "node_modules/process-nextick-args": { "version": "2.0.1", + "dev": true, "license": "MIT" }, "node_modules/progress": { "version": "2.0.3", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -14486,6 +14582,7 @@ }, "node_modules/require-directory": { "version": "2.1.1", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14615,6 +14712,7 @@ }, "node_modules/rimraf": { "version": "3.0.2", + "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -14650,6 +14748,7 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", + "dev": true, "funding": [ { "type": "github", @@ -14805,6 +14904,7 @@ }, "node_modules/semver": { "version": "7.3.5", + "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" @@ -14861,7 +14961,8 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, "node_modules/setprototypeof": { "version": "1.2.0", @@ -14925,6 +15026,7 @@ }, "node_modules/signal-exit": { "version": "3.0.6", + "dev": true, "license": "ISC" }, "node_modules/signale": { @@ -15180,6 +15282,7 @@ }, "node_modules/sprintf-js": { "version": "1.0.3", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/ssf": { @@ -15339,6 +15442,7 @@ }, "node_modules/string-width": { "version": "4.2.3", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -15351,6 +15455,7 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -15400,6 +15505,7 @@ }, "node_modules/supports-color": { "version": "7.2.0", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -15479,6 +15585,7 @@ "version": "4.4.19", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", + "dev": true, "dependencies": { "chownr": "^1.1.4", "fs-minipass": "^1.2.7", @@ -15496,6 +15603,7 @@ "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, "dependencies": { "minimist": "^1.2.5" }, @@ -15506,7 +15614,8 @@ "node_modules/tar/node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true }, "node_modules/temp-dir": { "version": "2.0.0", @@ -16143,6 +16252,7 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", + "dev": true, "license": "MIT" }, "node_modules/utils-merge": { @@ -16405,6 +16515,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -16447,6 +16558,7 @@ }, "node_modules/wrap-ansi": { "version": "7.0.0", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -16462,6 +16574,7 @@ }, "node_modules/wrappy": { "version": "1.0.2", + "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { @@ -16540,6 +16653,7 @@ }, "node_modules/y18n": { "version": "5.0.8", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -16547,6 +16661,7 @@ }, "node_modules/yallist": { "version": "4.0.0", + "dev": true, "license": "ISC" }, "node_modules/yaml": { @@ -16576,6 +16691,7 @@ }, "node_modules/yargs-parser": { "version": "20.2.9", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -18286,6 +18402,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.4.tgz", "integrity": "sha512-M669Qo4nRT7iDmQEjQYC7RU8Z6dpz9UmSbkJ1OFEja3uevCdLKh7IZZki7L1TZj02kRyl82snXFY8QqkyfowrQ==", + "dev": true, "requires": { "detect-libc": "^1.0.3", "https-proxy-agent": "^5.0.0", @@ -18302,6 +18419,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "requires": { "debug": "4" } @@ -18309,12 +18427,14 @@ "chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true }, "fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, "requires": { "minipass": "^3.0.0" } @@ -18323,6 +18443,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, "requires": { "agent-base": "6", "debug": "4" @@ -18332,6 +18453,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, "requires": { "semver": "^6.0.0" }, @@ -18339,7 +18461,8 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, @@ -18347,6 +18470,7 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", + "dev": true, "requires": { "yallist": "^4.0.0" } @@ -18355,6 +18479,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, "requires": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -18364,6 +18489,7 @@ "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "dev": true, "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -18878,6 +19004,7 @@ "version": "3.16.0", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-3.16.0.tgz", "integrity": "sha512-RgsNaG/+krMMiKiG/uGPAjWM6KpT+z2wWQ2aLYSTTuQqQIksFJSUzhZncWAXykHCKgg64Pr14wyAT6gLV1amng==", + "dev": true, "requires": { "@tensorflow/tfjs-backend-cpu": "3.16.0", "@tensorflow/tfjs-backend-webgl": "3.16.0", @@ -18896,6 +19023,7 @@ "version": "3.16.0", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-3.16.0.tgz", "integrity": "sha512-VuHG0b1obO5vQsnl4atDYxroMmnKqThYnXiHccr+KNkNcZbo4MgDxeRczTsRqIDTnA0cMYn3WQjgYaYX3NxftA==", + "dev": true, "requires": { "@types/node-fetch": "^2.1.2", "node-fetch": "~2.6.1" @@ -18905,6 +19033,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -18913,12 +19042,14 @@ "version": "2.4.4", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.4.tgz", "integrity": "sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==", + "dev": true, "peer": true }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -18935,6 +19066,7 @@ "version": "3.16.0", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.16.0.tgz", "integrity": "sha512-8hpk/FSbx0TGV58E3qIOqNYrNXYGAgZwap3i/7A+rDuKZYtYb+EX9a+aEFBomeMSetp4xqaEYBuhOOImw4/CaA==", + "dev": true, "requires": { "@types/seedrandom": "2.4.27", "seedrandom": "2.4.3" @@ -18943,7 +19075,8 @@ "seedrandom": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz", - "integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw=" + "integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw=", + "dev": true } } }, @@ -18951,6 +19084,7 @@ "version": "3.16.0", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.16.0.tgz", "integrity": "sha512-PrZ4//pbsP5DCz25huC3YYG6bq4+KotepPrt81pA6zCVE401qPe5CvzG/5vq0/GjVqB8uNtR1BdRL0Yonu+Urw==", + "dev": true, "requires": { "@tensorflow/tfjs-backend-cpu": "3.16.0", "@types/offscreencanvas": "~2019.3.0", @@ -18963,7 +19097,8 @@ "seedrandom": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz", - "integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw=" + "integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw=", + "dev": true } } }, @@ -18971,6 +19106,7 @@ "version": "3.16.0", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-3.16.0.tgz", "integrity": "sha512-qPzI0BvPa//YTyk704RhlshMjM++FWaery1ns/lhGJBXD50HQazUpjP+bzJ4OIOmPSKB85BOQuzZo58JPpZSug==", + "dev": true, "requires": {} }, "@tensorflow/tfjs-core": { @@ -19004,6 +19140,7 @@ "version": "3.16.0", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-node/-/tfjs-node-3.16.0.tgz", "integrity": "sha512-v1hS5F5gnhit7haDer75qnE8OnrRkhpcYktRKJkhIvvRc3XviuZMsKcLaqUp+lSOhb438cgJdzbENrB445FSoA==", + "dev": true, "requires": { "@mapbox/node-pre-gyp": "1.0.4", "@tensorflow/tfjs": "3.16.0", @@ -19017,6 +19154,7 @@ "dependencies": { "rimraf": { "version": "2.7.1", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -19204,12 +19342,14 @@ "dev": true }, "@types/node": { - "version": "16.11.7" + "version": "16.11.7", + "dev": true }, "@types/node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz", "integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==", + "dev": true, "requires": { "@types/node": "*", "form-data": "^3.0.0" @@ -19219,6 +19359,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -19279,7 +19420,8 @@ "@types/webgl2": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.6.tgz", - "integrity": "sha512-50GQhDVTq/herLMiqSQkdtRu+d5q/cWHn4VvKJtrj4DJAjo1MNkWYa2MA41BaBO1q1HgsUjuQvEOk0QHvlnAaQ==" + "integrity": "sha512-50GQhDVTq/herLMiqSQkdtRu+d5q/cWHn4VvKJtrj4DJAjo1MNkWYa2MA41BaBO1q1HgsUjuQvEOk0QHvlnAaQ==", + "dev": true }, "@types/yargs": { "version": "16.0.4", @@ -19552,7 +19694,8 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true }, "accepts": { "version": "1.3.8", @@ -19621,10 +19764,12 @@ "adm-zip": { "version": "0.5.9", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", - "integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==" + "integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==", + "dev": true }, "agent-base": { "version": "4.3.0", + "dev": true, "requires": { "es6-promisify": "^5.0.0" } @@ -19676,10 +19821,12 @@ } }, "ansi-regex": { - "version": "5.0.1" + "version": "5.0.1", + "dev": true }, "ansi-styles": { "version": "4.3.0", + "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -19701,12 +19848,14 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true }, "are-we-there-yet": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "dev": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -19716,6 +19865,7 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -19729,12 +19879,14 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -19783,7 +19935,8 @@ "dev": true }, "asynckit": { - "version": "0.4.0" + "version": "0.4.0", + "dev": true }, "at-least-node": { "version": "1.0.0", @@ -19945,7 +20098,8 @@ } }, "balanced-match": { - "version": "1.0.2" + "version": "1.0.2", + "dev": true }, "base64-arraybuffer": { "version": "1.0.2", @@ -20033,6 +20187,7 @@ }, "brace-expansion": { "version": "1.1.11", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -20161,6 +20316,7 @@ }, "chalk": { "version": "4.1.2", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -20199,7 +20355,8 @@ "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true }, "chrome-trace-event": { "version": "1.0.3", @@ -20237,6 +20394,7 @@ }, "cliui": { "version": "7.0.4", + "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -20262,7 +20420,8 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true }, "codepage": { "version": "1.15.0", @@ -20279,12 +20438,14 @@ }, "color-convert": { "version": "2.0.1", + "dev": true, "requires": { "color-name": "~1.1.4" } }, "color-name": { - "version": "1.1.4" + "version": "1.1.4", + "dev": true }, "colors": { "version": "1.4.0", @@ -20295,6 +20456,7 @@ }, "combined-stream": { "version": "1.0.8", + "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -20327,7 +20489,8 @@ "dev": true }, "concat-map": { - "version": "0.0.1" + "version": "0.0.1", + "dev": true }, "connect": { "version": "3.7.0", @@ -20361,7 +20524,8 @@ "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true }, "content-type": { "version": "1.0.4", @@ -20456,7 +20620,8 @@ "core-js": { "version": "3.20.2", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.2.tgz", - "integrity": "sha512-nuqhq11DcOAbFBV4zCbKeGbKQsUDRqTX0oqx7AttUBuqe3h20ixsE039QHelbL6P4h+9kytVqyEtyZ6gsiwEYw==" + "integrity": "sha512-nuqhq11DcOAbFBV4zCbKeGbKQsUDRqTX0oqx7AttUBuqe3h20ixsE039QHelbL6P4h+9kytVqyEtyZ6gsiwEYw==", + "dev": true }, "core-js-compat": { "version": "3.20.1", @@ -20473,7 +20638,8 @@ } }, "core-util-is": { - "version": "1.0.2" + "version": "1.0.2", + "dev": true }, "cors": { "version": "2.8.5", @@ -20776,6 +20942,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "requires": { "ms": "2.1.2" } @@ -20864,12 +21031,14 @@ } }, "delayed-stream": { - "version": "1.0.0" + "version": "1.0.0", + "dev": true }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true }, "depd": { "version": "2.0.0", @@ -20896,7 +21065,8 @@ "detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true }, "detect-newline": { "version": "3.1.0", @@ -21038,7 +21208,8 @@ "peer": true }, "emoji-regex": { - "version": "8.0.0" + "version": "8.0.0", + "dev": true }, "encodeurl": { "version": "1.0.2", @@ -21143,10 +21314,12 @@ "dev": true }, "es6-promise": { - "version": "4.2.8" + "version": "4.2.8", + "dev": true }, "es6-promisify": { "version": "5.0.0", + "dev": true, "requires": { "es6-promise": "^4.0.3" } @@ -21320,7 +21493,8 @@ "optional": true }, "escalade": { - "version": "3.1.1" + "version": "3.1.1", + "dev": true }, "escape-html": { "version": "1.0.3", @@ -21850,6 +22024,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dev": true, "requires": { "minipass": "^2.6.0" } @@ -21859,7 +22034,8 @@ "dev": true }, "fs.realpath": { - "version": "1.0.0" + "version": "1.0.0", + "dev": true }, "fsevents": { "version": "2.3.2", @@ -21878,6 +22054,7 @@ "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -21892,12 +22069,14 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -21906,6 +22085,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -21916,6 +22096,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -21927,7 +22108,8 @@ "dev": true }, "get-caller-file": { - "version": "2.0.5" + "version": "2.0.5", + "dev": true }, "get-intrinsic": { "version": "1.1.1", @@ -22038,6 +22220,7 @@ }, "glob": { "version": "7.2.0", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -22126,7 +22309,8 @@ } }, "google-protobuf": { - "version": "3.19.1" + "version": "3.19.1", + "dev": true }, "graceful-fs": { "version": "4.2.10", @@ -22179,7 +22363,8 @@ } }, "has-flag": { - "version": "4.0.0" + "version": "4.0.0", + "dev": true }, "has-symbols": { "version": "1.0.2", @@ -22188,7 +22373,8 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true }, "homedir-polyfill": { "version": "1.0.3", @@ -22289,6 +22475,7 @@ }, "https-proxy-agent": { "version": "2.2.4", + "dev": true, "requires": { "agent-base": "^4.3.0", "debug": "^3.1.0" @@ -22296,6 +22483,7 @@ "dependencies": { "debug": { "version": "3.2.7", + "dev": true, "requires": { "ms": "^2.1.1" } @@ -22422,13 +22610,15 @@ }, "inflight": { "version": "1.0.6", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" } }, "inherits": { - "version": "2.0.4" + "version": "2.0.4", + "dev": true }, "ini": { "version": "1.3.8", @@ -22467,7 +22657,8 @@ "dev": true }, "is-fullwidth-code-point": { - "version": "3.0.0" + "version": "3.0.0", + "dev": true }, "is-generator-fn": { "version": "2.1.0", @@ -22540,7 +22731,8 @@ "dev": true }, "isarray": { - "version": "1.0.0" + "version": "1.0.0", + "dev": true }, "isbinaryfile": { "version": "4.0.10", @@ -23773,6 +23965,7 @@ }, "lru-cache": { "version": "6.0.0", + "dev": true, "requires": { "yallist": "^4.0.0" } @@ -23916,10 +24109,12 @@ "dev": true }, "mime-db": { - "version": "1.51.0" + "version": "1.51.0", + "dev": true }, "mime-types": { "version": "2.1.34", + "dev": true, "requires": { "mime-db": "1.51.0" } @@ -23934,6 +24129,7 @@ }, "minimatch": { "version": "3.0.4", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -23941,7 +24137,8 @@ "minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, "minimist-options": { "version": "4.1.0", @@ -23956,6 +24153,7 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -23964,7 +24162,8 @@ "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, @@ -23972,12 +24171,14 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dev": true, "requires": { "minipass": "^2.9.0" } }, "mkdirp": { - "version": "1.0.4" + "version": "1.0.4", + "dev": true }, "modify-values": { "version": "1.0.1", @@ -23986,7 +24187,8 @@ "dev": true }, "ms": { - "version": "2.1.2" + "version": "2.1.2", + "dev": true }, "mylas": { "version": "2.1.6", @@ -24046,6 +24248,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, "requires": { "abbrev": "1" } @@ -26169,6 +26372,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -26179,7 +26383,8 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true }, "nwsapi": { "version": "2.2.0", @@ -26192,7 +26397,8 @@ "dev": true }, "object-assign": { - "version": "4.1.1" + "version": "4.1.1", + "dev": true }, "object-inspect": { "version": "1.12.0", @@ -26225,6 +26431,7 @@ }, "once": { "version": "1.4.0", + "dev": true, "requires": { "wrappy": "1" } @@ -26373,7 +26580,8 @@ "dev": true }, "path-is-absolute": { - "version": "1.0.1" + "version": "1.0.1", + "dev": true }, "path-key": { "version": "3.1.1", @@ -26550,10 +26758,12 @@ "dev": true }, "process-nextick-args": { - "version": "2.0.1" + "version": "2.0.1", + "dev": true }, "progress": { - "version": "2.0.3" + "version": "2.0.3", + "dev": true }, "prompts": { "version": "2.4.2", @@ -26866,7 +27076,8 @@ } }, "require-directory": { - "version": "2.1.1" + "version": "2.1.1", + "dev": true }, "require-from-string": { "version": "2.0.2", @@ -26956,6 +27167,7 @@ }, "rimraf": { "version": "3.0.2", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -26968,7 +27180,8 @@ } }, "safe-buffer": { - "version": "5.2.1" + "version": "5.2.1", + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -27078,6 +27291,7 @@ }, "semver": { "version": "7.3.5", + "dev": true, "requires": { "lru-cache": "^6.0.0" } @@ -27117,7 +27331,8 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, "setprototypeof": { "version": "1.2.0", @@ -27164,7 +27379,8 @@ } }, "signal-exit": { - "version": "3.0.6" + "version": "3.0.6", + "dev": true }, "signale": { "version": "1.4.0", @@ -27363,7 +27579,8 @@ } }, "sprintf-js": { - "version": "1.0.3" + "version": "1.0.3", + "dev": true }, "ssf": { "version": "0.11.2", @@ -27496,6 +27713,7 @@ }, "string-width": { "version": "4.2.3", + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -27504,6 +27722,7 @@ }, "strip-ansi": { "version": "6.0.1", + "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -27530,6 +27749,7 @@ }, "supports-color": { "version": "7.2.0", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -27591,6 +27811,7 @@ "version": "4.4.19", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", + "dev": true, "requires": { "chownr": "^1.1.4", "fs-minipass": "^1.2.7", @@ -27605,6 +27826,7 @@ "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, "requires": { "minimist": "^1.2.5" } @@ -27612,7 +27834,8 @@ "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, @@ -28013,7 +28236,8 @@ "dev": true }, "util-deprecate": { - "version": "1.0.2" + "version": "1.0.2", + "dev": true }, "utils-merge": { "version": "1.0.1", @@ -28217,6 +28441,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, "requires": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -28249,6 +28474,7 @@ }, "wrap-ansi": { "version": "7.0.0", + "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -28256,7 +28482,8 @@ } }, "wrappy": { - "version": "1.0.2" + "version": "1.0.2", + "dev": true }, "write-file-atomic": { "version": "3.0.3", @@ -28311,10 +28538,12 @@ "dev": true }, "y18n": { - "version": "5.0.8" + "version": "5.0.8", + "dev": true }, "yallist": { - "version": "4.0.0" + "version": "4.0.0", + "dev": true }, "yaml": { "version": "1.10.2", @@ -28340,7 +28569,8 @@ } }, "yargs-parser": { - "version": "20.2.9" + "version": "20.2.9", + "dev": true }, "yn": { "version": "3.1.1", diff --git a/package.json b/package.json index 177541f3..1df11168 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,8 @@ }, "homepage": "https://github.com/javascriptdata/scikit.js#readme", "dependencies": { - "@tensorflow/tfjs": "^3.16.0", - "@tensorflow/tfjs-node": "^3.16.0", + "@tensorflow/tfjs-core": "^3.16.0", + "@tensorflow/tfjs-layers": "^3.16.0", "base64-arraybuffer": "^1.0.2", "lodash": "^4.17.21", "mathjs": "^10.0.0", @@ -71,6 +71,8 @@ "@semantic-release/git": "9.0.0", "@semantic-release/npm": "^7.1.0", "@semantic-release/release-notes-generator": "9.0.3", + "@tensorflow/tfjs": "^3.16.0", + "@tensorflow/tfjs-node": "^3.16.0", "@types/chai": "^4.2.22", "@types/jest": "^27.4.0", "@types/lodash": "^4.14.177", From d3814caff4e204a0e919977ffec3686f69e28d24 Mon Sep 17 00:00:00 2001 From: Dan Crescimanno Date: Mon, 9 May 2022 23:11:16 -0700 Subject: [PATCH 4/6] feat: removed unneeded build steps --- build/browserReplacer.js | 10 ---------- build/nodeGpuReplacer.js | 7 ------- build/nodeReplacer.js | 8 -------- package.json | 6 +++--- tsconfig.build-es5.json | 15 +-------------- tsconfig.build-esm.json | 15 +-------------- tsconfig.build-node-gpu.json | 23 ----------------------- tsconfig.build-node.json | 15 +-------------- 8 files changed, 6 insertions(+), 93 deletions(-) delete mode 100644 build/browserReplacer.js delete mode 100644 build/nodeGpuReplacer.js delete mode 100644 build/nodeReplacer.js delete mode 100644 tsconfig.build-node-gpu.json diff --git a/build/browserReplacer.js b/build/browserReplacer.js deleted file mode 100644 index e4a8865a..00000000 --- a/build/browserReplacer.js +++ /dev/null @@ -1,10 +0,0 @@ -exports.default = function exampleReplacer({ - orig - // file -}) { - if (orig.includes('/shared-node/globals')) - return orig.replace('/shared-node/globals', '/shared-esm/globals') - else if (orig.includes('/shared/globals')) - return orig.replace('/shared/globals', '/shared-esm/globals') - return orig -} diff --git a/build/nodeGpuReplacer.js b/build/nodeGpuReplacer.js deleted file mode 100644 index 86a35e92..00000000 --- a/build/nodeGpuReplacer.js +++ /dev/null @@ -1,7 +0,0 @@ -exports.default = function exampleReplacer({ - orig - // file -}) { - if (orig.includes('/shared/globals')) return orig.replace('/shared/globals', '/shared/globals-node-gpu') - return orig; -} diff --git a/build/nodeReplacer.js b/build/nodeReplacer.js deleted file mode 100644 index 1c8e15bb..00000000 --- a/build/nodeReplacer.js +++ /dev/null @@ -1,8 +0,0 @@ -exports.default = function exampleReplacer({ - orig - // file -}) { - if (orig.includes('/shared/globals')) - return orig.replace('/shared/globals', '/shared-node/globals') - return orig -} diff --git a/package.json b/package.json index 1df11168..ed7b6b19 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,9 @@ "test:ci": "node --max_old_space_size=8192 node_modules/.bin/jest src/**/*.test.ts src/*.test.ts --coverage --runInBand --ci && npm run prettier:check && npm run test:browser", "test:clean": "node_modules/.bin/jest src/**/*.test.ts src/*.test.ts --coverage && npm run prettier:check && npm run test:browser", "compile:web": "node_modules/esbuild/bin/esbuild src/index.ts --bundle --platform=browser --minify --legal-comments=none --format=esm --outfile=dist/web/index.min.js", - "compile:esm": "node_modules/.bin/tsc -p tsconfig.build-esm.json && node_modules/.bin/tsc-alias -p tsconfig.build-esm.json", - "compile:node-cjs": "node_modules/.bin/tsc -p tsconfig.build-node.json && node_modules/.bin/tsc-alias -p tsconfig.build-node.json", - "compile:es5": "node_modules/.bin/tsc -p tsconfig.build-es5.json && node_modules/.bin/tsc-alias -p tsconfig.build-es5.json", + "compile:esm": "node_modules/.bin/tsc -p tsconfig.build-esm.json", + "compile:node-cjs": "node_modules/.bin/tsc -p tsconfig.build-node.json", + "compile:es5": "node_modules/.bin/tsc -p tsconfig.build-es5.json", "test:browser": "node esbuild.config.js && node_modules/karma/bin/karma start karma.config.js", "prettier:check": "node_modules/prettier/bin-prettier.js --check src", "build": "npm run compile:esm && npm run compile:node-cjs && npm run compile:web && npm run compile:es5", diff --git a/tsconfig.build-es5.json b/tsconfig.build-es5.json index 6c6aa1bc..d4572e54 100644 --- a/tsconfig.build-es5.json +++ b/tsconfig.build-es5.json @@ -5,19 +5,6 @@ "outDir": "./dist/es5", "target": "ES5", "downlevelIteration": true, - "baseUrl": "./src", - "paths": { - "shared/*": ["shared/*"] - } - }, - "tsc-alias": { - "verbose": false, - "resolveFullPaths": true, - "replacers": { - "exampleReplacer": { - "enabled": true, - "file": "./build/browserReplacer.js" - } - } + "baseUrl": "./src" } } diff --git a/tsconfig.build-esm.json b/tsconfig.build-esm.json index 1ea8eec2..bc3c1635 100644 --- a/tsconfig.build-esm.json +++ b/tsconfig.build-esm.json @@ -4,19 +4,6 @@ "declarationDir": "./dist/esm", "outDir": "./dist/esm", "target": "esnext", - "baseUrl": "./src", - "paths": { - "shared/*": ["shared/*"] - } - }, - "tsc-alias": { - "verbose": false, - "resolveFullPaths": true, - "replacers": { - "exampleReplacer": { - "enabled": true, - "file": "./build/browserReplacer.js" - } - } + "baseUrl": "./src" } } diff --git a/tsconfig.build-node-gpu.json b/tsconfig.build-node-gpu.json deleted file mode 100644 index f449feaa..00000000 --- a/tsconfig.build-node-gpu.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "extends": "./tsconfig.build.json", - "compilerOptions": { - "declarationDir": "./dist/node", - "outDir": "./dist/node", - "module": "commonjs", - "target": "esnext", - "baseUrl": "./src", - "paths": { - "shared/*": ["shared-node/*"] - } - }, - "tsc-alias": { - "verbose": false, - "resolveFullPaths": true, - "replacers": { - "exampleReplacer": { - "enabled": true, - "file": "./build/nodeGpuReplacer.js" - } - } - } -} diff --git a/tsconfig.build-node.json b/tsconfig.build-node.json index 2d8151fb..ad3ca102 100644 --- a/tsconfig.build-node.json +++ b/tsconfig.build-node.json @@ -5,19 +5,6 @@ "outDir": "./dist/node", "module": "commonjs", "target": "esnext", - "baseUrl": "./src", - "paths": { - "shared/*": ["shared-node/*"] - } - }, - "tsc-alias": { - "verbose": false, - "resolveFullPaths": true, - "replacers": { - "exampleReplacer": { - "enabled": true, - "file": "./build/nodeReplacer.js" - } - } + "baseUrl": "./src" } } From dc2ec4a5caf7f2eb95e9f91a730a28af6914a144 Mon Sep 17 00:00:00 2001 From: Dan Crescimanno Date: Mon, 9 May 2022 23:24:56 -0700 Subject: [PATCH 5/6] feat: added back in logistic regression tests --- src/index.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index eb1f7291..90146e5a 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -7,8 +7,7 @@ import './ensemble/VotingClassifier.test' import './ensemble/VotingRegressor.test' import './impute/SimpleImputer.test' import './linear_model/LinearRegression.test' -/* When we figure out why we can't save / load logistic regressions */ -// import './linear_model/logisticRegression.test' +import './linear_model/LogisticRegression.test' import './metrics/metrics.test' // import './model_selection/KFold.test' import './model_selection/trainTestSplit.test' From 8506540e2f6e6e6ae7be3b74a14cc6e7c6733bfc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 18 May 2022 00:43:37 +0000 Subject: [PATCH 6/6] chore(release): 1.22.0 [skip ci] # [1.22.0](https://github.com/javascriptdata/scikit.js/compare/v1.21.0...v1.22.0) (2022-05-18) ### Features * added back in logistic regression tests ([dc2ec4a](https://github.com/javascriptdata/scikit.js/commit/dc2ec4a5caf7f2eb95e9f91a730a28af6914a144)) * first pass at removing tensorflow from bundle ([7562da2](https://github.com/javascriptdata/scikit.js/commit/7562da244513f43e8e9d4ccbfb490c80f81f2704)) * more tests moved over ([76509d8](https://github.com/javascriptdata/scikit.js/commit/76509d8cc34628cf8f9e1ec2eeb076ecc332e191)) * removed hard dependency on tensorflow ([0f2736e](https://github.com/javascriptdata/scikit.js/commit/0f2736ef7abdfa47f535b396cc9cdac2ee40de47)) * removed unneeded build steps ([d3814ca](https://github.com/javascriptdata/scikit.js/commit/d3814caff4e204a0e919977ffec3686f69e28d24)) * updated serialize / deserialize to avoid tfjs error ([1bf508d](https://github.com/javascriptdata/scikit.js/commit/1bf508dd2e4baea5867c81183fdb609adde1938a)) --- CHANGELOG.md | 12 ++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8317c9b..781ea725 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# [1.22.0](https://github.com/javascriptdata/scikit.js/compare/v1.21.0...v1.22.0) (2022-05-18) + + +### Features + +* added back in logistic regression tests ([dc2ec4a](https://github.com/javascriptdata/scikit.js/commit/dc2ec4a5caf7f2eb95e9f91a730a28af6914a144)) +* first pass at removing tensorflow from bundle ([7562da2](https://github.com/javascriptdata/scikit.js/commit/7562da244513f43e8e9d4ccbfb490c80f81f2704)) +* more tests moved over ([76509d8](https://github.com/javascriptdata/scikit.js/commit/76509d8cc34628cf8f9e1ec2eeb076ecc332e191)) +* removed hard dependency on tensorflow ([0f2736e](https://github.com/javascriptdata/scikit.js/commit/0f2736ef7abdfa47f535b396cc9cdac2ee40de47)) +* removed unneeded build steps ([d3814ca](https://github.com/javascriptdata/scikit.js/commit/d3814caff4e204a0e919977ffec3686f69e28d24)) +* updated serialize / deserialize to avoid tfjs error ([1bf508d](https://github.com/javascriptdata/scikit.js/commit/1bf508dd2e4baea5867c81183fdb609adde1938a)) + # [1.21.0](https://github.com/javascriptdata/scikit.js/compare/v1.20.0...v1.21.0) (2022-05-08) diff --git a/package-lock.json b/package-lock.json index 00cf81aa..2d1ffc2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "scikitjs", - "version": "1.21.0", + "version": "1.22.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "scikitjs", - "version": "1.21.0", + "version": "1.22.0", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index 5e8f4a7e..59bc3324 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scikitjs", - "version": "1.21.0", + "version": "1.22.0", "description": "Scikit-Learn for JS", "output": { "node": "dist/node/index.js",