From 75bf3cc74e5c3af825aec7ab96d7313df3642657 Mon Sep 17 00:00:00 2001 From: Nakul Krishnakumar Date: Sun, 14 Dec 2025 03:25:15 +0530 Subject: [PATCH] feat: add `ml/incr/lvq` --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: na - task: lint_package_json status: passed - task: lint_repl_help status: na - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: na - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: passed - task: lint_typescript_tests status: na - task: lint_license_headers status: passed --- --- .../@stdlib/ml/incr/lvq/lib/add_proto.js | 75 ++++ .../@stdlib/ml/incr/lvq/lib/copy_matrix.js | 75 ++++ .../@stdlib/ml/incr/lvq/lib/copy_vector.js | 44 ++ .../ml/incr/lvq/lib/find_closest_proto.js | 63 +++ .../@stdlib/ml/incr/lvq/lib/index.js | 75 ++++ .../@stdlib/ml/incr/lvq/lib/int_vector.js | 58 +++ .../@stdlib/ml/incr/lvq/lib/main.js | 375 ++++++++++++++++++ .../@stdlib/ml/incr/lvq/lib/matrix.js | 59 +++ .../@stdlib/ml/incr/lvq/lib/metrics.json | 3 + .../ml/incr/lvq/lib/squared_euclidean.js | 51 +++ .../@stdlib/ml/incr/lvq/lib/update_proto.js | 67 ++++ .../@stdlib/ml/incr/lvq/lib/validate.js | 81 ++++ .../@stdlib/ml/incr/lvq/lib/vector.js | 58 +++ .../@stdlib/ml/incr/lvq/package.json | 72 ++++ 14 files changed, 1156 insertions(+) create mode 100644 lib/node_modules/@stdlib/ml/incr/lvq/lib/add_proto.js create mode 100644 lib/node_modules/@stdlib/ml/incr/lvq/lib/copy_matrix.js create mode 100644 lib/node_modules/@stdlib/ml/incr/lvq/lib/copy_vector.js create mode 100644 lib/node_modules/@stdlib/ml/incr/lvq/lib/find_closest_proto.js create mode 100644 lib/node_modules/@stdlib/ml/incr/lvq/lib/index.js create mode 100644 lib/node_modules/@stdlib/ml/incr/lvq/lib/int_vector.js create mode 100644 lib/node_modules/@stdlib/ml/incr/lvq/lib/main.js create mode 100644 lib/node_modules/@stdlib/ml/incr/lvq/lib/matrix.js create mode 100644 lib/node_modules/@stdlib/ml/incr/lvq/lib/metrics.json create mode 100644 lib/node_modules/@stdlib/ml/incr/lvq/lib/squared_euclidean.js create mode 100644 lib/node_modules/@stdlib/ml/incr/lvq/lib/update_proto.js create mode 100644 lib/node_modules/@stdlib/ml/incr/lvq/lib/validate.js create mode 100644 lib/node_modules/@stdlib/ml/incr/lvq/lib/vector.js create mode 100644 lib/node_modules/@stdlib/ml/incr/lvq/package.json diff --git a/lib/node_modules/@stdlib/ml/incr/lvq/lib/add_proto.js b/lib/node_modules/@stdlib/ml/incr/lvq/lib/add_proto.js new file mode 100644 index 000000000000..4a2bc41416f5 --- /dev/null +++ b/lib/node_modules/@stdlib/ml/incr/lvq/lib/add_proto.js @@ -0,0 +1,75 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var dcopy = require( '@stdlib/blas/base/dcopy' ); +var copyVector = require( './copy_vector.js' ); +var createIntVector = require( './int_vector.js' ); +var createMatrix = require( './matrix.js' ); + + +// MAIN // + +/** +* Adds a new prototype with the given data point and label. +* +* @private +* @param {PositiveInteger} k - current number of prototypes +* @param {PositiveInteger} ndims - number of dimensions +* @param {ndarray} protos - matrix containing existing prototypes +* @param {ndarray} protoLabels - vector containing class labels for existing prototypes +* @param {ndarray} vec - data vector to use as new prototype +* @param {integer} label - class label for new prototype +* @returns {Object} object containing new protos and protoLabels +*/ +function addPrototype( k, ndims, protos, protoLabels, vec, label ) { + var newProtos; + var newLabels; + var vbuf; + var sv; + var ov; + var i; + + newProtos = createMatrix( k + 1, ndims, true ); + newLabels = createIntVector( k + 1, true ); + + if ( k > 0 && protos ) { + dcopy( protos.data.length, protos.data, 1, newProtos.data, 1 ); + copyVector( newLabels, protoLabels ); + } + + vbuf = vec.data; + sv = vec.strides[ 0 ]; + ov = vec.offset; + + for ( i = 0; i < ndims; i++ ) { + newProtos.data[ ( k * ndims ) + i ] = vbuf[ ov + ( i * sv ) ]; + } + + newLabels.data[ newLabels.offset + ( k * newLabels.strides[ 0 ] ) ] = label; + + return { + 'protos': newProtos, + 'protoLabels': newLabels + }; +} + +module.exports = addPrototype; diff --git a/lib/node_modules/@stdlib/ml/incr/lvq/lib/copy_matrix.js b/lib/node_modules/@stdlib/ml/incr/lvq/lib/copy_matrix.js new file mode 100644 index 000000000000..5aa78ec8d5dd --- /dev/null +++ b/lib/node_modules/@stdlib/ml/incr/lvq/lib/copy_matrix.js @@ -0,0 +1,75 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var gcopy = require( '@stdlib/blas/base/gcopy' ).ndarray; + + +// MAIN // + +/** +* Copies matrix elements to another matrix. +* +* @private +* @param {ndarray} Y - destination matrix +* @param {ndarray} X - source matrix +* @returns {ndarray} destination matrix +*/ +function copyMatrix( Y, X ) { + var xbuf; + var ybuf; + var sx1; + var sx2; + var sy1; + var sy2; + var ox; + var oy; + var M; + var N; + var i; + + M = X.shape[ 0 ]; + N = X.shape[ 1 ]; + + xbuf = X.data; + ybuf = Y.data; + + sx1 = X.strides[ 0 ]; + sx2 = X.strides[ 1 ]; + + sy1 = Y.strides[ 0 ]; + sy2 = Y.strides[ 1 ]; + + ox = X.offset; + oy = Y.offset; + + for ( i = 0; i < M; i++ ) { + gcopy( N, xbuf, sx2, ox, ybuf, sy2, oy ); + ox += sx1; + oy += sy1; + } + return Y; +} + + +// EXPORTS // + +module.exports = copyMatrix; diff --git a/lib/node_modules/@stdlib/ml/incr/lvq/lib/copy_vector.js b/lib/node_modules/@stdlib/ml/incr/lvq/lib/copy_vector.js new file mode 100644 index 000000000000..343c9b4665c1 --- /dev/null +++ b/lib/node_modules/@stdlib/ml/incr/lvq/lib/copy_vector.js @@ -0,0 +1,44 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var gcopy = require( '@stdlib/blas/base/gcopy' ).ndarray; + + +// MAIN // + +/** +* Copies vector elements to another vector. +* +* @private +* @param {ndarray} out - destination vector +* @param {ndarray} src - source vector +* @returns {ndarray} destination vector +*/ +function copyVector( out, src ) { + gcopy( src.shape[0], src.data, src.strides[0], src.offset, out.data, out.strides[0], out.offset ); // eslint-disable-line max-len + return out; +} + + +// EXPORTS // + +module.exports = copyVector; diff --git a/lib/node_modules/@stdlib/ml/incr/lvq/lib/find_closest_proto.js b/lib/node_modules/@stdlib/ml/incr/lvq/lib/find_closest_proto.js new file mode 100644 index 000000000000..32181fe941c7 --- /dev/null +++ b/lib/node_modules/@stdlib/ml/incr/lvq/lib/find_closest_proto.js @@ -0,0 +1,63 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var PINF = require( '@stdlib/constants/float64/pinf' ); + + +// MAIN // + +/** +* Finds the closest prototypes. +* +* @private +* @param {Function} dist - distance function +* @param {PositiveInteger} k - number of clusters +* @param {PositiveInteger} ndims - number of dimensions +* @param {NumericArray} P - strided array containing prototypes +* @param {PositiveInteger} sp - prototype row stride +* @param {NonNegativeInteger} op - prototype index offset +* @param {NumericArray} X - strided array containing a data point +* @param {integer} sx - data point stride +* @param {NonNegativeInteger} ox - data point index offset +* @returns {NonNegativeInteger} prototype index +*/ +function closestPrototype( dist, k, ndims, P, sp, op, X, sx, ox ) { + var mindist; + var mini; + var d; + var i; + + mindist = PINF; + mini = -1; + for (i = 0; i < k; ++i) { + d = dist( ndims, P, 1, op, X, sx, ox ); + + if (d < mindist) { + mindist = d; + mini = i; + } + op += sp; + } + return mini; +} + +module.exports = closestPrototype; diff --git a/lib/node_modules/@stdlib/ml/incr/lvq/lib/index.js b/lib/node_modules/@stdlib/ml/incr/lvq/lib/index.js new file mode 100644 index 000000000000..657762d19979 --- /dev/null +++ b/lib/node_modules/@stdlib/ml/incr/lvq/lib/index.js @@ -0,0 +1,75 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +/** +* Incrementally perform supervised classification using lvq1. +* +* @module @stdlib/ml/incr/lvq +* +* @example +* var Float64Array = require( '@stdlib/array/float64' ); +* var Int32Array = require( '@stdlib/array/int32' ); +* var ndarray = require( '@stdlib/ndarray/ctor' ); +* var incrlvq = require( '@stdlib/ml/incr/lvq' ); +* +* // Define initial prototype locations (2 prototypes, 2 dimensions): +* var buffer = new Float64Array( [ 0.0, 0.0, 1.0, 1.0 ] ); +* var prototypes = ndarray( 'float64', buffer, [ 2, 2 ], [ 2, 1 ], 0, 'row-major' ); +* +* // Define prototype labels: +* var labelBuffer = new Int32Array( [ 0, 1 ] ); +* var labels = ndarray( 'int32', labelBuffer, [ 2 ], [ 1 ], 0, 'row-major' ); +* +* // Create an LVQ accumulator: +* var accumulator = incrlvq( prototypes, labels ); +* +* var out = accumulator(); +* // returns {...} +* +* // Create a data vector: +* var vecBuffer = new Float64Array( 2 ); +* var vec = ndarray( 'float64', vecBuffer, [ 2 ], [ 1 ], 0, 'row-major' ); +* +* // Provide labeled data to the accumulator: +* vec.set( 0, 0.5 ); +* vec.set( 1, 0.5 ); +* +* out = accumulator( vec, 0 ); // data point with label 0 +* // returns {...} +* +* vec.set( 0, 1.5 ); +* vec.set( 1, 1.5 ); +* +* out = accumulator( vec, 1 ); // data point with label 1 +* // returns {...} +* +* // Retrieve the current results: +* out = accumulator(); +* // returns {...} +*/ + +// MAIN // + +var main = require( './main.js' ); + + +// EXPORTS // + +module.exports = main; diff --git a/lib/node_modules/@stdlib/ml/incr/lvq/lib/int_vector.js b/lib/node_modules/@stdlib/ml/incr/lvq/lib/int_vector.js new file mode 100644 index 000000000000..06efe7850c1b --- /dev/null +++ b/lib/node_modules/@stdlib/ml/incr/lvq/lib/int_vector.js @@ -0,0 +1,58 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var Int32Array = require( '@stdlib/array/int32' ); +var ctor = require( '@stdlib/ndarray/ctor' ); +var bctor = require( '@stdlib/ndarray/base/ctor' ); + + +// MAIN // + +/** +* Returns an integer vector. +* +* @private +* @param {PositiveInteger} N - number of elements +* @param {boolean} bool - boolean indicating whether to create a low-level ndarray +* @returns {ndarray} vector +*/ +function createIntVector( N, bool ) { + var strides; + var buffer; + var shape; + var f; + + if ( bool ) { + f = bctor; + } else { + f = ctor; + } + buffer = new Int32Array( N ); + shape = [ N ]; + strides = [ 1 ]; + return f( 'int32', buffer, shape, strides, 0, 'row-major' ); +} + + +// EXPORTS // + +module.exports = createIntVector; diff --git a/lib/node_modules/@stdlib/ml/incr/lvq/lib/main.js b/lib/node_modules/@stdlib/ml/incr/lvq/lib/main.js new file mode 100644 index 000000000000..1f1fed826179 --- /dev/null +++ b/lib/node_modules/@stdlib/ml/incr/lvq/lib/main.js @@ -0,0 +1,375 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var setReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' ); +var isMatrixLike = require( '@stdlib/assert/is-matrix-like' ); +var isVectorLike = require( '@stdlib/assert/is-vector-like' ); +var isInteger = require( '@stdlib/assert/is-integer' ).isPrimitive; +var format = require( '@stdlib/string/format' ); +var isObject = require( '@stdlib/assert/is-object' ); +var dcopy = require( '@stdlib/blas/base/dcopy' ); +var closestPrototype = require( './find_closest_proto.js' ); +var squaredEuclidean = require( './squared_euclidean.js' ); +var createIntVector = require( './int_vector.js' ); +var copyVector = require( './copy_vector.js' ); +var updatePrototype = require( './update_proto.js' ); +var createMatrix = require( './matrix.js' ); +var copyMatrix = require( './copy_matrix.js' ); +var validate = require( './validate.js' ); +var addPrototype = require( './add_proto.js' ); + + +// FUNCTIONS // + +/** +* Gets the label at a given index from a labels vector. +* +* @private +* @param {ndarray} labels - labels vector +* @param {NonNegativeInteger} idx - index +* @returns {NonNegativeInteger} label value +*/ +function getLabel( labels, idx ) { + return labels.data[ labels.offset + ( idx * labels.strides[ 0 ] ) ]; +} + +/** +* Checks if a label exists in the labels vector. +* +* @private +* @param {ndarray} labels - labels vector +* @param {NonNegativeInteger} label - label to search for +* @param {PositiveInteger} len - number of labels +* @returns {boolean} whether label exists +*/ +function hasLabel( labels, label, len ) { + var i; + for ( i = 0; i < len; i++ ) { + if ( getLabel( labels, i ) === label ) { + return true; + } + } + return false; +} + +/** +* Returns a results object. +* +* @private +* @param {PositiveInteger} k - number of prototypes +* @param {PositiveInteger} ndims - number of dimensions +* @returns {Object} results object +*/ +function createResults( k, ndims ) { + var out = {}; + out.prototypes = createMatrix( k, ndims, false ); // high-level + out.labels = createIntVector( k, false ); // high-level + return out; +} + + +// MAIN // + +/** +* Returns an accumulator function which incrementally performs supervised classification using lvq1. +* +* @param {ndarray|Options} [prototypes] - a `k x ndims` matrix containing initial prototypes +* @param {ndarray} [labels] - a vector of length `k` containing prototype labels (integers) +* @param {Options} [options] - function options +* @param {string} [options.metric="euclidean"] - distance metric +* @param {PositiveNumber} [options.learningRate=0.1] - initial learning rate +* @throws {TypeError} second argument must be a vector if first argument is a matrix +* @throws {TypeError} labels vector length must match number of prototypes +* @throws {TypeError} options argument must be an object +* @throws {TypeError} must provide valid options +* @returns {Function} accumulator function +* +* @example +* var Float64Array = require( '@stdlib/array/float64' ); +* var Int32Array = require( '@stdlib/array/int32' ); +* var ndarray = require( '@stdlib/ndarray/ctor' ); +* +* // Define initial prototype locations (2 prototypes, 2 dimensions): +* var buffer = new Float64Array( [ 0.0, 0.0, 1.0, 1.0 ] ); +* var prototypes = ndarray( 'float64', buffer, [ 2, 2 ], [ 2, 1 ], 0, 'row-major' ); +* +* // Define prototype labels: +* var labelBuffer = new Int32Array( [ 0, 1 ] ); +* var labels = ndarray( 'int32', labelBuffer, [ 2 ], [ 1 ], 0, 'row-major' ); +* +* // Create an LVQ accumulator: +* var accumulator = incrlvq( prototypes, labels ); +* +* var out = accumulator(); +* // returns {...} +* +* // Create a data vector: +* var vecBuffer = new Float64Array( 2 ); +* var vec = ndarray( 'float64', vecBuffer, [ 2 ], [ 1 ], 0, 'row-major' ); +* +* // Provide labeled data to the accumulator: +* vec.set( 0, 0.5 ); +* vec.set( 1, 0.5 ); +* +* out = accumulator( vec, 0 ); // data point with label 0 +* // returns {...} +*/ +function incrlvq( ) { + var protoLabels; + var options; + var results; + var protos; + var ndims; + var opts; + var dist; + + var FLG; + var err; + var k; + + opts = { + 'metric': 'euclidean', + 'learningRate': 0.1 + }; + + if ( isMatrixLike( arguments[ 0 ] ) ) { + if ( !isVectorLike( arguments[ 1 ] ) ) { + throw new TypeError( format( 'invalid argument. Second argument must be a vector containing prototype labels if prototypes are passed. Value: `%s`.', arguments[ 1 ] ) ); + } + + k = arguments[ 0 ].shape[ 0 ]; + ndims = arguments[ 0 ].shape[ 1 ]; + + if ( arguments[ 1 ].shape[ 0 ] !== k ) { + throw new TypeError( format( 'invalid argument. Labels vector length must match number of prototypes. Expected: `%u`. Actual: `%u`.', k, arguments[ 1 ].shape[ 0 ] ) ); + } + + protos = createMatrix( k, ndims, true ); // low-level + protos = copyMatrix( protos, arguments[ 0 ] ); + + protoLabels = createIntVector( k, true ); // low-level + protoLabels = copyVector( protoLabels, arguments[ 1 ] ); + if ( arguments.length > 2 ) { + options = arguments[ 2 ]; + FLG = true; + } + } else if ( isObject( arguments[ 0 ] ) ) { + if ( arguments.length > 1 ) { + throw new TypeError( format( 'invalid argument. First argument must be a matrix containing initial prototypes. Value: `%s`.', arguments[ 0 ] ) ); + } + options = arguments[ 0 ]; + FLG = true; + } else if ( arguments.length !== 0 ) { + throw new TypeError( format( 'invalid argument. Arguments must either be prototypes, labels and options or just options. Value: `%s`.', arguments ) ); + } + + if ( FLG ) { + err = validate( opts, options ); + if ( err ) { + throw err; + } + } + if ( opts.metric === 'euclidean' ) { + dist = squaredEuclidean; + } + if ( protos ) { + results = createResults( k, ndims ); + copyMatrix( results.prototypes, protos ); + copyVector( results.labels, protoLabels ); + } else { + // No prototype initialized + k = 0; + } + + setReadOnly( accumulator, 'predict', predict ); + + return accumulator; + + /** + * If provided a data point vector and label, the accumulator function returns updated results. If not provided arguments, the accumulator function returns the current results. + * + * @private + * @param {ndarray} [vec] - data vector + * @param {integer} [y] - target class label + * @throws {TypeError} first argument must be a 1-dimensional ndarray + * @throws {TypeError} second argument must be an integer (class label) + * @throws {Error} vector length must match prototype dimensions + * @returns {(Object|null)} classification results or null + */ + function accumulator( vec, y ) { + var newData; + var mini; + var pbuf; + var vbuf; + var sp; + var op; + var sv; + var ov; + var lr; + + if ( arguments.length === 0 ) { + if ( k === 0 ) { + return null; + } + return results; + } + if ( arguments.length < 2 ) { + throw new TypeError( format( 'invalid invocation. Must provide both a data vector and a class label.' ) ); + } + if ( !isVectorLike( vec ) ) { + throw new TypeError( format( 'invalid argument. First argument must be a one-dimensional ndarray. Value: `%s`.', vec ) ); + } + if ( ndims !== void 0 && vec.shape[ 0 ] !== ndims ) { + throw new Error( format( 'invalid argument. Vector length must match prototype dimensions. Expected: `%u`. Actual: `%u`.', ndims, vec.shape[ 0 ] ) ); + } + if ( !isInteger( y ) ) { + throw new TypeError( format( 'invalid argument. Second argument must be an integer representing the class label. Value: `%s`.', y ) ); + } + + vbuf = vec.data; + sv = vec.strides[ 0 ]; + ov = vec.offset; + + if ( k > 0 && hasLabel( protoLabels, y, k ) ) { + // Get buffer references and strides + pbuf = protos.data; + sp = protos.strides[ 0 ]; + + // Find the closest prototype + mini = closestPrototype( dist, k, ndims, pbuf, sp, 0, vbuf, sv, ov ); // eslint-disable-line max-len + + // Compute prototype buffer offset + op = sp * mini; + + if ( getLabel( protoLabels, mini ) === y ) { + lr = opts.learningRate; // correct: move towards + } else { + lr = -opts.learningRate; // incorrect: move away + } + + // Update the closest prototype + updatePrototype( ndims, pbuf, 1, op, vbuf, sv, ov, lr ); // Magic number `1` as we know that the matrix is row-major single-segment contiguous + + // Update the results object using dcopy (no dimensionality change) + dcopy( protos.data.length, protos.data, 1, results.prototypes.data, 1 ); // eslint-disable-line max-len + copyVector( results.labels, protoLabels ); + } else { + // Set ndims if this is the first prototype + if ( ndims === void 0 ) { + ndims = vec.shape[ 0 ]; + } + + // Add new prototype for this label + newData = addPrototype( k, ndims, protos, protoLabels, vec, y ); + protos = newData.protos; + protoLabels = newData.protoLabels; + k += 1; + + // Update the results object (dimensionality changed) + results = createResults( k, ndims ); + copyMatrix( results.prototypes, protos ); + copyVector( results.labels, protoLabels ); + } + + // TODO: Is it required to maintain statistics just like in incr/kmeans? + + return results; + } + + /** + * Predicts class labels for data points. + * + * @private + * @param {ndarray} [out] - output vector for storing predictions + * @param {ndarray} X - matrix containing data points (`n x ndims`) + * @throws {TypeError} output argument must be a vector + * @throws {TypeError} must provide a matrix + * @throws {Error} vector length must match number of data points + * @throws {Error} number of matrix columns must match prototype dimensions + * @returns {ndarray} vector containing predicted class labels + */ + function predict( out, X ) { + var xbuf; + var pbuf; + var npts; + var sx1; + var sx2; + var sp; + var ox; + var x; + var o; + var c; + var i; + + if ( k === 0 ) { + return null; + } + + if ( arguments.length > 1 ) { + if ( !isVectorLike( out ) ) { + throw new TypeError( format( 'invalid argument. Output argument must be a one-dimensional ndarray. Value: `%s`.', out ) ); + } + o = out; + x = X; + } else { + x = out; + } + if ( !isMatrixLike( x ) ) { + throw new TypeError( format( 'invalid argument. Must provide a two-dimensional ndarray. Value: `%s`.', x ) ); + } + if ( x.shape[ 1 ] !== ndims ) { + throw new Error( format( 'invalid argument. Number of matrix columns must match prototype dimensions. Expected: `%u`. Actual: `%u`.', ndims, x.shape[ 1 ] ) ); + } + if ( o === void 0 ) { + o = createIntVector( x.shape[ 0 ], false ); // high-level + } else if ( o.shape[ 0 ] !== x.shape[ 0 ] ) { + throw new Error( format( 'invalid argument. Output vector length must match the number of data points. Expected: `%u`. Actual: `%u`.', x.shape[ 0 ], o.shape[ 0 ] ) ); + } + + npts = x.shape[ 0 ]; + + pbuf = protos.data; + sp = protos.strides[ 0 ]; + + xbuf = x.data; + sx1 = x.strides[ 0 ]; + sx2 = x.strides[ 1 ]; + ox = x.offset; + + // For each data point, find the closest prototype and return its label + for ( i = 0; i < npts; i++ ) { + c = closestPrototype( dist, k, ndims, pbuf, sp, 0, xbuf, sx2, ox ); + + // Set the predicted label (the label of the closest prototype) + o.set( i, getLabel( protoLabels, c ) ); + + // Move to next data point + ox += sx1; + } + return o; + } +} + + +// EXPORTS // + +module.exports = incrlvq; diff --git a/lib/node_modules/@stdlib/ml/incr/lvq/lib/matrix.js b/lib/node_modules/@stdlib/ml/incr/lvq/lib/matrix.js new file mode 100644 index 000000000000..31a10c0044c3 --- /dev/null +++ b/lib/node_modules/@stdlib/ml/incr/lvq/lib/matrix.js @@ -0,0 +1,59 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var Float64Array = require( '@stdlib/array/float64' ); +var ctor = require( '@stdlib/ndarray/ctor' ); +var bctor = require( '@stdlib/ndarray/base/ctor' ); + + +// MAIN // + +/** +* Returns a matrix. +* +* @private +* @param {PositiveInteger} m - number of rows +* @param {PositiveInteger} n - number of columns +* @param {boolean} bool - boolean indicating whether to create a low-level ndarray +* @returns {ndarray} matrix +*/ +function createMatrix( m, n, bool ) { + var strides; + var buffer; + var shape; + var f; + + if ( bool ) { + f = bctor; + } else { + f = ctor; + } + buffer = new Float64Array( m*n ); + shape = [ m, n ]; + strides = [ n, 1 ]; + return f( 'float64', buffer, shape, strides, 0, 'row-major' ); +} + + +// EXPORTS // + +module.exports = createMatrix; diff --git a/lib/node_modules/@stdlib/ml/incr/lvq/lib/metrics.json b/lib/node_modules/@stdlib/ml/incr/lvq/lib/metrics.json new file mode 100644 index 000000000000..10241fb6cf29 --- /dev/null +++ b/lib/node_modules/@stdlib/ml/incr/lvq/lib/metrics.json @@ -0,0 +1,3 @@ +[ + "euclidean" +] diff --git a/lib/node_modules/@stdlib/ml/incr/lvq/lib/squared_euclidean.js b/lib/node_modules/@stdlib/ml/incr/lvq/lib/squared_euclidean.js new file mode 100644 index 000000000000..d9f4c7449205 --- /dev/null +++ b/lib/node_modules/@stdlib/ml/incr/lvq/lib/squared_euclidean.js @@ -0,0 +1,51 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var drrss = require( '@stdlib/blas/ext/base/drrss' ); + + +// MAIN // + +/** +* Computes the squared Euclidean distance between two data points. +* +* @private +* @param {NonNegativeInteger} N - number of elements +* @param {NumericArray} X - strided array +* @param {PositiveInteger} strideX - stride +* @param {NonNegativeInteger} offsetX - index offset +* @param {NumericArray} Y - strided array +* @param {PositiveInteger} strideY - stride +* @param {NonNegativeInteger} offsetY - index offset +* @returns {number} squared Euclidean distance +*/ +function squaredEuclidean( N, X, strideX, offsetX, Y, strideY, offsetY ) { + var dist; + + dist = drrss.ndarray( N, X, strideX, offsetX, Y, strideY, offsetY ); // replace with drss once it is implemented + return dist * dist; +} + + +// EXPORTS // + +module.exports = squaredEuclidean; diff --git a/lib/node_modules/@stdlib/ml/incr/lvq/lib/update_proto.js b/lib/node_modules/@stdlib/ml/incr/lvq/lib/update_proto.js new file mode 100644 index 000000000000..da246480a6bd --- /dev/null +++ b/lib/node_modules/@stdlib/ml/incr/lvq/lib/update_proto.js @@ -0,0 +1,67 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MAIN // + +/** +* Updates the winner prototype using LVQ1 update rule. +* +* ## Notes +* +* - LVQ1 update rule: +* - If classification is correct: m_c = m_c + lr * (x - m_c) +* - If classification is incorrect: m_c = m_c - lr * (x - m_c) +* +* @private +* @param {PositiveInteger} ndims - number of dimensions +* @param {NumericArray} P - strided array containing prototypes +* @param {PositiveInteger} sp - prototype column stride +* @param {NonNegativeInteger} op - prototype index offset +* @param {NumericArray} V - strided array containing a data point +* @param {PositiveInteger} sv - vector stride +* @param {NonNegativeInteger} ov - vector index offset +* @param {number} lr - learning rate (positive for correct, negative for incorrect classification) +* @returns {NumericArray} strided array containing prototypes +*/ +function updatePrototype( ndims, P, sp, op, V, sv, ov, lr ) { + var delta; + var pi; + var i; + + /* + * LVQ update: P_new = P_old + lr * (V - P_old) + * When lr is negative (incorrect classification), prototype moves away from input. + */ + for ( i = 0; i < ndims; i++ ) { + pi = P[ op ]; + delta = V[ ov ] - pi; + pi += lr * delta; + P[ op ] = pi; + + op += sp; + ov += sv; + } + return P; +} + + +// EXPORTS // + +module.exports = updatePrototype; diff --git a/lib/node_modules/@stdlib/ml/incr/lvq/lib/validate.js b/lib/node_modules/@stdlib/ml/incr/lvq/lib/validate.js new file mode 100644 index 000000000000..ef21bc4c72d0 --- /dev/null +++ b/lib/node_modules/@stdlib/ml/incr/lvq/lib/validate.js @@ -0,0 +1,81 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var isObject = require( '@stdlib/assert/is-plain-object' ); +var hasOwnProp = require( '@stdlib/assert/has-own-property' ); +var isPositiveNumber = require( '@stdlib/assert/is-positive-number' ).isPrimitive; +var contains = require( '@stdlib/array/base/assert/contains' ).factory; +var format = require( '@stdlib/string/format' ); +var METRICS = require( './metrics.json' ); + + +// VARIABLES // + +var isMetric = contains( METRICS ); + + +// MAIN // + +/** +* Validates function options. +* +* @private +* @param {Object} opts - destination object +* @param {Options} options - function options +* @param {string} [options.metric] - distance metric +* @param {PositiveNumber} [options.learningRate] - initial learning rate +* @returns {(Error|null)} null or an error object +* +* @example +* var opts = {}; +* var options = { +* 'metric': 'euclidean', +* 'learningRate': 0.1 +* }; +* var err = validate( opts, options ); +* if ( err ) { +* throw err; +* } +*/ +function validate( opts, options ) { + if ( !isObject( options ) ) { + return new TypeError( format( 'invalid argument. Options argument must be an object. Value: `%s`.', options ) ); + } + if ( hasOwnProp( options, 'metric' ) ) { + opts.metric = options.metric; + if ( !isMetric( opts.metric ) ) { + return new TypeError( format( 'invalid option. `%s` option must be one of the following: "%s". Option: `%s`.', 'metric', METRICS.join( '", "' ), opts.metric ) ); + } + } + if ( hasOwnProp( options, 'learningRate' ) ) { + opts.learningRate = options.learningRate; + if ( !isPositiveNumber( opts.learningRate ) ) { + return new TypeError( format( 'invalid option. `%s` option must be a positive number. Option: `%s`.', 'learningRate', opts.learningRate ) ); + } + } + return null; +} + + +// EXPORTS // + +module.exports = validate; diff --git a/lib/node_modules/@stdlib/ml/incr/lvq/lib/vector.js b/lib/node_modules/@stdlib/ml/incr/lvq/lib/vector.js new file mode 100644 index 000000000000..08a0313c4d36 --- /dev/null +++ b/lib/node_modules/@stdlib/ml/incr/lvq/lib/vector.js @@ -0,0 +1,58 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var Float64Array = require( '@stdlib/array/float64' ); +var ctor = require( '@stdlib/ndarray/ctor' ); +var bctor = require( '@stdlib/ndarray/base/ctor' ); + + +// MAIN // + +/** +* Returns a vector. +* +* @private +* @param {PositiveInteger} N - number of elements +* @param {boolean} bool - boolean indicating whether to create a low-level ndarray +* @returns {ndarray} vector +*/ +function createVector( N, bool ) { + var strides; + var buffer; + var shape; + var f; + + if ( bool ) { + f = bctor; + } else { + f = ctor; + } + buffer = new Float64Array( N ); + shape = [ N ]; + strides = [ 1 ]; + return f( 'float64', buffer, shape, strides, 0, 'row-major' ); +} + + +// EXPORTS // + +module.exports = createVector; diff --git a/lib/node_modules/@stdlib/ml/incr/lvq/package.json b/lib/node_modules/@stdlib/ml/incr/lvq/package.json new file mode 100644 index 000000000000..c866ba589055 --- /dev/null +++ b/lib/node_modules/@stdlib/ml/incr/lvq/package.json @@ -0,0 +1,72 @@ +{ + "name": "@stdlib/ml/incr/lvq", + "version": "0.0.0", + "description": "Incrementally performs supervised classification using lvq1", + "license": "Apache-2.0", + "author": { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + }, + "contributors": [ + { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + } + ], + "main": "./lib", + "directories": { + "benchmark": "./benchmark", + "doc": "./docs", + "example": "./examples", + "lib": "./lib", + "test": "./test" + }, + "types": "./docs/types", + "scripts": {}, + "homepage": "https://github.com/stdlib-js/stdlib", + "repository": { + "type": "git", + "url": "git://github.com/stdlib-js/stdlib.git" + }, + "bugs": { + "url": "https://github.com/stdlib-js/stdlib/issues" + }, + "dependencies": {}, + "devDependencies": {}, + "engines": { + "node": ">=0.10.0", + "npm": ">2.7.0" + }, + "os": [ + "aix", + "darwin", + "freebsd", + "linux", + "macos", + "openbsd", + "sunos", + "win32", + "windows" + ], + "keywords": [ + "stdlib", + "stdmath", + "stdml", + "ml", + "machine", + "learning", + "mathematics", + "math", + "statistics", + "stats", + "data mining", + "quantization", + "euclidean", + "lvq", + "learning vector quantization", + "classification", + "supervised", + "incremental", + "accumulator" + ] +}