From 2bea49c2bd711687df538109a6ef70043f3d434f Mon Sep 17 00:00:00 2001 From: ishii-norimi Date: Mon, 7 Mar 2022 23:47:37 +0900 Subject: [PATCH] Add tests --- tests/gui/data/functional.test.js | 34 +++++++ tests/lib/model/diffusion_map.test.js | 2 +- .../lib/model/growing_cell_structures.test.js | 14 +++ .../model/kernel_density_estimator.test.js | 28 +++++- tests/lib/model/maxabs.test.js | 95 +++++++++++++++---- tests/lib/model/minmax.test.js | 95 ++++++++++++++++--- tests/lib/model/nns/optimizer.test.js | 30 ++++++ tests/lib/model/robust_scaler.test.js | 61 +++++++++--- tests/lib/model/standardization.test.js | 65 ++++++++++--- tests/lib/model/tsne.test.js | 15 ++- 10 files changed, 376 insertions(+), 63 deletions(-) create mode 100644 tests/gui/data/functional.test.js create mode 100644 tests/lib/model/growing_cell_structures.test.js create mode 100644 tests/lib/model/nns/optimizer.test.js diff --git a/tests/gui/data/functional.test.js b/tests/gui/data/functional.test.js new file mode 100644 index 000000000..f964c69db --- /dev/null +++ b/tests/gui/data/functional.test.js @@ -0,0 +1,34 @@ +import puppeteer from 'puppeteer' + +/** @type {puppeteer.Browser} */ +let browser +beforeAll(async () => { + browser = await puppeteer.launch({ args: ['--no-sandbox'] }) +}) + +afterAll(async () => { + await browser.close() +}) + +describe('classification', () => { + /** @type {puppeteer.Page} */ + let page + beforeEach(async () => { + page = await browser.newPage() + await page.goto(`http://${process.env.SERVER_HOST}/`) + page.on('console', message => console.log(`${message.type().substring(0, 3).toUpperCase()} ${message.text()}`)) + .on('pageerror', ({ message }) => console.log(message)) + .on('requestfailed', request => console.log(`${request.failure().errorText} ${request.url()}`)) + await page.waitForSelector('#data_menu > *') + }, 10000) + + test('initialize', async () => { + const dataSelectBox = await page.waitForSelector('#ml_selector dl:first-child dd:nth-child(2) select') + dataSelectBox.select('functional') + + const dataMenu = await page.waitForSelector('#ml_selector #data_menu') + const dimensionTextBox = await dataMenu.waitForSelector('input[name=dim]') + const dimension = await (await dimensionTextBox.getProperty('value')).jsonValue() + expect(dimension).toBe('1') + }, 10000) +}) diff --git a/tests/lib/model/diffusion_map.test.js b/tests/lib/model/diffusion_map.test.js index f9ada9a66..d1806220a 100644 --- a/tests/lib/model/diffusion_map.test.js +++ b/tests/lib/model/diffusion_map.test.js @@ -1,5 +1,5 @@ import { jest } from '@jest/globals' -jest.retryTimes(5) +jest.retryTimes(10) import Matrix from '../../../lib/util/matrix.js' import DiffusionMap from '../../../lib/model/diffusion_map.js' diff --git a/tests/lib/model/growing_cell_structures.test.js b/tests/lib/model/growing_cell_structures.test.js new file mode 100644 index 000000000..3ed9c3642 --- /dev/null +++ b/tests/lib/model/growing_cell_structures.test.js @@ -0,0 +1,14 @@ +import Matrix from '../../../lib/util/matrix.js' +import GrowingCellStructures from '../../../lib/model/growing_cell_structures.js' + +test('clustering', () => { + const model = new GrowingCellStructures() + const n = 50 + const x = Matrix.concat(Matrix.randn(n, 2, 0, 0.1), Matrix.randn(n, 2, 5, 0.1)).toArray() + + for (let i = 0; i < 100; i++) { + model.fit(x) + } + const y = model.predict(x) + expect(y).toHaveLength(x.length) +}) diff --git a/tests/lib/model/kernel_density_estimator.test.js b/tests/lib/model/kernel_density_estimator.test.js index f8ebcec9c..6b85c7dcd 100644 --- a/tests/lib/model/kernel_density_estimator.test.js +++ b/tests/lib/model/kernel_density_estimator.test.js @@ -6,8 +6,30 @@ import KernelDensityEstimator from '../../../lib/model/kernel_density_estimator. import { correlation } from '../../../lib/evaluate/regression.js' -test('density estimation', () => { - const model = new KernelDensityEstimator() +test.each([undefined, 'gaussian', 'triangular', 'epanechnikov', 'biweight', 'triweight'])( + 'density estimation %s', + kernel => { + const model = new KernelDensityEstimator(kernel) + const n = 500 + const x = Matrix.concat(Matrix.randn(n, 2, 0, 0.1), Matrix.randn(n, 2, 5, 0.1)).toArray() + + model.fit(x) + const y = model.predict(x) + expect(y).toHaveLength(x.length) + + const p = [] + for (let i = 0; i < x.length; i++) { + const p1 = Math.exp(-x[i].reduce((s, v) => s + v ** 2, 0) / (2 * 0.1)) / (2 * Math.PI * 0.1) + const p2 = Math.exp(-x[i].reduce((s, v) => s + (v - 5) ** 2, 0) / (2 * 0.1)) / (2 * Math.PI * 0.1) + p[i] = (p1 + p2) / 2 + } + const corr = correlation(y, p) + expect(corr).toBeGreaterThan(0.9) + } +) + +test.each(['rectangular'])('density estimation %s', kernel => { + const model = new KernelDensityEstimator(kernel) const n = 500 const x = Matrix.concat(Matrix.randn(n, 2, 0, 0.1), Matrix.randn(n, 2, 5, 0.1)).toArray() @@ -22,5 +44,5 @@ test('density estimation', () => { p[i] = (p1 + p2) / 2 } const corr = correlation(y, p) - expect(corr).toBeGreaterThan(0.9) + expect(corr).toBeGreaterThan(0.8) }) diff --git a/tests/lib/model/maxabs.test.js b/tests/lib/model/maxabs.test.js index 7181a2080..93c61527d 100644 --- a/tests/lib/model/maxabs.test.js +++ b/tests/lib/model/maxabs.test.js @@ -1,31 +1,90 @@ import Matrix from '../../../lib/util/matrix.js' import MaxAbsScaler from '../../../lib/model/maxabs.js' -test('fit', () => { +test('mat mat', () => { const model = new MaxAbsScaler() const x = Matrix.randn(50, 2, 1, 0.2) const xabsmax = Matrix.map(x, Math.abs).max(0).value model.fit(x.toArray()) - const y = model.predict(x.toArray()) - - const min = Array(2).fill(Infinity) - const max = Array(2).fill(-Infinity) - const absmax = Array(2).fill(0) - for (let i = 0; i < x.rows; i++) { - for (let k = 0; k < x.cols; k++) { - expect(y[i][k]).toBeCloseTo(x.at(i, k) / xabsmax[k]) - min[k] = Math.min(min[k], y[i][k]) - max[k] = Math.max(max[k], y[i][k]) - absmax[k] = Math.max(max[k], Math.abs(y[i][k])) + + const x1 = Matrix.randn(50, 2, 0, 0.2) + const y = model.predict(x1.toArray()) + + for (let i = 0; i < x1.rows; i++) { + for (let j = 0; j < x1.cols; j++) { + expect(y[i][j]).toBeCloseTo(x1.at(i, j) / xabsmax[j]) } } - for (let k = 0; k < min.length; k++) { - expect(min[k]).toBeGreaterThanOrEqual(-1) +}) + +test('mat arr', () => { + const model = new MaxAbsScaler() + const x = Matrix.randn(50, 2, 1, 0.2) + const xabsmax = Matrix.map(x, Math.abs).max(0).value + model.fit(x.toArray()) + + const x1 = Matrix.randn(50, 1, 0, 0.2) + const y = model.predict(x1.value) + + for (let i = 0; i < x1.rows; i++) { + expect(y[i]).toBeCloseTo(x1.at(i, 0) / xabsmax[0]) } - for (let k = 0; k < max.length; k++) { - expect(max[k]).toBeLessThanOrEqual(1) +}) + +test('mat 0', () => { + const model = new MaxAbsScaler() + const x = Matrix.zeros(50, 2) + model.fit(x.toArray()) + + const x1 = Matrix.randn(50, 2, 0, 0.2) + const y = model.predict(x1.toArray()) + + for (let i = 0; i < x1.rows; i++) { + for (let j = 0; j < x1.cols; j++) { + expect(y[i][j]).toBeCloseTo(x1.at(i, j)) + } } - for (let k = 0; k < absmax.length; k++) { - expect(absmax[k]).toBeCloseTo(1) +}) + +test('arr mat', () => { + const model = new MaxAbsScaler() + const x = Matrix.randn(50, 1, 1, 0.2) + const xabsmax = Matrix.map(x, Math.abs).max() + model.fit(x.value) + + const x1 = Matrix.randn(50, 2, 0, 0.2) + const y = model.predict(x1.toArray()) + + for (let i = 0; i < x1.rows; i++) { + for (let j = 0; j < x1.cols; j++) { + expect(y[i][j]).toBeCloseTo(x1.at(i, j) / xabsmax) + } + } +}) + +test('arr arr', () => { + const model = new MaxAbsScaler() + const x = Matrix.randn(50, 1, 1, 0.2) + const xabsmax = Matrix.map(x, Math.abs).max() + model.fit(x.value) + + const x1 = Matrix.randn(50, 1, 0, 0.2) + const y = model.predict(x1.value) + + for (let i = 0; i < x1.rows; i++) { + expect(y[i]).toBeCloseTo(x1.at(i, 0) / xabsmax) + } +}) + +test('arr 0', () => { + const model = new MaxAbsScaler() + const x = Matrix.zeros(50, 1, 1, 0.2) + model.fit(x.value) + + const x1 = Matrix.randn(50, 1, 0, 0.2) + const y = model.predict(x1.value) + + for (let i = 0; i < x1.rows; i++) { + expect(y[i]).toBeCloseTo(x1.at(i, 0)) } }) diff --git a/tests/lib/model/minmax.test.js b/tests/lib/model/minmax.test.js index eaa21e657..551db02b2 100644 --- a/tests/lib/model/minmax.test.js +++ b/tests/lib/model/minmax.test.js @@ -1,27 +1,96 @@ import Matrix from '../../../lib/util/matrix.js' import MinmaxNormalization from '../../../lib/model/minmax.js' -test('fit', () => { +test('mat mat', () => { const model = new MinmaxNormalization() const x = Matrix.randn(50, 2, 1, 0.2) model.fit(x.toArray()) - const y = model.predict(x.toArray()) + + const x1 = Matrix.randn(50, 2, 0, 0.2) + const y = model.predict(x1.toArray()) + + const xmin = x.min(0).value + const xmax = x.max(0).value + for (let i = 0; i < x1.rows; i++) { + for (let k = 0; k < x1.cols; k++) { + expect(y[i][k]).toBeCloseTo((x1.at(i, k) - xmin[k]) / (xmax[k] - xmin[k])) + } + } +}) + +test('mat arr', () => { + const model = new MinmaxNormalization() + const x = Matrix.randn(50, 2, 1, 0.2) + model.fit(x.toArray()) + + const x1 = Matrix.randn(50, 1, 0, 0.2) + const y = model.predict(x1.value) const xmin = x.min(0).value const xmax = x.max(0).value - const min = Array(2).fill(Infinity) - const max = Array(2).fill(-Infinity) - for (let i = 0; i < x.rows; i++) { - for (let k = 0; k < x.cols; k++) { - expect(y[i][k]).toBeCloseTo((x.at(i, k) - xmin[k]) / (xmax[k] - xmin[k])) - min[k] = Math.min(min[k], y[i][k]) - max[k] = Math.max(max[k], y[i][k]) + for (let i = 0; i < x1.rows; i++) { + expect(y[i]).toBeCloseTo((x1.at(i, 0) - xmin[0]) / (xmax[0] - xmin[0])) + } +}) + +test('mat same', () => { + const model = new MinmaxNormalization() + const r = Math.random() + const x = new Matrix(50, 2, r) + model.fit(x.toArray()) + + const x1 = Matrix.randn(50, 2, 0, 0.2) + const y = model.predict(x1.toArray()) + + for (let i = 0; i < x1.rows; i++) { + for (let k = 0; k < x1.cols; k++) { + expect(y[i][k]).toBeCloseTo(x1.at(i, k) - r) } } - for (let k = 0; k < min.length; k++) { - expect(min[k]).toBeCloseTo(0) +}) + +test('arr mat', () => { + const model = new MinmaxNormalization() + const x = Matrix.randn(50, 1, 1, 0.2) + model.fit(x.value) + + const x1 = Matrix.randn(50, 2, 0, 0.2) + const y = model.predict(x1.toArray()) + + const xmin = x.min() + const xmax = x.max() + for (let i = 0; i < x1.rows; i++) { + for (let k = 0; k < x1.cols; k++) { + expect(y[i][k]).toBeCloseTo((x1.at(i, k) - xmin) / (xmax - xmin)) + } + } +}) + +test('arr arr', () => { + const model = new MinmaxNormalization() + const x = Matrix.randn(50, 1, 1, 0.2) + model.fit(x.value) + + const x1 = Matrix.randn(50, 1, 0, 0.2) + const y = model.predict(x1.value) + + const xmin = x.min() + const xmax = x.max() + for (let i = 0; i < x1.rows; i++) { + expect(y[i]).toBeCloseTo((x1.at(i, 0) - xmin) / (xmax - xmin)) } - for (let k = 0; k < max.length; k++) { - expect(max[k]).toBeCloseTo(1) +}) + +test('arr same', () => { + const model = new MinmaxNormalization() + const r = Math.random() + const x = new Matrix(50, 1, r) + model.fit(x.value) + + const x1 = Matrix.randn(50, 1, 0, 0.2) + const y = model.predict(x1.value) + + for (let i = 0; i < x1.rows; i++) { + expect(y[i]).toBeCloseTo(x1.at(i, 0) - r) } }) diff --git a/tests/lib/model/nns/optimizer.test.js b/tests/lib/model/nns/optimizer.test.js new file mode 100644 index 000000000..f8b5382db --- /dev/null +++ b/tests/lib/model/nns/optimizer.test.js @@ -0,0 +1,30 @@ +import NeuralNetwork from '../../../../lib/model/neuralnetwork.js' +import Matrix from '../../../../lib/util/matrix.js' + +describe('nn', () => { + test.each(['sgd', 'momentum', 'rmsprop', 'adam'])('%s', optimizer => { + const net = NeuralNetwork.fromObject( + [ + { type: 'input', name: 'in' }, + { type: 'full', out_size: 5, activation: 'sigmoid' }, + { type: 'full', out_size: 3 }, + ], + 'mse', + optimizer + ) + const x = Matrix.randn(1, 10) + const t = Matrix.randn(1, 3) + + for (let i = 0; i < 1000; i++) { + const loss = net.fit(x, t, 1000, 0.01) + if (loss[0] < 1.0e-8) { + break + } + } + + const y = net.calc(x) + for (let i = 0; i < 3; i++) { + expect(y.at(0, i)).toBeCloseTo(t.at(0, i)) + } + }) +}) diff --git a/tests/lib/model/robust_scaler.test.js b/tests/lib/model/robust_scaler.test.js index 915f29275..434e905dd 100644 --- a/tests/lib/model/robust_scaler.test.js +++ b/tests/lib/model/robust_scaler.test.js @@ -1,29 +1,62 @@ import Matrix from '../../../lib/util/matrix.js' import RobustScaler from '../../../lib/model/robust_scaler.js' -test('fit', () => { +test('mat mat', () => { const model = new RobustScaler() const x = Matrix.randn(101, 2, 1, 0.2) const median = x.median(0).value const iqr = Matrix.sub(x.quantile(0.75, 0), x.quantile(0.25, 0)).value model.fit(x.toArray()) - const y = model.predict(x.toArray()) - for (let i = 0; i < x.rows; i++) { - for (let k = 0; k < x.cols; k++) { - expect(y[i][k]).toBeCloseTo((x.at(i, k) - median[k]) / iqr[k]) + + const x1 = Matrix.randn(50, 2, 0, 0.2) + const y = model.predict(x1.toArray()) + for (let i = 0; i < x1.rows; i++) { + for (let k = 0; k < x1.cols; k++) { + expect(y[i][k]).toBeCloseTo((x1.at(i, k) - median[k]) / iqr[k]) } } +}) - const q = (arr, p) => { - const np = (arr.length - 1) * p - const np_l = Math.floor(np) - const np_h = Math.ceil(np) - return arr[np_l] + (np - np_l) * (arr[np_h] - arr[np_l]) +test('mat arr', () => { + const model = new RobustScaler() + const x = Matrix.randn(101, 2, 1, 0.2) + const median = x.median(0).value + const iqr = Matrix.sub(x.quantile(0.75, 0), x.quantile(0.25, 0)).value + model.fit(x.toArray()) + + const x1 = Matrix.randn(50, 1, 0, 0.2) + const y = model.predict(x1.value) + for (let i = 0; i < x1.rows; i++) { + expect(y[i]).toBeCloseTo((x1.at(i, 0) - median[0]) / iqr[0]) } +}) + +test('arr mat', () => { + const model = new RobustScaler() + const x = Matrix.randn(101, 1, 1, 0.2) + const median = x.median() + const iqr = x.quantile(0.75) - x.quantile(0.25) + model.fit(x.value) + + const x1 = Matrix.randn(50, 2, 0, 0.2) + const y = model.predict(x1.toArray()) + for (let i = 0; i < x1.rows; i++) { + for (let k = 0; k < x1.cols; k++) { + expect(y[i][k]).toBeCloseTo((x1.at(i, k) - median) / iqr) + } + } +}) + +test('arr arr', () => { + const model = new RobustScaler() + const x = Matrix.randn(101, 1, 1, 0.2) + const median = x.median() + const iqr = x.quantile(0.75) - x.quantile(0.25) + model.fit(x.value) - for (let k = 0; k < 2; k++) { - const yk = y.map(v => v[k]) - yk.sort((a, b) => a - b) - expect(q(yk, 0.5)).toBeCloseTo(0) + const x1 = Matrix.randn(50, 1, 0, 0.2) + const y = model.predict(x1.value) + for (let i = 0; i < x1.rows; i++) { + expect(y[i]).toBeCloseTo((x1.at(i, 0) - median) / iqr) } }) diff --git a/tests/lib/model/standardization.test.js b/tests/lib/model/standardization.test.js index 70ff10ec7..1d2ee65c7 100644 --- a/tests/lib/model/standardization.test.js +++ b/tests/lib/model/standardization.test.js @@ -1,27 +1,66 @@ import Matrix from '../../../lib/util/matrix.js' import Standardization from '../../../lib/model/standardization.js' -test('fit', () => { +test('mat mat', () => { const model = new Standardization() const x = Matrix.randn(50, 2, 1, 0.2) model.fit(x.toArray()) - const y = model.predict(x.toArray()) + + const x1 = Matrix.randn(50, 2, 0, 0.2) + const y = model.predict(x1.toArray()) const mean = x.mean(0).value const std = x.std(0).value - const s = Array(2).fill(0) - const v = Array(2).fill(0) - for (let i = 0; i < x.rows; i++) { - for (let k = 0; k < x.cols; k++) { - expect(y[i][k]).toBeCloseTo((x.at(i, k) - mean[k]) / std[k], 1) - s[k] += y[i][k] - v[k] += y[i][k] ** 2 + for (let i = 0; i < x1.rows; i++) { + for (let k = 0; k < x1.cols; k++) { + expect(y[i][k]).toBeCloseTo((x1.at(i, k) - mean[k]) / std[k], 1) } } - for (let k = 0; k < s.length; k++) { - expect(s[k] / x.rows).toBeCloseTo(0) +}) + +test('mat arr', () => { + const model = new Standardization() + const x = Matrix.randn(50, 2, 1, 0.2) + model.fit(x.toArray()) + + const x1 = Matrix.randn(50, 1, 0, 0.2) + const y = model.predict(x1.value) + + const mean = x.mean(0).value + const std = x.std(0).value + for (let i = 0; i < x1.rows; i++) { + expect(y[i]).toBeCloseTo((x1.at(i, 0) - mean[0]) / std[0], 1) + } +}) + +test('arr mat', () => { + const model = new Standardization() + const x = Matrix.randn(50, 1, 1, 0.2) + model.fit(x.value) + + const x1 = Matrix.randn(50, 2, 0, 0.2) + const y = model.predict(x1.toArray()) + + const mean = x.mean() + const std = x.std() + for (let i = 0; i < x1.rows; i++) { + for (let k = 0; k < x1.cols; k++) { + expect(y[i][k]).toBeCloseTo((x1.at(i, k) - mean) / std, 1) + } } - for (let k = 0; k < v.length; k++) { - expect(v[k] / x.rows).toBeCloseTo(1) +}) + +test('arr arr', () => { + const model = new Standardization() + const x = Matrix.randn(50, 1, 1, 0.2) + model.fit(x.value) + + const x1 = Matrix.randn(50, 1, 0, 0.2) + const y = model.predict(x1.value) + + const mean = x.mean() + const std = x.std() + for (let i = 0; i < x1.rows; i++) { + expect(y[i]).toBeCloseTo((x1.at(i, 0) - mean) / std, 1) } }) diff --git a/tests/lib/model/tsne.test.js b/tests/lib/model/tsne.test.js index a2b1772f8..26c2765b7 100644 --- a/tests/lib/model/tsne.test.js +++ b/tests/lib/model/tsne.test.js @@ -2,10 +2,23 @@ import { jest } from '@jest/globals' jest.retryTimes(3) import Matrix from '../../../lib/util/matrix.js' -import { tSNE } from '../../../lib/model/tsne.js' +import { SNE, tSNE } from '../../../lib/model/tsne.js' import { coRankingMatrix } from '../../../lib/evaluate/dimensionality_reduction.js' +test('SNE', () => { + const x = Matrix.concat(Matrix.randn(20, 5, 0, 0.2), Matrix.randn(20, 5, 5, 0.2)).toArray() + const model = new SNE(x, 2) + model._perplexity = 30 + + for (let i = 0; i < 20; i++) { + model.fit() + } + const y = model.predict() + const q = coRankingMatrix(x, y, 10, 10) + expect(q).toBeGreaterThan(0.9) +}) + test('tSNE', () => { const x = Matrix.concat(Matrix.randn(20, 5, 0, 0.2), Matrix.randn(20, 5, 5, 0.2)).toArray() const model = new tSNE(x, 2)