@jsmlt/jsmlt
Version:
JavaScript Machine Learning
214 lines (166 loc) • 8.89 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.randint = randint;
exports.rand = rand;
exports.sampleFisherYates = sampleFisherYates;
exports.sample = sample;
var Arrays = _interopRequireWildcard(require("../arrays"));
var Search = _interopRequireWildcard(require("../util/search"));
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; if (obj != null) { var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
/**
* Generate a random integer between a lower bound (inclusive) and an upper bound (exclusive).
*
* @param {number} a - Lower bound (inclusive)
* @param {number} b - Upper bound (exclusive)
* @param {number|Array.<number>} [shape = null] - If null, a single element is returned. If
* integer, an array of {shape} elements is returned. If an Array, random numbers are returned in
* a shape specified by this array. n-th element corresponds to the number of elements in the
* n-th dimension.
* @return {Array.<mixed>} Array of the specified with zero in all entries
* @return {number|Array.<mixed>} Random integer in range [a,b), or a possibly nested array of
* random integers in range [a, b)
*/
function randint(a, b) {
var shape = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
if (Number.isInteger(shape)) {
// List of random integers
return _toConsumableArray(Array(shape)).map(function (x) {
return randint(a, b);
});
}
if (Array.isArray(shape) && shape.length > 0) {
if (shape.length === 1) {
// Single shape item remaining; return list of integers
return randint(a, b, shape[0]);
} // Nested list of random integers
return _toConsumableArray(Array(shape[0])).map(function () {
return randint(a, b, shape.slice(1));
});
} // Single random integer
return a + Math.floor((b - a) * Math.random());
}
/**
* Generate a random number between a lower bound (inclusive) and an upper bound (exclusive).
*
* @param {number} a - Lower bound (inclusive)
* @param {number} b - Upper bound (exclusive)
* @return {number} Random number in range [a,b)
*/
function rand(a, b) {
return a + Math.random() * (b - a);
}
/**
* Take a random sample without replacement from an array. Uses the Fisher-Yates shuffling,
* algorithm, modified to accomodate sampling.
*
* @param {Array.<mixed>} input Input array
* @param {number} number Number of elements to sample from the input array
* @return {Array.<mixed>} Array of length {number} with values sampled from the input array
*/
function sampleFisherYates(input, number) {
// Copy input array
var shuffledArray = input.slice(0); // Number of elements in the input array
var numElements = input.length;
for (var i = numElements - 1; i >= numElements - number; i -= 1) {
var index = randint(0, i + 1);
var tmp = shuffledArray[index];
shuffledArray[index] = shuffledArray[i];
shuffledArray[i] = tmp;
} // Return the sampled values
return shuffledArray.slice(numElements - number);
}
/**
* Take a random sample with or without replacement from an array with uniform weights.
*
* @param {Array.<mixed>} input - Input array
* @param {number} number - Number of elements to sample from the input array
* @param {boolean} [withReplacement=true] - Whether to sample with (set to true) or without
* replacement (false)
* @return {Array.<mixed>} Array of length {number} with values sampled from the input array
*/
function sampleUniform(input, number) {
var withReplacement = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
// If sampling without replacement, use Fisher-Yates sampling
if (!withReplacement) {
return sampleFisherYates(input, number);
} // If sampling with replacement, choose a random element each time
var indices = randint(0, input.length, number);
return indices.map(function (x) {
return input[x];
});
}
/**
* Take a random sample with or without replacement from an array. Supports using sampling weights,
* governing the probability of an item in the input array being selected.
*
* @param {Array.<mixed>} input - Input array
* @param {number} number - Number of elements to sample from the input array
* @param {boolean} [withReplacement=true] - Whether to sample with (set to true) or without
* replacement (false)
* @param {Array.<number>|string} [weights='uniform'] - Weights to use for sampling. Defaults to
* 'uniform', which means all samples have equal probability of being selected. Alternatively, you
* can pass an array of weights, containing a single weight for each element in the input array
* @return {Array.<mixed>} Array of length {number} with values sampled from the input array
*/
function sample(input, number) {
var withReplacement = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
var weights = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'uniform';
if (Array.isArray(weights)) {
if (weights.length !== input.length) {
throw new Error('Weights array length does not equal input array length.');
}
if (!withReplacement && number > weights.filter(function (x) {
return x > 0;
}).length) {
throw new Error('Invalid sampling quantity specified: sampling without replacement cannot sample more elements than the number of non-zero weights in the weights array.');
}
} else if (weights !== 'uniform') {
throw new Error('Invalid value specified for "weights".');
}
if (!withReplacement && number > input.length) {
throw new Error('Invalid sampling quantity specified: sampling without replacement cannot sample more elements than the number of input elements.');
} // Use the uniform sampling method if the user has specified uniform weights
if (weights === 'uniform') {
return sampleUniform(input, number, withReplacement);
} // Copy weights vector
var useWeights = weights.slice();
var calculateCumWeights = function calculateCumWeights(localWeights) {
return localWeights.reduce(function (r, a, i) {
return [].concat(_toConsumableArray(r), [i > 0 ? r[i - 1] + a : a]);
}, []);
};
var sampleSingle = function sampleSingle(useCumWeights) {
// Generate a random number, and find the interval in the array of cumulative weights to which
// it corresponds. We use this index as the sampled array index, which corresponds to weighted
// sampling
var randomNumber = rand(0, useCumWeights[useCumWeights.length - 1]);
return Search.binaryIntervalSearch([0].concat(_toConsumableArray(useCumWeights)), randomNumber);
};
if (withReplacement) {
// Sample with replacement, i.e., randomly sample one element n times in a row
var cumWeights = calculateCumWeights(useWeights);
return Arrays.zeros(number).map(function () {
return input[sampleSingle(cumWeights)];
});
} // Sample without replacement: randomly sample one element, remove it from the list of elements
// that can still be sampled and the weights list, and sample from the remaining elements
// Copy input
var useInput = input.slice(); // List of elements sampled
var samples = [];
while (samples.length < number) {
// Sample from remaining inputs
var _cumWeights = calculateCumWeights(useWeights);
var useSample = sampleSingle(_cumWeights);
samples.push(useInput[useSample]); // Remove sampled element from input elements and weights lists
useInput.splice(useSample, 1);
useWeights.splice(useSample, 1);
}
return samples;
}