UNPKG

@jsmlt/jsmlt

Version:

JavaScript Machine Learning

364 lines (304 loc) 14.4 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); // External dependencies // Internal dependencies var _marchingsquares = require('marchingsquares'); var MarchingSquaresJS = _interopRequireWildcard(_marchingsquares); var _arrays = require('../arrays'); var Arrays = _interopRequireWildcard(_arrays); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * The decision boundary module calculates decision boundaries for a trained classifier on a * 2-dimensional grid of points. It works by taking a classifier, predicting the output label for * many points on a 2-D grid, and using the [Marching Squares](https://www.npmjs.com/package/marchingsquares) * algorithm to calculate the decision boundaries. * * @example <caption>Calculating the decision boundaries for a classifier</caption> * var Boundaries = new Boundaries(); * * var classIndexBoundaries = boundaries.calculateClassifierDecisionBoundaries( * classifier, // The classifier you've trained * 51, // Number of points on the x- and y-axis (so 51 x 51 = 2,601 points in total) * ); * * // Depending on the classifier used, the output will look something like this: * { * "0":[ // Decision boundaries for class 0 * [ // First (and only, as binary classification) decision boundary or class 0 * [-0.22, -0.96], // Point 1 of decision boundary * [-0.22, -1.00], // Point 2 of decision boundary * // <...23 more elements...> * [-0.24, -0.92], * [-0.26, -0.96] * ] * ], * "1":[ // Decision boundaries for class 1 * [ // First (and only, as binary classification) decision boundary or class 1 * [-0.22, -1.00], // Point 1 of decision boundary * [-0.22, -0.96], // Point 2 of decision boundary * // <...82 more elements...> * [-0.20, -1.00], * [-0.22, -1.00] * ] * ] * } */ var Boundaries = function () { /** * Constructor. Initializes boundary object properties. */ function Boundaries() { _classCallCheck(this, Boundaries); /** * Feature list of the grid points. n-by-2 array, where each row consists of the x- and * y-coordinates of a point on the grid. * * @type {Array} */ this.features = null; /** * List of classifier predictions for each grid point. The nth prediction is the prediction for * the nth point in the `features` property. n-dimensional array. * * @type {Array} */ this.predictions = null; /** * Grid of classifier predictions for each grid point. m-by-n array, where the array element at * index (j, i) contains the prediction for the grid point at x-index i and y-index j. * * @type {Array} */ this.predictionsGrid = null; } /** * Determine decision boundaries for a specific classifier. * * @param {jsmlt.Classification.Classifier} classifier - Classifier for which to generate the * decision boundaries * @param {Array.<number>|number} resolution - Number of points on the x-axis and on the y-axis. * Use integer for the same resolution on the x- and y-axis, and a 2-dimensional array to * specify resolutions per axis * @param {Array.<number>} [gridCoordinates = [-1, -1, 1, 1]] - 4-dimensional array containing, * in order, the x1, y1, x2, and y2-coordinates of the grid * @return {Object.<string, Array.<Array.<Array.<number>>>>} The returned object contains the * boundaries per level (class label). Each boundary then consists of some coordinates (forming * a path), and each coordinate is a 2-dimensional array where the first entry is the * x-coordinate and the second entry is the y-coordinate */ _createClass(Boundaries, [{ key: 'calculateClassifierDecisionBoundaries', value: function calculateClassifierDecisionBoundaries(classifier, resolution) { var gridCoordinates = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [-1, -1, 1, 1]; var resolutionX = Array.isArray(resolution) ? resolution[0] : resolution; var resolutionY = Array.isArray(resolution) ? resolution[1] : resolution; // Generate features var features = this.generateFeaturesFromLinSpaceGrid(resolutionX, resolutionY, [gridCoordinates[0], gridCoordinates[2]], [gridCoordinates[1], gridCoordinates[3]]); // Predict labels for all grid points var predictions = classifier.predict(features); var predictionsGrid = Arrays.reshape(predictions, [resolutionX, resolutionY]); // Determine decision boundaries for grid this.features = features; this.predictions = predictions; this.predictionsGrid = predictionsGrid; return this.getGridDecisionBoundaries(predictionsGrid); } /** * Obtain the features list corresponding with the grid coordinates of the last decision * boundaries calculation * * @return {Array.<Array.<number>>} Features for all data points (2-dimensional) */ }, { key: 'getFeatures', value: function getFeatures() { return this.features; } /** * Obtain the predictions list for the last decision boundaries calculation * * @return {Array.<number>} List of predicted class labels */ }, { key: 'getPredictions', value: function getPredictions() { return this.predictions; } /** * Obtain the grid of predictions for the last decision bundaries calculation * * @return {Array.<Array.<number>>} Predicted class labels for each grid point. m-by-n array for m * rows, n columns */ }, { key: 'getPredictionsGrid', value: function getPredictionsGrid() { return this.predictionsGrid; } /** * Determine the decision boundaries for a grid of class predictions * * @param {Array.<Array.<mixed>>} grid - Grid of predictions, an array of row arrays, where each * row array contains the predictions for the cells in that row. For an m x n prediction grid, * each of the m entries of `grid` should have n entries * @return {Object.<string, Array.<Array.<Array.<number>>>>} The returned object contains the * boundaries per level (class label). Each boundary then consists of some coordinates (forming * a path), and each coordinate is a 2-dimensional array where the first entry is the * x-coordinate and the second entry is the y-coordinate */ }, { key: 'getGridDecisionBoundaries', value: function getGridDecisionBoundaries(grid) { // Get unique prediction values var levels = Array.from(new Set(Arrays.flatten(grid))); // Contours per level var contours = {}; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = levels[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var level = _step.value; contours[level] = this.getGridLevelBoundaries(grid, level); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return contours; } /** * Determine the decision boundaries for a single grid level (class label) * * @param {Array.<Array.<number>>} grid - See this.getGridDecisionBoundaries@param:grid * @param {string} level - Level (class label) to calculate boundaries for * @return {Array.<Array.<Array.<number>>>} Boundaries for this level (class label). See * this.getGridDecisionBoundaries@return */ }, { key: 'getGridLevelBoundaries', value: function getGridLevelBoundaries(grid, level) { // Create 1-vs-all grid grid for this level var gridLocal = []; for (var i = 0; i < grid.length; i += 1) { gridLocal.push([]); for (var j = 0; j < grid.length; j += 1) { gridLocal[i].push(grid[i][j] === level ? -2 : -1); } } // Check boundaries to see whether padding should be applied var pad = true; for (var _i = 0; _i < grid.length; _i += 1) { if (gridLocal[_i][0] === -1 || gridLocal[_i][grid.length - 1] === -1 || gridLocal[0][_i] === -1 || gridLocal[grid.length - 1][_i] === -1) { pad = false; } } if (pad) { // Add padding to the grid gridLocal = Arrays.pad(gridLocal, [1, 1], [-1, -1]); } // Calculate contours var contours = MarchingSquaresJS.isoBands(gridLocal, -2, 0.5); // Reshape contours to fit square centered around 0. This has to be done because isoBands // assumes the x- and y-coordinates of the grid points are the array indices. The square is // roughly 2-by-2, but slightly larger to account for the outside boundaries formed because of // the "cliff" padding added earlier. var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = contours[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var contour = _step2.value; var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = contour[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var contourPoint = _step3.value; if (pad) { contourPoint[0] = (contourPoint[0] - 1) / (gridLocal.length - 3) * 2 - 1; contourPoint[1] = (contourPoint[1] - 1) / (gridLocal[0].length - 3) * 2 - 1; } else { contourPoint[0] = contourPoint[0] / (gridLocal.length - 1) * 2 - 1; contourPoint[1] = contourPoint[1] / (gridLocal[0].length - 1) * 2 - 1; } } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } return contours; } /** * Generate a list of features from a grid of points with linear spacing * * @param {number} pointsX - Number of points on the x-axis * @param {number} pointsY - Number of points on the y-axis * @param {Array.<number>} boundsX - 2-dimensional array of left and right bound on the points on * the x-axis * @param {Array.<number>} boundsY - 2-dimensional array of left and right bound on the points on * the y-axis * @return {Array.Array<number>} (pointsX * pointsY)-by-2 array, containing the coordinates * of all grid points */ }, { key: 'generateFeaturesFromLinSpaceGrid', value: function generateFeaturesFromLinSpaceGrid(pointsX, pointsY, boundsX, boundsY) { // Generate vectors with linear spacing var linspaceX = Arrays.linspace(boundsX[0], boundsX[1], pointsX); var linspaceY = Arrays.linspace(boundsY[0], boundsY[1], pointsY); // Create mesh grid with coordinates for each point in the grid var _Arrays$meshGrid = Arrays.meshGrid(linspaceX, linspaceY), _Arrays$meshGrid2 = _slicedToArray(_Arrays$meshGrid, 2), gridX = _Arrays$meshGrid2[0], gridY = _Arrays$meshGrid2[1]; // Generate corresponding vectors of coordinate components var gridXVec = Arrays.flatten(gridX); var gridYVec = Arrays.flatten(gridY); // Join coordinate components per data point, yielding the feature vector return Arrays.concatenate(1, gridXVec, gridYVec); } }]); return Boundaries; }(); exports.default = Boundaries; module.exports = exports['default'];