license import * as tf from '@tensorflow/tfjs' ; import { getDisplayDecimalPlaces , getSuccinctNumberDisplay , progressBarHelper } from './callbacks' ; describe ( 'progbarLogger' , ( ) => { class FakeProgbar { readonly tickConfigs : Array < { placeholderForLossesAndMetrics : string ; } > = [ ] ; constructor ( readonly specs , readonly config ? ) { } tick ( tickConfig ) { this . tickConfigs . push ( tickConfig ) ; } } let originalStderrColumns ; beforeEach ( ( ) => { originalStderrColumns = process . stderr . columns ; process . stderr . columns = 100 ; } ) ; afterEach ( ( ) => { process . stderr . columns = originalStderrColumns ; } ) ; it ( 'Model.fit with loss, no metric, no validation, verobse = 1' , async ( ) => { const fakeProgbars = [ ] ; spyOn ( progressBarHelper , 'ProgressBar' ) . and . callFake ( ( specs , config ) => { const fakeProgbar = new FakeProgbar ( specs , config ) ; fakeProgbars . push ( fakeProgbar ) ; return fakeProgbar ; } ) ; const consoleMessages = [ ] ; spyOn ( progressBarHelper , 'log' ) . and . callFake ( ( message ) => { consoleMessages . push ( message ) ; } ) ; const model = tf . sequential ( ) ; model . add ( tf . layers . dense ( { units : 10 , inputShape : [ 8 ] , activation : 'relu' } ) ) ; model . add ( tf . layers . dense ( { units : 1 } ) ) ; model . compile ( { loss : 'meanSquaredError' , optimizer : 'sgd' } ) ; const numSamples = 14 ; const epochs = 3 ; const batchSize = 8 ; const xs = tf . randomNormal ( [ numSamples , 8 ] ) ; const ys = tf . randomNormal ( [ numSamples , 1 ] ) ; await model . fit ( xs , ys , { epochs , batchSize , verbose : 1 } ) ; expect ( fakeProgbars . length ) . toEqual ( 3 ) ; for ( const fakeProgbar of fakeProgbars ) { const tickConfigs = fakeProgbar . tickConfigs ; expect ( tickConfigs . length ) . toEqual ( 3 ) ; for ( let i = 0 ; i < 2 ; ++ i ) { expect ( Object . keys ( tickConfigs [ i ] ) ) . toEqual ( [ 'placeholderForLossesAndMetrics' ] ) ; expect ( tickConfigs [ i ] [ 'placeholderForLossesAndMetrics' ] ) . toMatch ( /^loss=.*/ ) ; } expect ( tickConfigs [ 2 ] ) . toEqual ( { placeholderForLossesAndMetrics : '' } ) ; } expect ( consoleMessages . length ) . toEqual ( 6 ) ; expect ( consoleMessages [ 0 ] ) . toEqual ( 'Epoch 1 / 3' ) ; expect ( consoleMessages [ 1 ] ) . toMatch ( /.*ms .*us\/step - loss=.*/ ) ; expect ( consoleMessages [ 2 ] ) . toEqual ( 'Epoch 2 / 3' ) ; expect ( consoleMessages [ 3 ] ) . toMatch ( /.*ms .*us\/step - loss=.*/ ) ; expect ( consoleMessages [ 4 ] ) . toEqual ( 'Epoch 3 / 3' ) ; expect ( consoleMessages [ 5 ] ) . toMatch ( /.*ms .*us\/step - loss=.*/ ) ; } ) ; it ( 'Model.fit with loss, metric and validation, verbose = 2' , async ( ) => { const fakeProgbars = [ ] ; spyOn ( progressBarHelper , 'ProgressBar' ) . and . callFake ( ( specs , config ) => { const fakeProgbar = new FakeProgbar ( specs , config ) ; fakeProgbars . push ( fakeProgbar ) ; return fakeProgbar ; } ) ; const consoleMessages = [ ] ; spyOn ( progressBarHelper , 'log' ) . and . callFake ( ( message ) => { consoleMessages . push ( message ) ; } ) ; const model = tf . sequential ( ) ; model . add ( tf . layers . dense ( { units : 10 , inputShape : [ 8 ] , activation : 'relu' } ) ) ; model . add ( tf . layers . dense ( { units : 1 } ) ) ; model . compile ( { loss : 'meanSquaredError' , optimizer : 'sgd' , metrics : [ 'acc' ] } ) ; const numSamples = 40 ; const epochs = 2 ; const batchSize = 8 ; const validationSplit = 0.15 ; const xs = tf . randomNormal ( [ numSamples , 8 ] ) ; const ys = tf . randomNormal ( [ numSamples , 1 ] ) ; await model . fit ( xs , ys , { epochs , batchSize , validationSplit , verbose : 2 } ) ; expect ( fakeProgbars . length ) . toEqual ( 2 ) ; for ( const fakeProgbar of fakeProgbars ) { const tickConfigs = fakeProgbar . tickConfigs ; expect ( tickConfigs . length ) . toEqual ( 6 ) ; for ( let i = 0 ; i < 5 ; ++ i ) { expect ( Object . keys ( tickConfigs [ i ] ) ) . toEqual ( [ 'placeholderForLossesAndMetrics' ] ) ; expect ( tickConfigs [ i ] [ 'placeholderForLossesAndMetrics' ] ) . toMatch ( /^acc=.* loss=.*/ ) ; } expect ( tickConfigs [ 5 ] ) . toEqual ( { placeholderForLossesAndMetrics : '' } ) ; } expect ( consoleMessages . length ) . toEqual ( 4 ) ; expect ( consoleMessages [ 0 ] ) . toEqual ( 'Epoch 1 / 2' ) ; expect ( consoleMessages [ 1 ] ) . toMatch ( /.*ms .*us\/step - acc=.* loss=.* val_acc=.* val_loss=.*/ ) ; expect ( consoleMessages [ 2 ] ) . toEqual ( 'Epoch 2 / 2' ) ; expect ( consoleMessages [ 3 ] ) . toMatch ( /.*ms .*us\/step - acc=.* loss=.* val_acc=.* val_loss=.*/ ) ; } ) ; it ( 'Model.fit does not create ProgbarLogger if verbose is 0' , async ( ) => { const fakeProgbars = [ ] ; spyOn ( progressBarHelper , 'ProgressBar' ) . and . callFake ( ( specs , config ) => { const fakeProgbar = new FakeProgbar ( specs , config ) ; fakeProgbars . push ( fakeProgbar ) ; return fakeProgbar ; } ) ; const consoleMessages = [ ] ; spyOn ( progressBarHelper , 'log' ) . and . callFake ( ( message ) => { consoleMessages . push ( message ) ; } ) ; const model = tf . sequential ( ) ; model . add ( tf . layers . dense ( { units : 10 , inputShape : [ 8 ] , activation : 'relu' } ) ) ; model . add ( tf . layers . dense ( { units : 1 } ) ) ; model . compile ( { loss : 'meanSquaredError' , optimizer : 'sgd' , metrics : [ 'acc' ] } ) ; const numSamples = 40 ; const epochs = 2 ; const batchSize = 8 ; const validationSplit = 0.15 ; const xs = tf . randomNormal ( [ numSamples , 8 ] ) ; const ys = tf . randomNormal ( [ numSamples , 1 ] ) ; await model . fit ( xs , ys , { epochs , batchSize , validationSplit , verbose : 0 } ) ; expect ( fakeProgbars . length ) . toEqual ( 0 ) ; } ) ; it ( 'Model.fitDataset: batchesPerEpoch specified, verbose = 1' , async ( ) => { const fakeProgbars = [ ] ; spyOn ( progressBarHelper , 'ProgressBar' ) . and . callFake ( ( specs , config ) => { const fakeProgbar = new FakeProgbar ( specs , config ) ; fakeProgbars . push ( fakeProgbar ) ; return fakeProgbar ; } ) ; const consoleMessages = [ ] ; spyOn ( progressBarHelper , 'log' ) . and . callFake ( ( message ) => { consoleMessages . push ( message ) ; } ) ; const epochs = 2 ; const xDataset = tf . data . array ( [ [ 1 , 2 ] , [ 3 , 4 ] , [ 5 , 6 ] , [ 7 , 8 ] ] ) . map ( x => tf . tensor2d ( x , [ 1 , 2 ] ) ) ; const yDataset = tf . data . array ( [ [ 1 ] , [ 2 ] , [ 3 ] , [ 4 ] ] ) . map ( y => tf . tensor2d ( y , [ 1 , 1 ] ) ) ; const dataset = tf . data . zip ( { xs : xDataset , ys : yDataset } ) . repeat ( epochs ) ; const model = tf . sequential ( ) ; model . add ( tf . layers . dense ( { units : 1 , inputShape : [ 2 ] } ) ) ; model . compile ( { loss : 'meanSquaredError' , optimizer : 'sgd' } ) ; await model . fitDataset ( dataset , { batchesPerEpoch : 4 , epochs , verbose : 1 } ) ; expect ( consoleMessages . length ) . toEqual ( 4 ) ; expect ( consoleMessages [ 0 ] ) . toEqual ( 'Epoch 1 / 2' ) ; expect ( consoleMessages [ 1 ] ) . toMatch ( /.*ms .*us\/step - loss=.*/ ) ; expect ( consoleMessages [ 2 ] ) . toEqual ( 'Epoch 2 / 2' ) ; expect ( consoleMessages [ 3 ] ) . toMatch ( /.*ms .*us\/step - loss=.*/ ) ; } ) ; it ( 'Model.fitDataset: batchesPerEpoch unavailable, verbose = 1' , async ( ) => { const fakeProgbars = [ ] ; spyOn ( progressBarHelper , 'ProgressBar' ) . and . callFake ( ( specs , config ) => { const fakeProgbar = new FakeProgbar ( specs , config ) ; fakeProgbars . push ( fakeProgbar ) ; return fakeProgbar ; } ) ; const consoleMessages = [ ] ; spyOn ( progressBarHelper , 'log' ) . and . callFake ( ( message ) => { consoleMessages . push ( message ) ; } ) ; const epochs = 2 ; const xDataset = tf . data . array ( [ [ 1 , 2 ] , [ 3 , 4 ] , [ 5 , 6 ] , [ 7 , 8 ] ] ) . map ( x => tf . tensor2d ( x , [ 1 , 2 ] ) ) ; const yDataset = tf . data . array ( [ [ 1 ] , [ 2 ] , [ 3 ] , [ 4 ] ] ) . map ( y => tf . tensor2d ( y , [ 1 , 1 ] ) ) ; const dataset = tf . data . zip ( { xs : xDataset , ys : yDataset } ) . repeat ( epochs ) ; const model = tf . sequential ( ) ; model . add ( tf . layers . dense ( { units : 1 , inputShape : [ 2 ] } ) ) ; model . compile ( { loss : 'meanSquaredError' , optimizer : 'sgd' } ) ; await model . fitDataset ( dataset , { epochs , verbose : 1 } ) ; expect ( consoleMessages . length ) . toEqual ( 4 ) ; expect ( consoleMessages [ 0 ] ) . toEqual ( 'Epoch 1 / 2' ) ; expect ( consoleMessages [ 1 ] ) . toMatch ( /.*ms .*us\/step - loss=.*/ ) ; expect ( consoleMessages [ 2 ] ) . toEqual ( 'Epoch 2 / 2' ) ; expect ( consoleMessages [ 3 ] ) . toMatch ( /.*ms .*us\/step - loss=.*/ ) ; } ) ; it ( 'Model.fitDataset: verbose = 0 leads to no logging' , async ( ) => { const fakeProgbars = [ ] ; spyOn ( progressBarHelper , 'ProgressBar' ) . and . callFake ( ( specs , config ) => { const fakeProgbar = new FakeProgbar ( specs , config ) ; fakeProgbars . push ( fakeProgbar ) ; return fakeProgbar ; } ) ; const consoleMessages = [ ] ; spyOn ( progressBarHelper , 'log' ) . and . callFake ( ( message ) => { consoleMessages . push ( message ) ; } ) ; const xDataset = tf . data . array ( [ [ 1 , 2 ] , [ 3 , 4 ] , [ 5 , 6 ] , [ 7 , 8 ] ] ) . map ( x => tf . tensor2d ( x , [ 1 , 2 ] ) ) ; const yDataset = tf . data . array ( [ [ 1 ] , [ 2 ] , [ 3 ] , [ 4 ] ] ) . map ( y => tf . tensor2d ( y , [ 1 , 1 ] ) ) ; const dataset = tf . data . zip ( { xs : xDataset , ys : yDataset } ) ; const model = tf . sequential ( ) ; model . add ( tf . layers . dense ( { units : 1 , inputShape : [ 2 ] } ) ) ; model . compile ( { loss : 'meanSquaredError' , optimizer : 'sgd' } ) ; const history = await model . fitDataset ( dataset , { epochs : 1 , verbose : 0 } ) ; expect ( history . history . loss . length ) . toEqual ( 1 ) ; expect ( consoleMessages . length ) . toEqual ( 0 ) ; } ) ; } ) ; describe ( 'getSuccinctNumberDisplay' , ( ) => { it ( 'Not finite' , ( ) => { expect ( getSuccinctNumberDisplay ( Infinity ) ) . toEqual ( 'Infinity' ) ; expect ( getSuccinctNumberDisplay ( - Infinity ) ) . toEqual ( '-Infinity' ) ; expect ( getSuccinctNumberDisplay ( NaN ) ) . toEqual ( 'NaN' ) ; } ) ; it ( 'zero' , ( ) => { expect ( getSuccinctNumberDisplay ( 0 ) ) . toEqual ( '0.00' ) ; } ) ; it ( 'Finite and positive' , ( ) => { expect ( getSuccinctNumberDisplay ( 300 ) ) . toEqual ( '300.00' ) ; expect ( getSuccinctNumberDisplay ( 30 ) ) . toEqual ( '30.00' ) ; expect ( getSuccinctNumberDisplay ( 1 ) ) . toEqual ( '1.00' ) ; expect ( getSuccinctNumberDisplay ( 1e-2 ) ) . toEqual ( '0.0100' ) ; expect ( getSuccinctNumberDisplay ( 1e-3 ) ) . toEqual ( '1.00e-3' ) ; expect ( getSuccinctNumberDisplay ( 4e-3 ) ) . toEqual ( '4.00e-3' ) ; expect ( getSuccinctNumberDisplay ( 1e-6 ) ) . toEqual ( '1.00e-6' ) ; } ) ; it ( 'Finite and negative' , ( ) => { expect ( getSuccinctNumberDisplay ( - 300 ) ) . toEqual ( '-300.00' ) ; expect ( getSuccinctNumberDisplay ( - 30 ) ) . toEqual ( '-30.00' ) ; expect ( getSuccinctNumberDisplay ( - 1 ) ) . toEqual ( '-1.00' ) ; expect ( getSuccinctNumberDisplay ( - 1e-2 ) ) . toEqual ( '-0.0100' ) ; expect ( getSuccinctNumberDisplay ( - 1e-3 ) ) . toEqual ( '-1.00e-3' ) ; expect ( getSuccinctNumberDisplay ( - 4e-3 ) ) . toEqual ( '-4.00e-3' ) ; expect ( getSuccinctNumberDisplay ( - 1e-6 ) ) . toEqual ( '-1.00e-6' ) ; } ) ; } ) ; describe ( 'getDisplayDecimalPlaces' , ( ) => { it ( 'Not finite' , ( ) => { expect ( getDisplayDecimalPlaces ( Infinity ) ) . toEqual ( 2 ) ; expect ( getDisplayDecimalPlaces ( - Infinity ) ) . toEqual ( 2 ) ; expect ( getDisplayDecimalPlaces ( NaN ) ) . toEqual ( 2 ) ; } ) ; it ( 'zero' , ( ) => { expect ( getDisplayDecimalPlaces ( 0 ) ) . toEqual ( 2 ) ; } ) ; it ( 'Finite and positive' , ( ) => { expect ( getDisplayDecimalPlaces ( 300 ) ) . toEqual ( 2 ) ; expect ( getDisplayDecimalPlaces ( 30 ) ) . toEqual ( 2 ) ; expect ( getDisplayDecimalPlaces ( 1 ) ) . toEqual ( 2 ) ; expect ( getDisplayDecimalPlaces ( 1e-2 ) ) . toEqual ( 4 ) ; expect ( getDisplayDecimalPlaces ( 1e-3 ) ) . toEqual ( 5 ) ; expect ( getDisplayDecimalPlaces ( 4e-3 ) ) . toEqual ( 5 ) ; expect ( getDisplayDecimalPlaces ( 1e-6 ) ) . toEqual ( 8 ) ; } ) ; it ( 'Finite and negative' , ( ) => { expect ( getDisplayDecimalPlaces ( - 300 ) ) . toEqual ( 2 ) ; expect ( getDisplayDecimalPlaces ( - 30 ) ) . toEqual ( 2 ) ; expect ( getDisplayDecimalPlaces ( - 1 ) ) . toEqual ( 2 ) ; expect ( getDisplayDecimalPlaces ( - 1e-2 ) ) . toEqual ( 4 ) ; expect ( getDisplayDecimalPlaces ( - 1e-3 ) ) . toEqual ( 5 ) ; expect ( getDisplayDecimalPlaces ( - 4e-3 ) ) . toEqual ( 5 ) ; expect ( getDisplayDecimalPlaces ( - 1e-6 ) ) . toEqual ( 8 ) ; } ) ; } ) ;