deepbox 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +39 -37
  3. package/dist/{CSRMatrix-KzNt6QpS.d.ts → CSRMatrix-B7XtUAZO.d.cts} +3 -3
  4. package/dist/{CSRMatrix-CwGwQRea.d.cts → CSRMatrix-CtD23fRM.d.ts} +3 -3
  5. package/dist/{Tensor-BQLk1ltW.d.cts → Tensor-BORFp_zt.d.ts} +27 -7
  6. package/dist/{Tensor-g8mUClel.d.ts → Tensor-fxBg-TFZ.d.cts} +27 -7
  7. package/dist/{chunk-FJYLIGJX.js → chunk-3AX37GPK.js} +33 -7
  8. package/dist/chunk-3AX37GPK.js.map +1 -0
  9. package/dist/{chunk-PR647I7R.js → chunk-3YFEYDHN.js} +21 -4
  10. package/dist/chunk-3YFEYDHN.js.map +1 -0
  11. package/dist/{chunk-XMWVME2W.js → chunk-6SX26MAJ.js} +4 -4
  12. package/dist/{chunk-XMWVME2W.js.map → chunk-6SX26MAJ.js.map} +1 -1
  13. package/dist/{chunk-C4PKXY74.cjs → chunk-6X7XFNDO.cjs} +94 -77
  14. package/dist/chunk-6X7XFNDO.cjs.map +1 -0
  15. package/dist/{chunk-6AE5FKKQ.cjs → chunk-724CXHFH.cjs} +1211 -919
  16. package/dist/chunk-724CXHFH.cjs.map +1 -0
  17. package/dist/{chunk-AU7XHGKJ.js → chunk-AJTKVBY5.js} +4 -4
  18. package/dist/{chunk-AU7XHGKJ.js.map → chunk-AJTKVBY5.js.map} +1 -1
  19. package/dist/{chunk-ZB75FESB.cjs → chunk-AV6WGSYX.cjs} +130 -104
  20. package/dist/chunk-AV6WGSYX.cjs.map +1 -0
  21. package/dist/{chunk-ZLW62TJG.cjs → chunk-BWOSU234.cjs} +142 -141
  22. package/dist/chunk-BWOSU234.cjs.map +1 -0
  23. package/dist/{chunk-4S73VUBD.js → chunk-CZOMBUI7.js} +3 -3
  24. package/dist/chunk-CZOMBUI7.js.map +1 -0
  25. package/dist/{chunk-QERHVCHC.cjs → chunk-EUZHJDZ6.cjs} +419 -364
  26. package/dist/chunk-EUZHJDZ6.cjs.map +1 -0
  27. package/dist/{chunk-AD436M45.js → chunk-G2G55ATL.js} +120 -58
  28. package/dist/chunk-G2G55ATL.js.map +1 -0
  29. package/dist/{chunk-5R4S63PF.js → chunk-G3WNLNYS.js} +119 -64
  30. package/dist/chunk-G3WNLNYS.js.map +1 -0
  31. package/dist/{chunk-XEG44RF6.cjs → chunk-G7KXZHG6.cjs} +105 -95
  32. package/dist/chunk-G7KXZHG6.cjs.map +1 -0
  33. package/dist/{chunk-MLBMYKCG.js → chunk-H3JR7SV2.js} +255 -113
  34. package/dist/chunk-H3JR7SV2.js.map +1 -0
  35. package/dist/{chunk-PHV2DKRS.cjs → chunk-HDKMIG6E.cjs} +107 -107
  36. package/dist/{chunk-PHV2DKRS.cjs.map → chunk-HDKMIG6E.cjs.map} +1 -1
  37. package/dist/{chunk-ALS7ETWZ.cjs → chunk-HI2EZHCJ.cjs} +111 -102
  38. package/dist/chunk-HI2EZHCJ.cjs.map +1 -0
  39. package/dist/{chunk-OX6QXFMV.cjs → chunk-IT4BZUYE.cjs} +490 -428
  40. package/dist/chunk-IT4BZUYE.cjs.map +1 -0
  41. package/dist/{chunk-E3EU5FZO.cjs → chunk-JTZPRV6E.cjs} +123 -123
  42. package/dist/{chunk-E3EU5FZO.cjs.map → chunk-JTZPRV6E.cjs.map} +1 -1
  43. package/dist/{chunk-PL7TAYKI.js → chunk-K2L5C5YH.js} +8 -7
  44. package/dist/chunk-K2L5C5YH.js.map +1 -0
  45. package/dist/{chunk-BCR7G3A6.js → chunk-KCF6P34A.js} +356 -64
  46. package/dist/chunk-KCF6P34A.js.map +1 -0
  47. package/dist/{chunk-ZXKBDFP3.js → chunk-LZHVHD62.js} +15 -6
  48. package/dist/chunk-LZHVHD62.js.map +1 -0
  49. package/dist/{chunk-LWECRCW2.cjs → chunk-MTJF52AJ.cjs} +141 -141
  50. package/dist/{chunk-LWECRCW2.cjs.map → chunk-MTJF52AJ.cjs.map} +1 -1
  51. package/dist/{chunk-B5TNKUEY.js → chunk-NDDTUFKK.js} +16 -6
  52. package/dist/chunk-NDDTUFKK.js.map +1 -0
  53. package/dist/{chunk-DWZY6PIP.cjs → chunk-NOQI6OFL.cjs} +615 -473
  54. package/dist/chunk-NOQI6OFL.cjs.map +1 -0
  55. package/dist/{chunk-F3JWBINJ.js → chunk-OEXDJFHA.js} +4 -4
  56. package/dist/{chunk-F3JWBINJ.js.map → chunk-OEXDJFHA.js.map} +1 -1
  57. package/dist/{chunk-JSCDE774.cjs → chunk-Z6BGACIH.cjs} +3 -3
  58. package/dist/chunk-Z6BGACIH.cjs.map +1 -0
  59. package/dist/core/index.cjs +50 -50
  60. package/dist/core/index.d.cts +2 -2
  61. package/dist/core/index.d.ts +2 -2
  62. package/dist/core/index.js +1 -1
  63. package/dist/dataframe/index.cjs +6 -6
  64. package/dist/dataframe/index.d.cts +3 -3
  65. package/dist/dataframe/index.d.ts +3 -3
  66. package/dist/dataframe/index.js +3 -3
  67. package/dist/datasets/index.cjs +34 -34
  68. package/dist/datasets/index.d.cts +3 -3
  69. package/dist/datasets/index.d.ts +3 -3
  70. package/dist/datasets/index.js +3 -3
  71. package/dist/{index-C1mfVYoo.d.ts → index-B18dHc8q.d.ts} +81 -46
  72. package/dist/{index-GFAVyOWO.d.ts → index-BHHX0qTY.d.cts} +14 -12
  73. package/dist/{index-tk4lSYod.d.ts → index-BI6QOUvV.d.ts} +106 -80
  74. package/dist/{index-DIp_RrRt.d.ts → index-BKvK21lf.d.ts} +13 -35
  75. package/dist/{index-BJY2SI4i.d.ts → index-BL8jLf3K.d.cts} +12 -11
  76. package/dist/{index-Cn3SdB0O.d.ts → index-BNbX167d.d.cts} +16 -10
  77. package/dist/{index-BWGhrDlr.d.ts → index-BT2ofL7Z.d.cts} +35 -35
  78. package/dist/{index-BbA2Gxfl.d.ts → index-BqcfIcL4.d.ts} +15 -15
  79. package/dist/{index-ZtI1Iy4L.d.ts → index-BrgrECM2.d.ts} +41 -38
  80. package/dist/{index-CDw5CnOU.d.ts → index-BtYKI9yJ.d.ts} +10 -8
  81. package/dist/{index-DIT_OO9C.d.cts → index-C7nLsAOC.d.cts} +10 -8
  82. package/dist/{index-D9Loo1_A.d.cts → index-CNj2Mxwf.d.cts} +81 -46
  83. package/dist/{index-DmEg_LCm.d.cts → index-CYlxeNW1.d.cts} +5 -3
  84. package/dist/{index-D61yaSMY.d.cts → index-CiTd61a5.d.ts} +12 -11
  85. package/dist/{index-BndMbqsM.d.ts → index-Cjnn0KeN.d.cts} +35 -21
  86. package/dist/{index-9oQx1HgV.d.cts → index-CkGGAn69.d.cts} +41 -38
  87. package/dist/{index-74AB8Cyh.d.cts → index-D4URSgqA.d.ts} +16 -10
  88. package/dist/{index-DoPWVxPo.d.cts → index-D4pn5zLT.d.ts} +35 -21
  89. package/dist/{index-DuCxd-8d.d.ts → index-D9ztTlDr.d.ts} +60 -42
  90. package/dist/{index-BgHYAoSS.d.cts → index-DF28ZPB5.d.cts} +60 -42
  91. package/dist/{index-eJgeni9c.d.cts → index-DLdiQzf0.d.cts} +106 -80
  92. package/dist/{index-WHQLn0e8.d.cts → index-DN4omPQw.d.ts} +35 -35
  93. package/dist/{index-CrqLlS-a.d.ts → index-DUnFq1WV.d.ts} +5 -3
  94. package/dist/{index-DbultU6X.d.cts → index-DgaYshkF.d.ts} +14 -12
  95. package/dist/{index-B_DK4FKY.d.cts → index-GUHYEhxs.d.cts} +13 -35
  96. package/dist/{index-CCvlwAmL.d.cts → index-TP--4irE.d.cts} +16 -14
  97. package/dist/{index-Dx42TZaY.d.ts → index-x0z_sanT.d.ts} +16 -14
  98. package/dist/{index-DyZ4QQf5.d.cts → index-xWH7ujWa.d.cts} +15 -15
  99. package/dist/index.cjs +26 -26
  100. package/dist/index.d.cts +17 -17
  101. package/dist/index.d.ts +17 -17
  102. package/dist/index.js +13 -13
  103. package/dist/linalg/index.cjs +22 -22
  104. package/dist/linalg/index.d.cts +3 -3
  105. package/dist/linalg/index.d.ts +3 -3
  106. package/dist/linalg/index.js +3 -3
  107. package/dist/metrics/index.cjs +40 -40
  108. package/dist/metrics/index.d.cts +3 -3
  109. package/dist/metrics/index.d.ts +3 -3
  110. package/dist/metrics/index.js +3 -3
  111. package/dist/ml/index.cjs +23 -23
  112. package/dist/ml/index.d.cts +3 -3
  113. package/dist/ml/index.d.ts +3 -3
  114. package/dist/ml/index.js +4 -4
  115. package/dist/ndarray/index.cjs +125 -125
  116. package/dist/ndarray/index.d.cts +5 -5
  117. package/dist/ndarray/index.d.ts +5 -5
  118. package/dist/ndarray/index.js +2 -2
  119. package/dist/nn/index.cjs +36 -36
  120. package/dist/nn/index.d.cts +6 -6
  121. package/dist/nn/index.d.ts +6 -6
  122. package/dist/nn/index.js +3 -3
  123. package/dist/optim/index.cjs +19 -19
  124. package/dist/optim/index.d.cts +4 -4
  125. package/dist/optim/index.d.ts +4 -4
  126. package/dist/optim/index.js +2 -2
  127. package/dist/plot/index.cjs +29 -29
  128. package/dist/plot/index.d.cts +6 -6
  129. package/dist/plot/index.d.ts +6 -6
  130. package/dist/plot/index.js +3 -3
  131. package/dist/preprocess/index.cjs +21 -21
  132. package/dist/preprocess/index.d.cts +4 -4
  133. package/dist/preprocess/index.d.ts +4 -4
  134. package/dist/preprocess/index.js +3 -3
  135. package/dist/random/index.cjs +19 -19
  136. package/dist/random/index.d.cts +3 -3
  137. package/dist/random/index.d.ts +3 -3
  138. package/dist/random/index.js +3 -3
  139. package/dist/stats/index.cjs +36 -36
  140. package/dist/stats/index.d.cts +3 -3
  141. package/dist/stats/index.d.ts +3 -3
  142. package/dist/stats/index.js +3 -3
  143. package/dist/{tensor-B96jjJLQ.d.cts → tensor-IlVTF0bz.d.cts} +16 -3
  144. package/dist/{tensor-B96jjJLQ.d.ts → tensor-IlVTF0bz.d.ts} +16 -3
  145. package/package.json +3 -2
  146. package/dist/chunk-4S73VUBD.js.map +0 -1
  147. package/dist/chunk-5R4S63PF.js.map +0 -1
  148. package/dist/chunk-6AE5FKKQ.cjs.map +0 -1
  149. package/dist/chunk-AD436M45.js.map +0 -1
  150. package/dist/chunk-ALS7ETWZ.cjs.map +0 -1
  151. package/dist/chunk-B5TNKUEY.js.map +0 -1
  152. package/dist/chunk-BCR7G3A6.js.map +0 -1
  153. package/dist/chunk-C4PKXY74.cjs.map +0 -1
  154. package/dist/chunk-DWZY6PIP.cjs.map +0 -1
  155. package/dist/chunk-FJYLIGJX.js.map +0 -1
  156. package/dist/chunk-JSCDE774.cjs.map +0 -1
  157. package/dist/chunk-MLBMYKCG.js.map +0 -1
  158. package/dist/chunk-OX6QXFMV.cjs.map +0 -1
  159. package/dist/chunk-PL7TAYKI.js.map +0 -1
  160. package/dist/chunk-PR647I7R.js.map +0 -1
  161. package/dist/chunk-QERHVCHC.cjs.map +0 -1
  162. package/dist/chunk-XEG44RF6.cjs.map +0 -1
  163. package/dist/chunk-ZB75FESB.cjs.map +0 -1
  164. package/dist/chunk-ZLW62TJG.cjs.map +0 -1
  165. package/dist/chunk-ZXKBDFP3.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { svd, lstsq, cholesky, solveTriangular } from './chunk-AU7XHGKJ.js';
2
- import { tensor, mean, dot, sub, reshape, transpose, isContiguous } from './chunk-BCR7G3A6.js';
3
- import { __export, InvalidParameterError, NotImplementedError, NotFittedError, DataValidationError, ShapeError, DeepboxError } from './chunk-4S73VUBD.js';
1
+ import { svd, lstsq, cholesky, solveTriangular } from './chunk-AJTKVBY5.js';
2
+ import { tensor, mean, dot, sub, reshape, transpose, isContiguous } from './chunk-KCF6P34A.js';
3
+ import { __export, InvalidParameterError, NotImplementedError, NotFittedError, DataValidationError, ShapeError, DeepboxError } from './chunk-CZOMBUI7.js';
4
4
 
5
5
  // src/ml/index.ts
6
6
  var ml_exports = {};
@@ -272,6 +272,23 @@ var DBSCAN = class {
272
272
  }
273
273
  return this.labels_;
274
274
  }
275
+ /**
276
+ * Number of clusters found (excluding noise).
277
+ *
278
+ * @returns Number of distinct clusters (labels >= 0)
279
+ * @throws {NotFittedError} If the model has not been fitted
280
+ */
281
+ get nClusters() {
282
+ if (!this.fitted || !this.labels_) {
283
+ throw new NotFittedError("DBSCAN must be fitted to access nClusters");
284
+ }
285
+ const unique = /* @__PURE__ */ new Set();
286
+ for (let i = 0; i < this.labels_.size; i++) {
287
+ const label = Number(this.labels_.data[this.labels_.offset + i]);
288
+ if (label >= 0) unique.add(label);
289
+ }
290
+ return unique.size;
291
+ }
275
292
  /**
276
293
  * Get indices of core samples discovered during fitting.
277
294
  *
@@ -1903,10 +1920,10 @@ var GradientBoostingClassifier = class {
1903
1920
  maxDepth;
1904
1921
  /** Minimum samples to split */
1905
1922
  minSamplesSplit;
1906
- /** Array of weak learners */
1907
- estimators = [];
1908
- /** Initial log-odds prediction */
1909
- initPrediction = 0;
1923
+ /** Per-class arrays of weak learners (OvR for multiclass, single for binary) */
1924
+ estimatorsPerClass = [];
1925
+ /** Per-class initial log-odds predictions */
1926
+ initPredictions = [];
1910
1927
  /** Number of features */
1911
1928
  nFeatures = 0;
1912
1929
  /** Unique class labels */
@@ -1947,19 +1964,65 @@ var GradientBoostingClassifier = class {
1947
1964
  );
1948
1965
  }
1949
1966
  }
1967
+ /**
1968
+ * Fit a single binary boosting ensemble.
1969
+ * Trains nEstimators regression trees to optimize log loss for a binary target.
1970
+ */
1971
+ fitBinary(X, yBinary, nSamples) {
1972
+ const posCount = yBinary.filter((v) => v === 1).length;
1973
+ const negCount = nSamples - posCount;
1974
+ const initPred = Math.log((posCount + 1) / (negCount + 1));
1975
+ const rawScores = new Array(nSamples).fill(initPred);
1976
+ const estimators = [];
1977
+ for (let m = 0; m < this.nEstimators; m++) {
1978
+ const residuals = [];
1979
+ for (let i = 0; i < nSamples; i++) {
1980
+ const prob = 1 / (1 + Math.exp(-(rawScores[i] ?? 0)));
1981
+ residuals.push((yBinary[i] ?? 0) - prob);
1982
+ }
1983
+ const tree = new DecisionTreeRegressor({
1984
+ maxDepth: this.maxDepth,
1985
+ minSamplesSplit: this.minSamplesSplit,
1986
+ minSamplesLeaf: 1
1987
+ });
1988
+ tree.fit(X, tensor(residuals));
1989
+ estimators.push(tree);
1990
+ const treePred = tree.predict(X);
1991
+ for (let i = 0; i < nSamples; i++) {
1992
+ rawScores[i] = (rawScores[i] ?? 0) + this.learningRate * Number(treePred.data[treePred.offset + i]);
1993
+ }
1994
+ }
1995
+ return { estimators, initPred };
1996
+ }
1997
+ /**
1998
+ * Compute raw scores for a single binary ensemble.
1999
+ */
2000
+ predictRawBinary(X, classIdx) {
2001
+ const nSamples = X.shape[0] ?? 0;
2002
+ const rawScores = new Array(nSamples).fill(this.initPredictions[classIdx] ?? 0);
2003
+ const estimators = this.estimatorsPerClass[classIdx] ?? [];
2004
+ for (const tree of estimators) {
2005
+ const treePred = tree.predict(X);
2006
+ for (let i = 0; i < nSamples; i++) {
2007
+ rawScores[i] = (rawScores[i] ?? 0) + this.learningRate * Number(treePred.data[treePred.offset + i]);
2008
+ }
2009
+ }
2010
+ return rawScores;
2011
+ }
1950
2012
  /**
1951
2013
  * Fit the gradient boosting classifier on training data.
1952
2014
  *
1953
2015
  * Builds an additive model by sequentially fitting regression trees
1954
2016
  * to the pseudo-residuals (gradient of log loss).
2017
+ * Supports binary (2 classes) and multiclass (>2 classes via OvR).
1955
2018
  *
1956
2019
  * @param X - Training data of shape (n_samples, n_features)
1957
- * @param y - Target class labels of shape (n_samples,). Must contain exactly 2 classes.
2020
+ * @param y - Target class labels of shape (n_samples,). Must contain at least 2 classes.
1958
2021
  * @returns this - The fitted estimator
1959
2022
  * @throws {ShapeError} If X is not 2D or y is not 1D
1960
2023
  * @throws {ShapeError} If X and y have different number of samples
1961
2024
  * @throws {DataValidationError} If X or y contain NaN/Inf values
1962
- * @throws {InvalidParameterError} If y does not contain exactly 2 classes
2025
+ * @throws {InvalidParameterError} If y does not contain at least 2 classes
1963
2026
  */
1964
2027
  fit(X, y) {
1965
2028
  validateFitInputs(X, y);
@@ -1971,36 +2034,26 @@ var GradientBoostingClassifier = class {
1971
2034
  yData.push(Number(y.data[y.offset + i]));
1972
2035
  }
1973
2036
  this.classLabels = [...new Set(yData)].sort((a, b) => a - b);
1974
- if (this.classLabels.length !== 2) {
2037
+ if (this.classLabels.length < 2) {
1975
2038
  throw new InvalidParameterError(
1976
- "GradientBoostingClassifier requires exactly 2 classes",
2039
+ "GradientBoostingClassifier requires at least 2 classes",
1977
2040
  "y",
1978
2041
  this.classLabels.length
1979
2042
  );
1980
2043
  }
1981
- const yBinary = yData.map((label) => label === this.classLabels[0] ? 0 : 1);
1982
- const posCount = yBinary.filter((v) => v === 1).length;
1983
- const negCount = nSamples - posCount;
1984
- this.initPrediction = Math.log((posCount + 1) / (negCount + 1));
1985
- const rawScores = new Array(nSamples).fill(this.initPrediction);
1986
- this.estimators = [];
1987
- for (let m = 0; m < this.nEstimators; m++) {
1988
- const residuals = [];
1989
- for (let i = 0; i < nSamples; i++) {
1990
- const prob = 1 / (1 + Math.exp(-(rawScores[i] ?? 0)));
1991
- const y_i = yBinary[i] ?? 0;
1992
- residuals.push(y_i - prob);
1993
- }
1994
- const tree = new DecisionTreeRegressor({
1995
- maxDepth: this.maxDepth,
1996
- minSamplesSplit: this.minSamplesSplit,
1997
- minSamplesLeaf: 1
1998
- });
1999
- tree.fit(X, tensor(residuals));
2000
- this.estimators.push(tree);
2001
- const treePred = tree.predict(X);
2002
- for (let i = 0; i < nSamples; i++) {
2003
- rawScores[i] = (rawScores[i] ?? 0) + this.learningRate * Number(treePred.data[treePred.offset + i]);
2044
+ this.estimatorsPerClass = [];
2045
+ this.initPredictions = [];
2046
+ if (this.classLabels.length === 2) {
2047
+ const yBinary = yData.map((label) => label === this.classLabels[0] ? 0 : 1);
2048
+ const { estimators, initPred } = this.fitBinary(X, yBinary, nSamples);
2049
+ this.estimatorsPerClass.push(estimators);
2050
+ this.initPredictions.push(initPred);
2051
+ } else {
2052
+ for (const classLabel of this.classLabels) {
2053
+ const yBinary = yData.map((label) => label === classLabel ? 1 : 0);
2054
+ const { estimators, initPred } = this.fitBinary(X, yBinary, nSamples);
2055
+ this.estimatorsPerClass.push(estimators);
2056
+ this.initPredictions.push(initPred);
2004
2057
  }
2005
2058
  }
2006
2059
  this.fitted = true;
@@ -2021,29 +2074,40 @@ var GradientBoostingClassifier = class {
2021
2074
  }
2022
2075
  validatePredictInputs(X, this.nFeatures ?? 0, "GradientBoostingClassifier");
2023
2076
  const nSamples = X.shape[0] ?? 0;
2024
- const rawScores = new Array(nSamples).fill(this.initPrediction);
2025
- for (const tree of this.estimators) {
2026
- const treePred = tree.predict(X);
2077
+ const predictions = [];
2078
+ if (this.classLabels.length === 2) {
2079
+ const rawScores = this.predictRawBinary(X, 0);
2027
2080
  for (let i = 0; i < nSamples; i++) {
2028
- rawScores[i] = (rawScores[i] ?? 0) + this.learningRate * Number(treePred.data[treePred.offset + i]);
2081
+ const prob = 1 / (1 + Math.exp(-(rawScores[i] ?? 0)));
2082
+ predictions.push(prob >= 0.5 ? this.classLabels[1] ?? 0 : this.classLabels[0] ?? 0);
2083
+ }
2084
+ } else {
2085
+ const allScores = [];
2086
+ for (let c = 0; c < this.classLabels.length; c++) {
2087
+ allScores.push(this.predictRawBinary(X, c));
2088
+ }
2089
+ for (let i = 0; i < nSamples; i++) {
2090
+ let bestClass = 0;
2091
+ let bestScore = -Infinity;
2092
+ for (let c = 0; c < this.classLabels.length; c++) {
2093
+ const score = allScores[c]?.[i] ?? 0;
2094
+ if (score > bestScore) {
2095
+ bestScore = score;
2096
+ bestClass = c;
2097
+ }
2098
+ }
2099
+ predictions.push(this.classLabels[bestClass] ?? 0);
2029
2100
  }
2030
- }
2031
- const predictions = [];
2032
- for (let i = 0; i < nSamples; i++) {
2033
- const prob = 1 / (1 + Math.exp(-(rawScores[i] ?? 0)));
2034
- const predictedClass = prob >= 0.5 ? this.classLabels[1] : this.classLabels[0];
2035
- predictions.push(predictedClass ?? 0);
2036
2101
  }
2037
2102
  return tensor(predictions, { dtype: "int32" });
2038
2103
  }
2039
2104
  /**
2040
2105
  * Predict class probabilities for samples in X.
2041
2106
  *
2042
- * Returns a matrix of shape (n_samples, 2) where columns are
2043
- * [P(class_0), P(class_1)].
2107
+ * Returns a matrix of shape (n_samples, n_classes).
2044
2108
  *
2045
2109
  * @param X - Samples of shape (n_samples, n_features)
2046
- * @returns Class probability matrix of shape (n_samples, 2)
2110
+ * @returns Class probability matrix of shape (n_samples, n_classes)
2047
2111
  * @throws {NotFittedError} If the model has not been fitted
2048
2112
  * @throws {ShapeError} If X has wrong dimensions or feature count
2049
2113
  * @throws {DataValidationError} If X contains NaN/Inf values
@@ -2054,17 +2118,27 @@ var GradientBoostingClassifier = class {
2054
2118
  }
2055
2119
  validatePredictInputs(X, this.nFeatures ?? 0, "GradientBoostingClassifier");
2056
2120
  const nSamples = X.shape[0] ?? 0;
2057
- const rawScores = new Array(nSamples).fill(this.initPrediction);
2058
- for (const tree of this.estimators) {
2059
- const treePred = tree.predict(X);
2121
+ const nClasses = this.classLabels.length;
2122
+ const proba = [];
2123
+ if (nClasses === 2) {
2124
+ const rawScores = this.predictRawBinary(X, 0);
2060
2125
  for (let i = 0; i < nSamples; i++) {
2061
- rawScores[i] = (rawScores[i] ?? 0) + this.learningRate * Number(treePred.data[treePred.offset + i]);
2126
+ const prob = 1 / (1 + Math.exp(-(rawScores[i] ?? 0)));
2127
+ proba.push([1 - prob, prob]);
2128
+ }
2129
+ } else {
2130
+ const allScores = [];
2131
+ for (let c = 0; c < nClasses; c++) {
2132
+ allScores.push(this.predictRawBinary(X, c));
2133
+ }
2134
+ for (let i = 0; i < nSamples; i++) {
2135
+ const sigScores = [];
2136
+ for (let c = 0; c < nClasses; c++) {
2137
+ sigScores.push(1 / (1 + Math.exp(-(allScores[c]?.[i] ?? 0))));
2138
+ }
2139
+ const total = sigScores.reduce((s, v) => s + v, 0) || 1;
2140
+ proba.push(sigScores.map((v) => v / total));
2062
2141
  }
2063
- }
2064
- const proba = [];
2065
- for (let i = 0; i < nSamples; i++) {
2066
- const prob = 1 / (1 + Math.exp(-(rawScores[i] ?? 0)));
2067
- proba.push([1 - prob, prob]);
2068
2142
  }
2069
2143
  return tensor(proba);
2070
2144
  }
@@ -4699,6 +4773,19 @@ var TSNE = class {
4699
4773
  this.fitTransform(X);
4700
4774
  return this;
4701
4775
  }
4776
+ /**
4777
+ * Return the fitted embedding. For t-SNE, transform is equivalent to
4778
+ * returning the already-computed embedding (t-SNE is non-parametric).
4779
+ *
4780
+ * @param _X - Ignored, present for API compatibility
4781
+ * @returns Low-dimensional embedding of shape (n_samples, n_components)
4782
+ */
4783
+ transform(_X) {
4784
+ if (!this.fitted) {
4785
+ throw new NotFittedError("TSNE must be fitted before transform");
4786
+ }
4787
+ return tensor(this.embedding);
4788
+ }
4702
4789
  /**
4703
4790
  * Get the embedding.
4704
4791
  */
@@ -5332,13 +5419,13 @@ var LinearSVC = class {
5332
5419
  maxIter;
5333
5420
  /** Tolerance for stopping criterion */
5334
5421
  tol;
5335
- /** Weight vector of shape (n_features,) */
5336
- weights = [];
5337
- /** Bias term */
5338
- bias = 0;
5422
+ /** Per-class weight vectors (OvR for multiclass, single for binary) */
5423
+ weightsPerClass = [];
5424
+ /** Per-class bias terms */
5425
+ biasPerClass = [];
5339
5426
  /** Number of features seen during fit */
5340
5427
  nFeatures = 0;
5341
- /** Unique class labels [0, 1] mapped from original labels */
5428
+ /** Unique class labels */
5342
5429
  classLabels = [];
5343
5430
  /** Whether the model has been fitted */
5344
5431
  fitted = false;
@@ -5368,19 +5455,69 @@ var LinearSVC = class {
5368
5455
  throw new InvalidParameterError("tol must be >= 0", "tol", this.tol);
5369
5456
  }
5370
5457
  }
5458
+ /**
5459
+ * Fit a single binary SVM using sub-gradient descent on hinge loss.
5460
+ * Maps labels to {-1, +1} and returns learned weights + bias.
5461
+ */
5462
+ fitBinary(XData, yMapped, nSamples, nFeatures) {
5463
+ const weights = new Array(nFeatures).fill(0);
5464
+ let bias = 0;
5465
+ const learningRate = 0.01;
5466
+ for (let iter = 0; iter < this.maxIter; iter++) {
5467
+ let maxViolation = 0;
5468
+ for (let i = 0; i < nSamples; i++) {
5469
+ const xi = XData[i];
5470
+ const yi = yMapped[i];
5471
+ if (xi === void 0 || yi === void 0) continue;
5472
+ let decision = bias;
5473
+ for (let j = 0; j < nFeatures; j++) {
5474
+ decision += (weights[j] ?? 0) * (xi[j] ?? 0);
5475
+ }
5476
+ const margin = yi * decision;
5477
+ if (margin < 1) {
5478
+ maxViolation = Math.max(maxViolation, 1 - margin);
5479
+ }
5480
+ const effectiveLR = Math.min(learningRate, 1 / (this.C * 10));
5481
+ if (margin < 1) {
5482
+ for (let j = 0; j < nFeatures; j++) {
5483
+ weights[j] = (weights[j] ?? 0) * (1 - effectiveLR) + effectiveLR * this.C * yi * (xi[j] ?? 0);
5484
+ }
5485
+ bias += effectiveLR * this.C * yi;
5486
+ } else {
5487
+ for (let j = 0; j < nFeatures; j++) {
5488
+ weights[j] = (weights[j] ?? 0) * (1 - effectiveLR);
5489
+ }
5490
+ }
5491
+ }
5492
+ if (maxViolation < this.tol) break;
5493
+ }
5494
+ return { weights, bias };
5495
+ }
5496
+ /**
5497
+ * Compute decision value for a single binary classifier.
5498
+ */
5499
+ decisionBinary(x, classIdx) {
5500
+ const w = this.weightsPerClass[classIdx];
5501
+ let d = this.biasPerClass[classIdx] ?? 0;
5502
+ if (w) {
5503
+ for (let j = 0; j < w.length; j++) {
5504
+ d += (w[j] ?? 0) * (x[j] ?? 0);
5505
+ }
5506
+ }
5507
+ return d;
5508
+ }
5371
5509
  /**
5372
5510
  * Fit the SVM classifier using sub-gradient descent.
5373
5511
  *
5374
- * Uses a simplified hinge loss optimization with L2 regularization.
5375
- * Objective: minimize (1/2)||w||² + C * Σmax(0, 1 - y_i(w · x_i + b))
5512
+ * Supports both binary and multiclass classification (via OvR).
5376
5513
  *
5377
5514
  * @param X - Training data of shape (n_samples, n_features)
5378
- * @param y - Target labels of shape (n_samples,). Must contain exactly 2 classes.
5515
+ * @param y - Target labels of shape (n_samples,). Must contain at least 2 classes.
5379
5516
  * @returns this - The fitted estimator
5380
5517
  * @throws {ShapeError} If X is not 2D or y is not 1D
5381
5518
  * @throws {ShapeError} If X and y have different number of samples
5382
5519
  * @throws {DataValidationError} If X or y contain NaN/Inf values
5383
- * @throws {InvalidParameterError} If y does not contain exactly 2 classes
5520
+ * @throws {InvalidParameterError} If y does not contain at least 2 classes
5384
5521
  */
5385
5522
  fit(X, y) {
5386
5523
  validateFitInputs(X, y);
@@ -5398,45 +5535,26 @@ var LinearSVC = class {
5398
5535
  yData.push(Number(y.data[y.offset + i]));
5399
5536
  }
5400
5537
  this.classLabels = [...new Set(yData)].sort((a, b) => a - b);
5401
- if (this.classLabels.length !== 2) {
5538
+ if (this.classLabels.length < 2) {
5402
5539
  throw new InvalidParameterError(
5403
- "LinearSVC requires exactly 2 classes for binary classification",
5540
+ "LinearSVC requires at least 2 classes",
5404
5541
  "y",
5405
5542
  this.classLabels.length
5406
5543
  );
5407
5544
  }
5408
- const yMapped = yData.map((label) => label === this.classLabels[0] ? -1 : 1);
5409
- this.weights = new Array(nFeatures).fill(0);
5410
- this.bias = 0;
5411
- const learningRate = 0.01;
5412
- for (let iter = 0; iter < this.maxIter; iter++) {
5413
- let maxViolation = 0;
5414
- for (let i = 0; i < nSamples; i++) {
5415
- const xi = XData[i];
5416
- const yi = yMapped[i];
5417
- if (xi === void 0 || yi === void 0) continue;
5418
- let decision = this.bias;
5419
- for (let j = 0; j < nFeatures; j++) {
5420
- decision += (this.weights[j] ?? 0) * (xi[j] ?? 0);
5421
- }
5422
- const margin = yi * decision;
5423
- if (margin < 1) {
5424
- maxViolation = Math.max(maxViolation, 1 - margin);
5425
- }
5426
- const effectiveLR = Math.min(learningRate, 1 / (this.C * 10));
5427
- if (margin < 1) {
5428
- for (let j = 0; j < nFeatures; j++) {
5429
- this.weights[j] = (this.weights[j] ?? 0) * (1 - effectiveLR) + effectiveLR * this.C * yi * (xi[j] ?? 0);
5430
- }
5431
- this.bias += effectiveLR * this.C * yi;
5432
- } else {
5433
- for (let j = 0; j < nFeatures; j++) {
5434
- this.weights[j] = (this.weights[j] ?? 0) * (1 - effectiveLR);
5435
- }
5436
- }
5437
- }
5438
- if (maxViolation < this.tol) {
5439
- break;
5545
+ this.weightsPerClass = [];
5546
+ this.biasPerClass = [];
5547
+ if (this.classLabels.length === 2) {
5548
+ const yMapped = yData.map((label) => label === this.classLabels[0] ? -1 : 1);
5549
+ const { weights, bias } = this.fitBinary(XData, yMapped, nSamples, nFeatures);
5550
+ this.weightsPerClass.push(weights);
5551
+ this.biasPerClass.push(bias);
5552
+ } else {
5553
+ for (const classLabel of this.classLabels) {
5554
+ const yMapped = yData.map((label) => label === classLabel ? 1 : -1);
5555
+ const { weights, bias } = this.fitBinary(XData, yMapped, nSamples, nFeatures);
5556
+ this.weightsPerClass.push(weights);
5557
+ this.biasPerClass.push(bias);
5440
5558
  }
5441
5559
  }
5442
5560
  this.fitted = true;
@@ -5460,12 +5578,25 @@ var LinearSVC = class {
5460
5578
  const nFeatures = X.shape[1] ?? 0;
5461
5579
  const predictions = [];
5462
5580
  for (let i = 0; i < nSamples; i++) {
5463
- let decision = this.bias;
5581
+ const xi = [];
5464
5582
  for (let j = 0; j < nFeatures; j++) {
5465
- decision += (this.weights[j] ?? 0) * Number(X.data[X.offset + i * nFeatures + j]);
5583
+ xi.push(Number(X.data[X.offset + i * nFeatures + j]));
5584
+ }
5585
+ if (this.classLabels.length === 2) {
5586
+ const d = this.decisionBinary(xi, 0);
5587
+ predictions.push(d >= 0 ? this.classLabels[1] ?? 0 : this.classLabels[0] ?? 0);
5588
+ } else {
5589
+ let bestClass = 0;
5590
+ let bestScore = -Infinity;
5591
+ for (let c = 0; c < this.classLabels.length; c++) {
5592
+ const score = this.decisionBinary(xi, c);
5593
+ if (score > bestScore) {
5594
+ bestScore = score;
5595
+ bestClass = c;
5596
+ }
5597
+ }
5598
+ predictions.push(this.classLabels[bestClass] ?? 0);
5466
5599
  }
5467
- const predictedClass = decision >= 0 ? this.classLabels[1] : this.classLabels[0];
5468
- predictions.push(predictedClass ?? 0);
5469
5600
  }
5470
5601
  return tensor(predictions, { dtype: "int32" });
5471
5602
  }
@@ -5485,14 +5616,25 @@ var LinearSVC = class {
5485
5616
  validatePredictInputs(X, this.nFeatures ?? 0, "LinearSVC");
5486
5617
  const nSamples = X.shape[0] ?? 0;
5487
5618
  const nFeatures = X.shape[1] ?? 0;
5619
+ const nClasses = this.classLabels.length;
5488
5620
  const proba = [];
5489
5621
  for (let i = 0; i < nSamples; i++) {
5490
- let decision = this.bias;
5622
+ const xi = [];
5491
5623
  for (let j = 0; j < nFeatures; j++) {
5492
- decision += (this.weights[j] ?? 0) * Number(X.data[X.offset + i * nFeatures + j]);
5624
+ xi.push(Number(X.data[X.offset + i * nFeatures + j]));
5625
+ }
5626
+ if (nClasses === 2) {
5627
+ const d = this.decisionBinary(xi, 0);
5628
+ const p1 = 1 / (1 + Math.exp(-d));
5629
+ proba.push([1 - p1, p1]);
5630
+ } else {
5631
+ const sigScores = [];
5632
+ for (let c = 0; c < nClasses; c++) {
5633
+ sigScores.push(1 / (1 + Math.exp(-this.decisionBinary(xi, c))));
5634
+ }
5635
+ const total = sigScores.reduce((s, v) => s + v, 0) || 1;
5636
+ proba.push(sigScores.map((v) => v / total));
5493
5637
  }
5494
- const p1 = 1 / (1 + Math.exp(-decision));
5495
- proba.push([1 - p1, p1]);
5496
5638
  }
5497
5639
  return tensor(proba);
5498
5640
  }
@@ -5541,19 +5683,19 @@ var LinearSVC = class {
5541
5683
  if (!this.fitted) {
5542
5684
  throw new NotFittedError("LinearSVC must be fitted to access coefficients");
5543
5685
  }
5544
- return tensor([this.weights]);
5686
+ return tensor(this.weightsPerClass);
5545
5687
  }
5546
5688
  /**
5547
- * Get the bias term.
5689
+ * Get the bias terms.
5548
5690
  *
5549
- * @returns Bias value
5691
+ * @returns Bias values as tensor
5550
5692
  * @throws {NotFittedError} If the model has not been fitted
5551
5693
  */
5552
5694
  get intercept() {
5553
5695
  if (!this.fitted) {
5554
5696
  throw new NotFittedError("LinearSVC must be fitted to access intercept");
5555
5697
  }
5556
- return this.bias;
5698
+ return tensor(this.biasPerClass);
5557
5699
  }
5558
5700
  /**
5559
5701
  * Get hyperparameters for this estimator.
@@ -6375,5 +6517,5 @@ var RandomForestRegressor = class {
6375
6517
  };
6376
6518
 
6377
6519
  export { DBSCAN, DecisionTreeClassifier, DecisionTreeRegressor, GaussianNB, GradientBoostingClassifier, GradientBoostingRegressor, KMeans, KNeighborsClassifier, KNeighborsRegressor, Lasso, LinearRegression, LinearSVC, LinearSVR, LogisticRegression, PCA, RandomForestClassifier, RandomForestRegressor, Ridge, TSNE, ml_exports };
6378
- //# sourceMappingURL=chunk-MLBMYKCG.js.map
6379
- //# sourceMappingURL=chunk-MLBMYKCG.js.map
6520
+ //# sourceMappingURL=chunk-H3JR7SV2.js.map
6521
+ //# sourceMappingURL=chunk-H3JR7SV2.js.map