UNPKG

mkdata

Version:

Make synthetic datasets

286 lines (235 loc) 7.22 kB
const Random = require('seedrandom') const defaults = { nSamples: 100, noise: 0, seed: null, X: null // Passing X will generate only a target variable } function shuffle (X, y) { const n = X.length for (let ri = n - 1; ri > 0; ri--) { const i = Math.floor(Math.random() * (ri + 1)) if (X) [X[ri], X[i]] = [X[i], X[ri]] if (y) [y[ri], y[i]] = [y[i], y[ri]] } } function initRandom (seed) { return seed ? new Random(seed) : Math.random } // Based on https://stackoverflow.com/questions/25582882/javascript-math-random-normal-distribution-gaussian-bell-curve function normal (random) { let u = 0 let v = 0 while (u === 0) u = random() while (v === 0) v = random() return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v) } function neg (X) { return X.map(row => row.map(v => - v)) } function friedman1 (opts) { const datasetDefaults = { nFeatures: 10 } const options = Object.assign({}, defaults, datasetDefaults, opts) const random = initRandom(options.seed) if (options.nFeatures < 5) throw new Error('nFeatures must be at least five') const X = [] const y = [] const f = (x) => 10 * Math.sin(Math.PI * x[0] * x[1]) + 20 * Math.pow((x[2] - 0.5), 2) + 10 * x[3] + 5 * x[4] + options.noise * random() for (let ri = 0; ri < options.nSamples; ri++) { const x = [] for (let ci = 0; ci < options.nFeatures; ci++) { x.push(random()) } X.push(x) y.push(f(x)) } return [X, y, f] } function friedman2 (opts) { const options = Object.assign({}, defaults, opts) const random = initRandom(options.seed) const X = [] const y = [] const f = (x) => Math.sqrt(Math.pow(x[0], 2) + Math.pow(x[1] * x[2] - 1 / (x[1] * x[3]), 2)) + options.noise * random() for (let ri = 0; ri < options.nSamples; ri++) { const x = [ random() * 100, random() * 520 * Math.PI + 40 * Math.PI, random(), random() * 10 + 1 ] X.push(x) y.push(f(x)) } return [X, y, f] } function friedman3 (opts) { const options = Object.assign({}, defaults, opts) const random = initRandom(options.seed) const X = [] const y = [] const f = (x) => Math.atan(x[1] * x[2] - 1 / (x[1] * x[3]) / x[0]) + options.noise * random() for (let ri = 0; ri < options.nSamples; ri++) { const x = [ random() * 100, random() * 520 * Math.PI + 40 * Math.PI, random(), random() * 10 + 1 ] X.push(x) y.push(f(x)) } return [X, y, f] } function hastie (opts) { const options = Object.assign({}, defaults, opts) const random = initRandom(options.seed) const X = [] const y = [] const f = (x) => +(x.reduce((a, v) => a + v * v, 0) > 9.34) for (let ri = 0; ri < options.nSamples; ri++) { const x = [] for (let ci = 0; ci < 10; ci++) { const n = normal(random) x.push(n) } X.push(x) y.push(f(x)) } return [X, y] } function moons (opts) { const datasetDefaults = { shuffle: true } const options = Object.assign({}, defaults, datasetDefaults, opts) let nSamplesIn let nSamplesOut if (Array.isArray(options.nSamples)) { nSamplesOut = options.nSamples[0] nSamplesIn = options.nSamples[1] } else { nSamplesOut = Math.floor(options.nSamples / 2) nSamplesIn = options.nSamples - nSamplesOut } const X = [] const y = [] const stepOut = Math.PI / nSamplesOut for (let s = 0; s < Math.PI; s += stepOut) { X.push([Math.cos(s), Math.sin(s)]) y.push(0) } const stepIn = Math.PI / nSamplesIn for (let s = 0; s < Math.PI; s += stepIn) { X.push([0.5 - Math.cos(s), 1 - Math.sin(s)]) y.push(1) } if (options.shuffle) { shuffle(X, y) } return [X, y] } // Peak Benchmark Problem (Regression) // Based on mlbench: https://cran.r-project.org/web/packages/mlbench/ function peak (opts) { const datasetDefaults = { nFeatures: 10 } const options = Object.assign({}, defaults, datasetDefaults, opts) const random = initRandom(options.seed) const X = [] const y = [] const f = (radius) => 25 * Math.exp(-0.5 * radius * radius) for (let ri = 0; ri < options.nSamples; ri++) { let x = [] for (let ci = 0; ci < options.nFeatures; ci++) { x.push(normal(random)) } const radius = Math.random() * 3 const metro = Math.sqrt(x.reduce((a, v) => a + v * v, 0)) x = x.map(v => radius * (v / metro)) X.push(x) y.push(f(radius)) } return [X, y] } // Ringnorm Benchmark Problem (Classification) // Based on mlbench: https://cran.r-project.org/web/packages/mlbench/ // Ref: Breiman, L. (1996). Bias, variance, and arcing classifiers function ringnorm (opts) { const datasetDefaults = { nFeatures: 10 } const options = Object.assign({}, defaults, datasetDefaults, opts) const random = initRandom(options.seed) const split = options.nSamples / 2 const y = Array(Math.floor(split)).fill(0).concat(Array(Math.ceil(split)).fill(1)) const a = 1 / Math.sqrt(options.nFeatures) const X = y.map(v => { if (v) { return Array(options.nFeatures).fill(0).map(_ => normal(random) * 2) } else { return Array(options.nFeatures).fill(0).map(_ => normal(random) + a) } }) return [X, y] } function oneSpiral (n, cycles=1, sd=0, seed) { // const w = Array(n).fill(0).map((_, i) => i * cycles / n) const random = initRandom(seed) const X = Array(n).fill(0).map((_, i) => { const w = i * cycles / n x1 = (2 * w + 1) * Math.cos(2 * Math.PI * w) / 3 x2 = (2 * w + 1) * Math.sin(2 * Math.PI * w) / 3 if (sd > 0) { const e = normal(random) * sd const xs = Math.cos(2 * Math.PI * w) - Math.PI * (2 * w + 1) * Math.sin(2 * Math.PI * w) const ys = Math.sin(2 * Math.PI * w) + Math.PI * (2 * w + 1) * Math.cos(2 * Math.PI * w) const nrm = Math.sqrt(xs * xs + ys * ys) x1 += e * ys / nrm x2 -= e * xs / nrm } return [x1, x2] }) return X } function spirals (opts) { const datasetDefaults = { cycles: 1 } const options = Object.assign({}, defaults, datasetDefaults, opts) const random = initRandom(options.seed) const split = options.nSamples / 2 const y = Array(Math.floor(split)).fill(0).concat(Array(Math.ceil(split)).fill(1)) const X = oneSpiral(Math.floor(split), options.cycles, options.noise, options.seed) .concat(neg(oneSpiral(Math.ceil(split), options.cycles, options.noise, options.seed))) if (options.shuffle) { shuffle(X, y) } return [X, y] } // Swissroll dataset // Based on sklearn implementation of // S. Marsland, "Machine Learning: An Algorithmic Perspective" Chapter 10, 2009 function swissroll (opts) { const options = Object.assign({}, defaults, opts) const random = initRandom(options.seed) const X = [] const t = [] for (let ri = 0; ri < options.nSamples; ri++) { const ti = 1.5 * Math.PI * (1 + 2 * random()) const x = [ ti * Math.cos(ti) + options.noise * random(), 21 * random() + options.noise * random(), ti * Math.sin(ti) + options.noise * random() ] X.push(x) t.push(ti) } return [X, t] } module.exports = { friedman1, friedman2, friedman3, hastie, moons, peak, ringnorm, spirals, swissroll }