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", pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy