@dniskav/neuron 0.3.0 → 0.3.1
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.
- package/README.md +88 -1
- package/dist/index.d.mts +118 -1
- package/dist/index.d.ts +118 -1
- package/dist/index.js +755 -0
- package/dist/index.mjs +749 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -4736,6 +4736,749 @@ var TCN = class {
|
|
|
4736
4736
|
}
|
|
4737
4737
|
};
|
|
4738
4738
|
|
|
4739
|
+
// src/Word2Vec.ts
|
|
4740
|
+
var Word2Vec = class {
|
|
4741
|
+
constructor(embeddingDim = 50, options = {}) {
|
|
4742
|
+
this._trained = false;
|
|
4743
|
+
this.embeddingDim = embeddingDim;
|
|
4744
|
+
this._windowSize = options.windowSize ?? 2;
|
|
4745
|
+
this._model = options.model ?? "skipgram";
|
|
4746
|
+
this._minCount = options.minCount ?? 1;
|
|
4747
|
+
this.embeddings = [];
|
|
4748
|
+
this._W2 = [];
|
|
4749
|
+
this.vocab = /* @__PURE__ */ new Map();
|
|
4750
|
+
this._indexToWord = [];
|
|
4751
|
+
this.vocabSize = 0;
|
|
4752
|
+
}
|
|
4753
|
+
// ── buildVocab ─────────────────────────────────────────────────────────────
|
|
4754
|
+
// Scans the corpus, counts word frequencies, discards rare words (< minCount),
|
|
4755
|
+
// and assigns each remaining word a unique integer index.
|
|
4756
|
+
buildVocab(sentences) {
|
|
4757
|
+
const freq = /* @__PURE__ */ new Map();
|
|
4758
|
+
for (const sentence of sentences) {
|
|
4759
|
+
for (const word of sentence) {
|
|
4760
|
+
freq.set(word, (freq.get(word) ?? 0) + 1);
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
4763
|
+
this.vocab = /* @__PURE__ */ new Map();
|
|
4764
|
+
this._indexToWord = [];
|
|
4765
|
+
for (const [word, count] of freq) {
|
|
4766
|
+
if (count >= this._minCount) {
|
|
4767
|
+
const idx = this._indexToWord.length;
|
|
4768
|
+
this.vocab.set(word, idx);
|
|
4769
|
+
this._indexToWord.push(word);
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
this.vocabSize = this._indexToWord.length;
|
|
4773
|
+
if (this.vocabSize === 0) {
|
|
4774
|
+
throw new Error("Word2Vec.buildVocab: vocabulary is empty after applying minCount filter");
|
|
4775
|
+
}
|
|
4776
|
+
const scale1 = Math.sqrt(1 / this.embeddingDim);
|
|
4777
|
+
const scale2 = Math.sqrt(1 / this.vocabSize);
|
|
4778
|
+
this.embeddings = Array.from(
|
|
4779
|
+
{ length: this.vocabSize },
|
|
4780
|
+
() => Array.from({ length: this.embeddingDim }, () => (Math.random() * 2 - 1) * scale1)
|
|
4781
|
+
);
|
|
4782
|
+
this._W2 = Array.from(
|
|
4783
|
+
{ length: this.embeddingDim },
|
|
4784
|
+
() => Array.from({ length: this.vocabSize }, () => (Math.random() * 2 - 1) * scale2)
|
|
4785
|
+
);
|
|
4786
|
+
this._trained = false;
|
|
4787
|
+
}
|
|
4788
|
+
// ── tokenize ───────────────────────────────────────────────────────────────
|
|
4789
|
+
// Simple tokenizer: lowercase, strip punctuation, split on whitespace.
|
|
4790
|
+
// Returns an array of tokens suitable for buildVocab / train.
|
|
4791
|
+
static tokenize(text) {
|
|
4792
|
+
return text.toLowerCase().replace(/[^a-z0-9\s'-]/g, " ").split(/\s+/).filter((t) => t.length > 0);
|
|
4793
|
+
}
|
|
4794
|
+
// ── train ──────────────────────────────────────────────────────────────────
|
|
4795
|
+
// Runs SGD over all (center, context) pairs in the corpus for `epochs` passes.
|
|
4796
|
+
// Returns the average cross-entropy loss per epoch.
|
|
4797
|
+
//
|
|
4798
|
+
// Note: uses full-vocabulary softmax (not negative sampling) for educational
|
|
4799
|
+
// clarity. This is O(vocabSize) per step — for large vocabularies you would
|
|
4800
|
+
// normally switch to negative sampling or hierarchical softmax.
|
|
4801
|
+
train(sentences, lr = 0.025, epochs = 5) {
|
|
4802
|
+
if (this.vocabSize === 0) this.buildVocab(sentences);
|
|
4803
|
+
const lossHistory = [];
|
|
4804
|
+
for (let epoch = 0; epoch < epochs; epoch++) {
|
|
4805
|
+
let totalLoss = 0;
|
|
4806
|
+
let nPairs = 0;
|
|
4807
|
+
for (const sentence of sentences) {
|
|
4808
|
+
const indices = sentence.map((w) => this.vocab.get(w)).filter((idx) => idx !== void 0);
|
|
4809
|
+
for (let t = 0; t < indices.length; t++) {
|
|
4810
|
+
const centerIdx = indices[t];
|
|
4811
|
+
const contextIndices = [];
|
|
4812
|
+
for (let offset = -this._windowSize; offset <= this._windowSize; offset++) {
|
|
4813
|
+
if (offset === 0) continue;
|
|
4814
|
+
const pos = t + offset;
|
|
4815
|
+
if (pos >= 0 && pos < indices.length) {
|
|
4816
|
+
contextIndices.push(indices[pos]);
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
if (contextIndices.length === 0) continue;
|
|
4820
|
+
if (this._model === "skipgram") {
|
|
4821
|
+
for (const contextIdx of contextIndices) {
|
|
4822
|
+
totalLoss += this._skipgramStep(centerIdx, contextIdx, lr);
|
|
4823
|
+
nPairs++;
|
|
4824
|
+
}
|
|
4825
|
+
} else {
|
|
4826
|
+
totalLoss += this._cbowStep(centerIdx, contextIndices, lr);
|
|
4827
|
+
nPairs++;
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
lossHistory.push(nPairs > 0 ? totalLoss / nPairs : 0);
|
|
4832
|
+
}
|
|
4833
|
+
this._trained = true;
|
|
4834
|
+
return lossHistory;
|
|
4835
|
+
}
|
|
4836
|
+
// ── getEmbedding ───────────────────────────────────────────────────────────
|
|
4837
|
+
// Returns the learned embedding vector for a word. Throws if unknown.
|
|
4838
|
+
getEmbedding(word) {
|
|
4839
|
+
const idx = this.vocab.get(word);
|
|
4840
|
+
if (idx === void 0) throw new Error(`Word2Vec: unknown word "${word}"`);
|
|
4841
|
+
return this.embeddings[idx];
|
|
4842
|
+
}
|
|
4843
|
+
// ── similarity ─────────────────────────────────────────────────────────────
|
|
4844
|
+
// Cosine similarity between two words.
|
|
4845
|
+
// cos(v1, v2) = (v1 · v2) / (‖v1‖ · ‖v2‖)
|
|
4846
|
+
// Returns a value in [-1, 1]. Higher → more similar context usage.
|
|
4847
|
+
similarity(word1, word2) {
|
|
4848
|
+
const v1 = this.getEmbedding(word1);
|
|
4849
|
+
const v2 = this.getEmbedding(word2);
|
|
4850
|
+
return this._cosine(v1, v2);
|
|
4851
|
+
}
|
|
4852
|
+
// ── mostSimilar ────────────────────────────────────────────────────────────
|
|
4853
|
+
// Returns the topK words (excluding `word` itself) sorted by cosine similarity.
|
|
4854
|
+
mostSimilar(word, topK = 10) {
|
|
4855
|
+
const v = this.getEmbedding(word);
|
|
4856
|
+
return this._nearestByVector(v, topK, /* @__PURE__ */ new Set([word]));
|
|
4857
|
+
}
|
|
4858
|
+
// ── analogy ───────────────────────────────────────────────────────────────
|
|
4859
|
+
// Vector arithmetic analogy: positive1 - negative + positive2 ≈ result
|
|
4860
|
+
//
|
|
4861
|
+
// getAnalogy('king', 'man', 'woman') finds the word closest to
|
|
4862
|
+
// vec('king') - vec('man') + vec('woman') ≈ vec('queen')
|
|
4863
|
+
//
|
|
4864
|
+
// The result is excluded from the input words so they don't pollute the top-K.
|
|
4865
|
+
analogy(positive1, negative, positive2, topK = 5) {
|
|
4866
|
+
const vPos1 = this.getEmbedding(positive1);
|
|
4867
|
+
const vNeg = this.getEmbedding(negative);
|
|
4868
|
+
const vPos2 = this.getEmbedding(positive2);
|
|
4869
|
+
const target = vPos1.map((v, i) => v - vNeg[i] + vPos2[i]);
|
|
4870
|
+
const exclude = /* @__PURE__ */ new Set([positive1, negative, positive2]);
|
|
4871
|
+
return this._nearestByVector(target, topK, exclude);
|
|
4872
|
+
}
|
|
4873
|
+
// ── Private: skip-gram step ───────────────────────────────────────────────
|
|
4874
|
+
// Forward + backward for one (center, target) pair.
|
|
4875
|
+
// Returns the cross-entropy loss for this pair.
|
|
4876
|
+
_skipgramStep(centerIdx, targetIdx, lr) {
|
|
4877
|
+
const h = this.embeddings[centerIdx];
|
|
4878
|
+
const scores = this._hiddenToScores(h);
|
|
4879
|
+
const probs = _softmax(scores);
|
|
4880
|
+
const loss = -Math.log(probs[targetIdx] + 1e-12);
|
|
4881
|
+
const err = probs.map((p, j) => j === targetIdx ? p - 1 : p);
|
|
4882
|
+
const dh = new Array(this.embeddingDim).fill(0);
|
|
4883
|
+
for (let d = 0; d < this.embeddingDim; d++) {
|
|
4884
|
+
for (let j = 0; j < this.vocabSize; j++) {
|
|
4885
|
+
this._W2[d][j] -= lr * h[d] * err[j];
|
|
4886
|
+
dh[d] += this._W2[d][j] * err[j];
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
for (let d = 0; d < this.embeddingDim; d++) {
|
|
4890
|
+
this.embeddings[centerIdx][d] -= lr * dh[d];
|
|
4891
|
+
}
|
|
4892
|
+
return loss;
|
|
4893
|
+
}
|
|
4894
|
+
// ── Private: CBOW step ────────────────────────────────────────────────────
|
|
4895
|
+
// Forward + backward for one (contextIndices → centerIdx) pair.
|
|
4896
|
+
// h is the mean of all context embeddings. The gradient is distributed
|
|
4897
|
+
// equally back to each context word's embedding row.
|
|
4898
|
+
_cbowStep(centerIdx, contextIndices, lr) {
|
|
4899
|
+
const k = contextIndices.length;
|
|
4900
|
+
const h = new Array(this.embeddingDim).fill(0);
|
|
4901
|
+
for (const ci of contextIndices) {
|
|
4902
|
+
for (let d = 0; d < this.embeddingDim; d++) {
|
|
4903
|
+
h[d] += this.embeddings[ci][d];
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
for (let d = 0; d < this.embeddingDim; d++) h[d] /= k;
|
|
4907
|
+
const scores = this._hiddenToScores(h);
|
|
4908
|
+
const probs = _softmax(scores);
|
|
4909
|
+
const loss = -Math.log(probs[centerIdx] + 1e-12);
|
|
4910
|
+
const err = probs.map((p, j) => j === centerIdx ? p - 1 : p);
|
|
4911
|
+
const dh = new Array(this.embeddingDim).fill(0);
|
|
4912
|
+
for (let d = 0; d < this.embeddingDim; d++) {
|
|
4913
|
+
for (let j = 0; j < this.vocabSize; j++) {
|
|
4914
|
+
this._W2[d][j] -= lr * h[d] * err[j];
|
|
4915
|
+
dh[d] += this._W2[d][j] * err[j];
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
for (const ci of contextIndices) {
|
|
4919
|
+
for (let d = 0; d < this.embeddingDim; d++) {
|
|
4920
|
+
this.embeddings[ci][d] -= lr * dh[d] / k;
|
|
4921
|
+
}
|
|
4922
|
+
}
|
|
4923
|
+
return loss;
|
|
4924
|
+
}
|
|
4925
|
+
// Computes scores = h · W2 → [vocabSize]
|
|
4926
|
+
_hiddenToScores(h) {
|
|
4927
|
+
const scores = new Array(this.vocabSize).fill(0);
|
|
4928
|
+
for (let d = 0; d < this.embeddingDim; d++) {
|
|
4929
|
+
for (let j = 0; j < this.vocabSize; j++) {
|
|
4930
|
+
scores[j] += h[d] * this._W2[d][j];
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4933
|
+
return scores;
|
|
4934
|
+
}
|
|
4935
|
+
// Returns topK words (from all embeddings) sorted by cosine similarity to v,
|
|
4936
|
+
// skipping any word in the exclude set.
|
|
4937
|
+
_nearestByVector(v, topK, exclude) {
|
|
4938
|
+
const results = [];
|
|
4939
|
+
for (let i = 0; i < this.vocabSize; i++) {
|
|
4940
|
+
const w = this._indexToWord[i];
|
|
4941
|
+
if (exclude.has(w)) continue;
|
|
4942
|
+
results.push({ word: w, score: this._cosine(v, this.embeddings[i]) });
|
|
4943
|
+
}
|
|
4944
|
+
results.sort((a, b) => b.score - a.score);
|
|
4945
|
+
return results.slice(0, topK);
|
|
4946
|
+
}
|
|
4947
|
+
// Cosine similarity: (v1 · v2) / (‖v1‖ · ‖v2‖)
|
|
4948
|
+
_cosine(v1, v2) {
|
|
4949
|
+
let dot = 0, n1 = 0, n2 = 0;
|
|
4950
|
+
for (let i = 0; i < v1.length; i++) {
|
|
4951
|
+
dot += v1[i] * v2[i];
|
|
4952
|
+
n1 += v1[i] * v1[i];
|
|
4953
|
+
n2 += v2[i] * v2[i];
|
|
4954
|
+
}
|
|
4955
|
+
const denom = Math.sqrt(n1) * Math.sqrt(n2);
|
|
4956
|
+
return denom < 1e-12 ? 0 : dot / denom;
|
|
4957
|
+
}
|
|
4958
|
+
};
|
|
4959
|
+
function _softmax(scores) {
|
|
4960
|
+
const max = Math.max(...scores);
|
|
4961
|
+
const exps = scores.map((s) => Math.exp(s - max));
|
|
4962
|
+
const sum = exps.reduce((a, b) => a + b, 0);
|
|
4963
|
+
return exps.map((e) => e / sum);
|
|
4964
|
+
}
|
|
4965
|
+
|
|
4966
|
+
// src/TSNE.ts
|
|
4967
|
+
var TSNE = class {
|
|
4968
|
+
constructor(options = {}) {
|
|
4969
|
+
// KL divergence tracked during the last fit() call.
|
|
4970
|
+
this._klDivergence = 0;
|
|
4971
|
+
// P matrix stored for kl() reporting.
|
|
4972
|
+
this._P = [];
|
|
4973
|
+
this._nComponents = options.nComponents ?? 2;
|
|
4974
|
+
this._perplexity = options.perplexity ?? 30;
|
|
4975
|
+
this._lr = options.lr ?? 200;
|
|
4976
|
+
this._nIter = options.nIter ?? 1e3;
|
|
4977
|
+
this._seed = options.seed;
|
|
4978
|
+
this.embedding = [];
|
|
4979
|
+
}
|
|
4980
|
+
// ── fit ────────────────────────────────────────────────────────────────────
|
|
4981
|
+
// Runs the full t-SNE algorithm on X (shape [n][d]).
|
|
4982
|
+
// Stores the result in this.embedding ([n][nComponents]).
|
|
4983
|
+
fit(X) {
|
|
4984
|
+
const n = X.length;
|
|
4985
|
+
if (n < 2) throw new Error("TSNE.fit: need at least 2 data points");
|
|
4986
|
+
if (this._perplexity >= n) {
|
|
4987
|
+
throw new Error(
|
|
4988
|
+
`TSNE.fit: perplexity (${this._perplexity}) must be less than n (${n})`
|
|
4989
|
+
);
|
|
4990
|
+
}
|
|
4991
|
+
const rng = this._seed !== void 0 ? _mulberry32(this._seed) : Math.random;
|
|
4992
|
+
const distSq = _pairwiseDistSq(X, n);
|
|
4993
|
+
const Pcond = this._computePcond(distSq, n);
|
|
4994
|
+
const P = _symmetrize(Pcond, n);
|
|
4995
|
+
this._P = P;
|
|
4996
|
+
let Y = Array.from({ length: n }, () => {
|
|
4997
|
+
return Array.from({ length: this._nComponents }, () => {
|
|
4998
|
+
const u1 = Math.max(rng(), 1e-12);
|
|
4999
|
+
const u2 = rng();
|
|
5000
|
+
const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
5001
|
+
return z * 0.01;
|
|
5002
|
+
});
|
|
5003
|
+
});
|
|
5004
|
+
let Yprev = Y.map((row) => [...row]);
|
|
5005
|
+
const EXAGGERATION_ITERS = 50;
|
|
5006
|
+
const EXAGGERATION_FACTOR = 4;
|
|
5007
|
+
const MOMENTUM_SWITCH = 20;
|
|
5008
|
+
for (let iter = 0; iter < this._nIter; iter++) {
|
|
5009
|
+
const momentum = iter < MOMENTUM_SWITCH ? 0.5 : 0.8;
|
|
5010
|
+
const pScale = iter < EXAGGERATION_ITERS ? EXAGGERATION_FACTOR : 1;
|
|
5011
|
+
const { Q, invDist } = _computeQ(Y, n, this._nComponents);
|
|
5012
|
+
const grad = Array.from(
|
|
5013
|
+
{ length: n },
|
|
5014
|
+
() => new Array(this._nComponents).fill(0)
|
|
5015
|
+
);
|
|
5016
|
+
for (let i = 0; i < n; i++) {
|
|
5017
|
+
for (let j = 0; j < n; j++) {
|
|
5018
|
+
if (i === j) continue;
|
|
5019
|
+
const pq = pScale * P[i][j] - Q[i][j];
|
|
5020
|
+
const c = 4 * pq * invDist[i][j];
|
|
5021
|
+
for (let d = 0; d < this._nComponents; d++) {
|
|
5022
|
+
grad[i][d] += c * (Y[i][d] - Y[j][d]);
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
const Ynext = Array.from(
|
|
5027
|
+
{ length: n },
|
|
5028
|
+
(_, i) => Array.from(
|
|
5029
|
+
{ length: this._nComponents },
|
|
5030
|
+
(_2, d) => Y[i][d] - this._lr * grad[i][d] + momentum * (Y[i][d] - Yprev[i][d])
|
|
5031
|
+
)
|
|
5032
|
+
);
|
|
5033
|
+
Yprev = Y;
|
|
5034
|
+
Y = Ynext;
|
|
5035
|
+
}
|
|
5036
|
+
this.embedding = Y;
|
|
5037
|
+
const { Q: Qfinal } = _computeQ(Y, n, this._nComponents);
|
|
5038
|
+
let kl = 0;
|
|
5039
|
+
for (let i = 0; i < n; i++) {
|
|
5040
|
+
for (let j = 0; j < n; j++) {
|
|
5041
|
+
if (i === j) continue;
|
|
5042
|
+
const p = P[i][j];
|
|
5043
|
+
if (p > 1e-12) {
|
|
5044
|
+
kl += p * Math.log(p / (Qfinal[i][j] + 1e-12));
|
|
5045
|
+
}
|
|
5046
|
+
}
|
|
5047
|
+
}
|
|
5048
|
+
this._klDivergence = kl;
|
|
5049
|
+
}
|
|
5050
|
+
// ── fitTransform ───────────────────────────────────────────────────────────
|
|
5051
|
+
// Convenience: fit() then return this.embedding.
|
|
5052
|
+
fitTransform(X) {
|
|
5053
|
+
this.fit(X);
|
|
5054
|
+
return this.embedding;
|
|
5055
|
+
}
|
|
5056
|
+
// ── kl ─────────────────────────────────────────────────────────────────────
|
|
5057
|
+
// Returns the KL divergence KL(P ‖ Q) from the last fit() call.
|
|
5058
|
+
// Lower is better. Useful for comparing perplexity settings or iteration counts.
|
|
5059
|
+
kl() {
|
|
5060
|
+
return this._klDivergence;
|
|
5061
|
+
}
|
|
5062
|
+
// ── Private: binary search for σi ─────────────────────────────────────────
|
|
5063
|
+
// For each point i, find σi such that the Shannon entropy of P(·|i) equals
|
|
5064
|
+
// log₂(perplexity). We use binary search on σ².
|
|
5065
|
+
_computePcond(distSq, n) {
|
|
5066
|
+
const targetEntropy = Math.log2(this._perplexity);
|
|
5067
|
+
const Pcond = Array.from({ length: n }, () => new Array(n).fill(0));
|
|
5068
|
+
for (let i = 0; i < n; i++) {
|
|
5069
|
+
let sigmaLo = 0;
|
|
5070
|
+
let sigmaHi = 1e10;
|
|
5071
|
+
let sigma2 = 1;
|
|
5072
|
+
for (let attempt = 0; attempt < 50; attempt++) {
|
|
5073
|
+
const dists = distSq[i];
|
|
5074
|
+
let sumExp = 0;
|
|
5075
|
+
const exps = new Array(n).fill(0);
|
|
5076
|
+
for (let j = 0; j < n; j++) {
|
|
5077
|
+
if (j === i) continue;
|
|
5078
|
+
const e = Math.exp(-dists[j] / (2 * sigma2));
|
|
5079
|
+
exps[j] = e;
|
|
5080
|
+
sumExp += e;
|
|
5081
|
+
}
|
|
5082
|
+
if (sumExp < 1e-12) break;
|
|
5083
|
+
let H = 0;
|
|
5084
|
+
for (let j = 0; j < n; j++) {
|
|
5085
|
+
if (j === i) continue;
|
|
5086
|
+
const p = exps[j] / sumExp;
|
|
5087
|
+
Pcond[i][j] = p;
|
|
5088
|
+
if (p > 1e-12) H -= p * Math.log2(p);
|
|
5089
|
+
}
|
|
5090
|
+
const delta = H - targetEntropy;
|
|
5091
|
+
if (Math.abs(delta) < 1e-5) break;
|
|
5092
|
+
if (delta > 0) {
|
|
5093
|
+
sigmaHi = sigma2;
|
|
5094
|
+
sigma2 = (sigmaLo + sigma2) / 2;
|
|
5095
|
+
} else {
|
|
5096
|
+
sigmaLo = sigma2;
|
|
5097
|
+
sigma2 = sigmaHi < 1e9 ? (sigma2 + sigmaHi) / 2 : sigma2 * 2;
|
|
5098
|
+
}
|
|
5099
|
+
}
|
|
5100
|
+
}
|
|
5101
|
+
return Pcond;
|
|
5102
|
+
}
|
|
5103
|
+
};
|
|
5104
|
+
function _pairwiseDistSq(X, n) {
|
|
5105
|
+
const D = Array.from({ length: n }, () => new Array(n).fill(0));
|
|
5106
|
+
for (let i = 0; i < n; i++) {
|
|
5107
|
+
for (let j = i + 1; j < n; j++) {
|
|
5108
|
+
let d = 0;
|
|
5109
|
+
for (let k = 0; k < X[i].length; k++) {
|
|
5110
|
+
const diff = X[i][k] - X[j][k];
|
|
5111
|
+
d += diff * diff;
|
|
5112
|
+
}
|
|
5113
|
+
D[i][j] = d;
|
|
5114
|
+
D[j][i] = d;
|
|
5115
|
+
}
|
|
5116
|
+
}
|
|
5117
|
+
return D;
|
|
5118
|
+
}
|
|
5119
|
+
function _symmetrize(Pcond, n) {
|
|
5120
|
+
const P = Array.from({ length: n }, () => new Array(n).fill(0));
|
|
5121
|
+
for (let i = 0; i < n; i++) {
|
|
5122
|
+
for (let j = 0; j < n; j++) {
|
|
5123
|
+
P[i][j] = (Pcond[i][j] + Pcond[j][i]) / (2 * n);
|
|
5124
|
+
}
|
|
5125
|
+
}
|
|
5126
|
+
return P;
|
|
5127
|
+
}
|
|
5128
|
+
function _computeQ(Y, n, nComponents) {
|
|
5129
|
+
const num = Array.from({ length: n }, () => new Array(n).fill(0));
|
|
5130
|
+
let Z = 0;
|
|
5131
|
+
for (let i = 0; i < n; i++) {
|
|
5132
|
+
for (let j = i + 1; j < n; j++) {
|
|
5133
|
+
let d2 = 0;
|
|
5134
|
+
for (let d = 0; d < nComponents; d++) {
|
|
5135
|
+
const diff = Y[i][d] - Y[j][d];
|
|
5136
|
+
d2 += diff * diff;
|
|
5137
|
+
}
|
|
5138
|
+
const inv = 1 / (1 + d2);
|
|
5139
|
+
num[i][j] = inv;
|
|
5140
|
+
num[j][i] = inv;
|
|
5141
|
+
Z += 2 * inv;
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
if (Z < 1e-12) Z = 1e-12;
|
|
5145
|
+
const Q = Array.from(
|
|
5146
|
+
{ length: n },
|
|
5147
|
+
(_, i) => num[i].map((v) => v / Z)
|
|
5148
|
+
);
|
|
5149
|
+
return { Q, invDist: num };
|
|
5150
|
+
}
|
|
5151
|
+
function _mulberry32(seed) {
|
|
5152
|
+
let s = seed >>> 0;
|
|
5153
|
+
return function() {
|
|
5154
|
+
s = s + 1831565813 >>> 0;
|
|
5155
|
+
let z = s;
|
|
5156
|
+
z = Math.imul(z ^ z >>> 15, z | 1);
|
|
5157
|
+
z ^= z + Math.imul(z ^ z >>> 7, z | 61);
|
|
5158
|
+
z = (z ^ z >>> 14) >>> 0;
|
|
5159
|
+
return z / 4294967296;
|
|
5160
|
+
};
|
|
5161
|
+
}
|
|
5162
|
+
|
|
5163
|
+
// src/PositionalEncoding.ts
|
|
5164
|
+
var PositionalEncoding = class _PositionalEncoding {
|
|
5165
|
+
// Compute the full PE vector for one token at position `pos`.
|
|
5166
|
+
// Returns an array of length `dModel`.
|
|
5167
|
+
//
|
|
5168
|
+
// Each pair of dimensions (2i, 2i+1) shares the same frequency 1/10000^(2i/dModel)
|
|
5169
|
+
// but is 90° out of phase (sin vs cos), which ensures no two positions produce
|
|
5170
|
+
// the identical vector.
|
|
5171
|
+
static encode(pos, dModel) {
|
|
5172
|
+
const pe = new Array(dModel);
|
|
5173
|
+
for (let i = 0; i < Math.floor(dModel / 2); i++) {
|
|
5174
|
+
const freq = Math.pow(1e4, 2 * i / dModel);
|
|
5175
|
+
pe[2 * i] = Math.sin(pos / freq);
|
|
5176
|
+
pe[2 * i + 1] = Math.cos(pos / freq);
|
|
5177
|
+
}
|
|
5178
|
+
if (dModel % 2 !== 0) {
|
|
5179
|
+
const i = Math.floor(dModel / 2);
|
|
5180
|
+
const freq = Math.pow(1e4, 2 * i / dModel);
|
|
5181
|
+
pe[dModel - 1] = Math.sin(pos / freq);
|
|
5182
|
+
}
|
|
5183
|
+
return pe;
|
|
5184
|
+
}
|
|
5185
|
+
// Build the full positional encoding matrix for a sequence of `seqLen` tokens.
|
|
5186
|
+
// Returns shape [seqLen][dModel].
|
|
5187
|
+
//
|
|
5188
|
+
// In practice this matrix is computed once and cached — it doesn't change
|
|
5189
|
+
// across examples, batches, or epochs.
|
|
5190
|
+
static encodeSequence(seqLen, dModel) {
|
|
5191
|
+
return Array.from(
|
|
5192
|
+
{ length: seqLen },
|
|
5193
|
+
(_, pos) => _PositionalEncoding.encode(pos, dModel)
|
|
5194
|
+
);
|
|
5195
|
+
}
|
|
5196
|
+
// Add positional encoding to an existing embedding matrix (in-place on a copy).
|
|
5197
|
+
//
|
|
5198
|
+
// `embeddings` shape: [seqLen][dModel].
|
|
5199
|
+
// `seqLen` is optional; defaults to embeddings.length.
|
|
5200
|
+
//
|
|
5201
|
+
// The sum e = token_embedding + PE is what actually enters the first
|
|
5202
|
+
// Transformer layer. Summing (rather than concatenating) keeps the model
|
|
5203
|
+
// dimension fixed and lets the network distribute its capacity freely —
|
|
5204
|
+
// it can choose how much of each dimension to allocate to content vs. position.
|
|
5205
|
+
static apply(embeddings, seqLen) {
|
|
5206
|
+
const len = seqLen ?? embeddings.length;
|
|
5207
|
+
const dModel = embeddings[0].length;
|
|
5208
|
+
const pe = _PositionalEncoding.encodeSequence(len, dModel);
|
|
5209
|
+
return embeddings.map(
|
|
5210
|
+
(emb, pos) => emb.map((val, d) => val + pe[pos][d])
|
|
5211
|
+
);
|
|
5212
|
+
}
|
|
5213
|
+
};
|
|
5214
|
+
var LearnedPositionalEncoding = class {
|
|
5215
|
+
constructor(maxSeqLen, dModel) {
|
|
5216
|
+
this.maxSeqLen = maxSeqLen;
|
|
5217
|
+
this.dModel = dModel;
|
|
5218
|
+
const limit = Math.sqrt(1 / dModel);
|
|
5219
|
+
this.weights = Array.from(
|
|
5220
|
+
{ length: maxSeqLen },
|
|
5221
|
+
() => Array.from({ length: dModel }, () => (Math.random() * 2 - 1) * limit)
|
|
5222
|
+
);
|
|
5223
|
+
}
|
|
5224
|
+
// Return the learned encoding for one position.
|
|
5225
|
+
// Returns a copy so callers cannot accidentally mutate the weight table.
|
|
5226
|
+
getEncoding(pos) {
|
|
5227
|
+
if (pos >= this.maxSeqLen) {
|
|
5228
|
+
throw new Error(
|
|
5229
|
+
`Position ${pos} exceeds maxSeqLen=${this.maxSeqLen}. Learned encodings cannot generalize beyond their training length.`
|
|
5230
|
+
);
|
|
5231
|
+
}
|
|
5232
|
+
return [...this.weights[pos]];
|
|
5233
|
+
}
|
|
5234
|
+
// Add learned positional encodings to `embeddings` (returns a new matrix).
|
|
5235
|
+
// Shape: [seqLen][dModel] → [seqLen][dModel].
|
|
5236
|
+
apply(embeddings, seqLen) {
|
|
5237
|
+
const len = seqLen ?? embeddings.length;
|
|
5238
|
+
if (len > this.maxSeqLen) {
|
|
5239
|
+
throw new Error(
|
|
5240
|
+
`Sequence length ${len} exceeds maxSeqLen=${this.maxSeqLen}.`
|
|
5241
|
+
);
|
|
5242
|
+
}
|
|
5243
|
+
return embeddings.map(
|
|
5244
|
+
(emb, pos) => emb.map((val, d) => val + this.weights[pos][d])
|
|
5245
|
+
);
|
|
5246
|
+
}
|
|
5247
|
+
// Apply gradient update to position encoding weights.
|
|
5248
|
+
//
|
|
5249
|
+
// `dWeights` has the same shape as `weights`: [maxSeqLen][dModel].
|
|
5250
|
+
// Each entry is dL/dW_pos[pos][d] — the loss gradient w.r.t. that weight.
|
|
5251
|
+
//
|
|
5252
|
+
// Simple SGD is used here (matching EmbeddingMatrix in MatMul.ts):
|
|
5253
|
+
// position embeddings are updated every step for all positions in the batch,
|
|
5254
|
+
// so the sparse-update problem of token embeddings doesn't apply.
|
|
5255
|
+
update(dWeights, lr) {
|
|
5256
|
+
for (let pos = 0; pos < this.maxSeqLen; pos++) {
|
|
5257
|
+
for (let d = 0; d < this.dModel; d++) {
|
|
5258
|
+
this.weights[pos][d] += lr * dWeights[pos][d];
|
|
5259
|
+
}
|
|
5260
|
+
}
|
|
5261
|
+
}
|
|
5262
|
+
};
|
|
5263
|
+
|
|
5264
|
+
// src/ContrastiveLearning.ts
|
|
5265
|
+
var Augmenter = class _Augmenter {
|
|
5266
|
+
// Add zero-mean Gaussian noise with standard deviation `sigma`.
|
|
5267
|
+
//
|
|
5268
|
+
// Uses the Box-Muller transform to produce normally distributed noise from
|
|
5269
|
+
// two uniform random variables:
|
|
5270
|
+
// z = √(-2·ln(u₁)) · cos(2π·u₂) where u₁, u₂ ~ Uniform(0, 1)
|
|
5271
|
+
//
|
|
5272
|
+
// This keeps us dependency-free while yielding proper Gaussian samples.
|
|
5273
|
+
static addNoise(x, sigma = 0.05) {
|
|
5274
|
+
return x.map((v) => {
|
|
5275
|
+
const u1 = Math.max(1e-10, Math.random());
|
|
5276
|
+
const u2 = Math.random();
|
|
5277
|
+
const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
5278
|
+
return v + sigma * z;
|
|
5279
|
+
});
|
|
5280
|
+
}
|
|
5281
|
+
// Randomly zero out features with probability `rate`.
|
|
5282
|
+
//
|
|
5283
|
+
// Analogous to masking in BERT or random crops in vision contrastive learning.
|
|
5284
|
+
// The encoder must learn representations that are robust to missing features —
|
|
5285
|
+
// it cannot simply memorize individual dimensions.
|
|
5286
|
+
static dropoutFeatures(x, rate = 0.1) {
|
|
5287
|
+
return x.map((v) => Math.random() < rate ? 0 : v);
|
|
5288
|
+
}
|
|
5289
|
+
// Apply both noise and feature dropout in sequence.
|
|
5290
|
+
//
|
|
5291
|
+
// Combining augmentations is standard in SimCLR — stronger augmentations
|
|
5292
|
+
// force the encoder to learn more robust, abstract representations.
|
|
5293
|
+
static augment(x, noiseStd = 0.05, dropRate = 0.1) {
|
|
5294
|
+
return _Augmenter.dropoutFeatures(_Augmenter.addNoise(x, noiseStd), dropRate);
|
|
5295
|
+
}
|
|
5296
|
+
// Generate a positive pair: [original, augmented_copy].
|
|
5297
|
+
//
|
|
5298
|
+
// These two views are used as the (i, j) positive pair in NT-Xent.
|
|
5299
|
+
// Everything else in the batch acts as a negative.
|
|
5300
|
+
static makePair(x) {
|
|
5301
|
+
return [x, _Augmenter.augment(x)];
|
|
5302
|
+
}
|
|
5303
|
+
};
|
|
5304
|
+
var ContrastiveLearning = class _ContrastiveLearning {
|
|
5305
|
+
// encoderHidden: hidden layer sizes for the encoder (not counting input/output).
|
|
5306
|
+
// e.g. inputSize=64, encoderHidden=[256, 128] → NetworkN([64, 256, 128])
|
|
5307
|
+
// The encoder output dimension is encoderHidden[last].
|
|
5308
|
+
//
|
|
5309
|
+
// projectionDim: dimension of the projection head output (the z space).
|
|
5310
|
+
// e.g. 64. Typically smaller than the encoder's output.
|
|
5311
|
+
//
|
|
5312
|
+
// The encoder uses ReLU activations throughout — empirically stronger than
|
|
5313
|
+
// sigmoid for representation learning because it doesn't saturate.
|
|
5314
|
+
constructor(inputSize, encoderHidden, projectionDim, options = {}) {
|
|
5315
|
+
if (encoderHidden.length === 0) {
|
|
5316
|
+
throw new Error("encoderHidden must have at least one element.");
|
|
5317
|
+
}
|
|
5318
|
+
this.temperature = options.temperature ?? 0.5;
|
|
5319
|
+
const encoderStructure = [inputSize, ...encoderHidden];
|
|
5320
|
+
const encoderActivations = encoderHidden.map(() => relu);
|
|
5321
|
+
this.encoder = new NetworkN(encoderStructure, {
|
|
5322
|
+
activations: encoderActivations,
|
|
5323
|
+
...options.encoderOptions
|
|
5324
|
+
});
|
|
5325
|
+
const encoderOut = encoderHidden[encoderHidden.length - 1];
|
|
5326
|
+
const projHidden = Math.max(projectionDim, Math.floor(encoderOut / 2));
|
|
5327
|
+
this.projectionHead = new NetworkN(
|
|
5328
|
+
[encoderOut, projHidden, projectionDim],
|
|
5329
|
+
{ activations: [relu, relu] }
|
|
5330
|
+
);
|
|
5331
|
+
}
|
|
5332
|
+
// ── Inference (downstream tasks use this, not project()) ─────────────────
|
|
5333
|
+
//
|
|
5334
|
+
// Returns h — the encoder representation before the projection head.
|
|
5335
|
+
// This is the vector to use for classification, clustering, retrieval, etc.
|
|
5336
|
+
//
|
|
5337
|
+
// The projection head is only active during training.
|
|
5338
|
+
encode(x) {
|
|
5339
|
+
return this.encoder.predict(x);
|
|
5340
|
+
}
|
|
5341
|
+
// ── Training path: encode then project ───────────────────────────────────
|
|
5342
|
+
//
|
|
5343
|
+
// Returns z — the projected representation used to compute NT-Xent.
|
|
5344
|
+
// Do NOT use this for downstream tasks (see encode() above).
|
|
5345
|
+
project(x) {
|
|
5346
|
+
const h = this.encoder.predict(x);
|
|
5347
|
+
return this.projectionHead.predict(h);
|
|
5348
|
+
}
|
|
5349
|
+
// ── Cosine similarity ─────────────────────────────────────────────────────
|
|
5350
|
+
//
|
|
5351
|
+
// sim(u, v) = uᵀv / (||u|| · ||v||)
|
|
5352
|
+
//
|
|
5353
|
+
// Range: [-1, 1]. We use cosine rather than Euclidean distance because it is
|
|
5354
|
+
// scale-invariant — only the direction of the projection matters, not its
|
|
5355
|
+
// magnitude. This prevents the trivial solution of making ||z|| → ∞.
|
|
5356
|
+
static cosineSimilarity(a, b) {
|
|
5357
|
+
let dot = 0, normA = 0, normB = 0;
|
|
5358
|
+
for (let d = 0; d < a.length; d++) {
|
|
5359
|
+
dot += a[d] * b[d];
|
|
5360
|
+
normA += a[d] * a[d];
|
|
5361
|
+
normB += b[d] * b[d];
|
|
5362
|
+
}
|
|
5363
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
5364
|
+
return denom < 1e-10 ? 0 : dot / denom;
|
|
5365
|
+
}
|
|
5366
|
+
// ── NT-Xent loss (no weight update) ──────────────────────────────────────
|
|
5367
|
+
//
|
|
5368
|
+
// Forward-only pass. Used for validation / monitoring during training.
|
|
5369
|
+
computeLoss(pairs) {
|
|
5370
|
+
const { projections, N } = this._forwardProjections(pairs);
|
|
5371
|
+
return this._ntXentLoss(projections, N);
|
|
5372
|
+
}
|
|
5373
|
+
// ── Training step ─────────────────────────────────────────────────────────
|
|
5374
|
+
//
|
|
5375
|
+
// Given a batch of positive pairs, compute NT-Xent loss and update weights
|
|
5376
|
+
// via finite-difference gradient approximation.
|
|
5377
|
+
//
|
|
5378
|
+
// Full analytical backprop through NT-Xent is complex to implement from
|
|
5379
|
+
// scratch without an autograd engine. Finite differences are slower but
|
|
5380
|
+
// correct and keep the implementation readable for educational purposes.
|
|
5381
|
+
// For production use, couple this with the Tape (autograd) module.
|
|
5382
|
+
//
|
|
5383
|
+
// Step-by-step:
|
|
5384
|
+
// 1. Forward all 2N inputs through encoder + projection head → { z_i }.
|
|
5385
|
+
// 2. Build the 2N×2N cosine similarity matrix (scaled by 1/τ).
|
|
5386
|
+
// 3. For each anchor i, identify its positive pair and all 2N-2 negatives.
|
|
5387
|
+
// 4. Apply softmax over the row; loss = -log(softmax at positive index).
|
|
5388
|
+
// 5. Average over all 2N anchors.
|
|
5389
|
+
// 6. Approximate ∂L/∂w per weight with finite differences and apply update.
|
|
5390
|
+
//
|
|
5391
|
+
// Returns: NT-Xent loss before the weight update.
|
|
5392
|
+
trainStep(pairs, lr) {
|
|
5393
|
+
const loss = this.computeLoss(pairs);
|
|
5394
|
+
const eps = 1e-4;
|
|
5395
|
+
for (const layer of this.encoder.layers) {
|
|
5396
|
+
for (const neuron of layer.neurons) {
|
|
5397
|
+
for (let j = 0; j < neuron.weights.length; j++) {
|
|
5398
|
+
neuron.weights[j] += eps;
|
|
5399
|
+
const lossPlus2 = this.computeLoss(pairs);
|
|
5400
|
+
neuron.weights[j] -= 2 * eps;
|
|
5401
|
+
const lossMinus2 = this.computeLoss(pairs);
|
|
5402
|
+
neuron.weights[j] += eps;
|
|
5403
|
+
const grad2 = (lossPlus2 - lossMinus2) / (2 * eps);
|
|
5404
|
+
neuron.weights[j] += lr * -grad2;
|
|
5405
|
+
}
|
|
5406
|
+
neuron.bias += eps;
|
|
5407
|
+
const lossPlus = this.computeLoss(pairs);
|
|
5408
|
+
neuron.bias -= 2 * eps;
|
|
5409
|
+
const lossMinus = this.computeLoss(pairs);
|
|
5410
|
+
neuron.bias += eps;
|
|
5411
|
+
const grad = (lossPlus - lossMinus) / (2 * eps);
|
|
5412
|
+
neuron.bias += lr * -grad;
|
|
5413
|
+
}
|
|
5414
|
+
}
|
|
5415
|
+
for (const layer of this.projectionHead.layers) {
|
|
5416
|
+
for (const neuron of layer.neurons) {
|
|
5417
|
+
for (let j = 0; j < neuron.weights.length; j++) {
|
|
5418
|
+
neuron.weights[j] += eps;
|
|
5419
|
+
const lossPlus2 = this.computeLoss(pairs);
|
|
5420
|
+
neuron.weights[j] -= 2 * eps;
|
|
5421
|
+
const lossMinus2 = this.computeLoss(pairs);
|
|
5422
|
+
neuron.weights[j] += eps;
|
|
5423
|
+
const grad2 = (lossPlus2 - lossMinus2) / (2 * eps);
|
|
5424
|
+
neuron.weights[j] += lr * -grad2;
|
|
5425
|
+
}
|
|
5426
|
+
neuron.bias += eps;
|
|
5427
|
+
const lossPlus = this.computeLoss(pairs);
|
|
5428
|
+
neuron.bias -= 2 * eps;
|
|
5429
|
+
const lossMinus = this.computeLoss(pairs);
|
|
5430
|
+
neuron.bias += eps;
|
|
5431
|
+
const grad = (lossPlus - lossMinus) / (2 * eps);
|
|
5432
|
+
neuron.bias += lr * -grad;
|
|
5433
|
+
}
|
|
5434
|
+
}
|
|
5435
|
+
return loss;
|
|
5436
|
+
}
|
|
5437
|
+
// ── Private: forward all pairs through the projection head ───────────────
|
|
5438
|
+
//
|
|
5439
|
+
// Returns a flat array of 2N projections.
|
|
5440
|
+
// Layout: [ z_0, z_0', z_1, z_1', ..., z_{N-1}, z_{N-1}' ]
|
|
5441
|
+
// Even indices 2i → original view of pair i
|
|
5442
|
+
// Odd indices 2i+1 → augmented view of pair i (the positive)
|
|
5443
|
+
_forwardProjections(pairs) {
|
|
5444
|
+
const N = pairs.length;
|
|
5445
|
+
const projections = [];
|
|
5446
|
+
for (const [x, xAug] of pairs) {
|
|
5447
|
+
projections.push(this.project(x));
|
|
5448
|
+
projections.push(this.project(xAug));
|
|
5449
|
+
}
|
|
5450
|
+
return { projections, N };
|
|
5451
|
+
}
|
|
5452
|
+
// ── Private: NT-Xent loss over a set of 2N projections ───────────────────
|
|
5453
|
+
//
|
|
5454
|
+
// pairs[2i] and pairs[2i+1] are positives.
|
|
5455
|
+
// All other 2N-2 samples are negatives for each anchor.
|
|
5456
|
+
_ntXentLoss(projections, N) {
|
|
5457
|
+
const total = 2 * N;
|
|
5458
|
+
const tau = this.temperature;
|
|
5459
|
+
const sim = Array.from(
|
|
5460
|
+
{ length: total },
|
|
5461
|
+
(_, i) => Array.from(
|
|
5462
|
+
{ length: total },
|
|
5463
|
+
(_2, j) => _ContrastiveLearning.cosineSimilarity(projections[i], projections[j]) / tau
|
|
5464
|
+
)
|
|
5465
|
+
);
|
|
5466
|
+
let totalLoss = 0;
|
|
5467
|
+
for (let i = 0; i < total; i++) {
|
|
5468
|
+
const posIdx = i % 2 === 0 ? i + 1 : i - 1;
|
|
5469
|
+
const numerator = Math.exp(sim[i][posIdx]);
|
|
5470
|
+
let denominator = 0;
|
|
5471
|
+
for (let k = 0; k < total; k++) {
|
|
5472
|
+
if (k !== i) {
|
|
5473
|
+
denominator += Math.exp(sim[i][k]);
|
|
5474
|
+
}
|
|
5475
|
+
}
|
|
5476
|
+
totalLoss += -Math.log(numerator / (denominator + 1e-10));
|
|
5477
|
+
}
|
|
5478
|
+
return totalLoss / total;
|
|
5479
|
+
}
|
|
5480
|
+
};
|
|
5481
|
+
|
|
4739
5482
|
// src/GAN.ts
|
|
4740
5483
|
var GAN = class {
|
|
4741
5484
|
constructor(latentDim, generatorHidden, outputDim, discriminatorHidden, options) {
|
|
@@ -5546,12 +6289,14 @@ function _sampleNormal() {
|
|
|
5546
6289
|
export {
|
|
5547
6290
|
Adam,
|
|
5548
6291
|
AttentionHead,
|
|
6292
|
+
Augmenter,
|
|
5549
6293
|
Autoencoder,
|
|
5550
6294
|
BatchNorm,
|
|
5551
6295
|
BiasVector,
|
|
5552
6296
|
CausalConv1D,
|
|
5553
6297
|
ClipOptimizer,
|
|
5554
6298
|
ClippedOptimizerFactory,
|
|
6299
|
+
ContrastiveLearning,
|
|
5555
6300
|
Conv1D,
|
|
5556
6301
|
Conv2D,
|
|
5557
6302
|
DataAugmentation,
|
|
@@ -5570,6 +6315,7 @@ export {
|
|
|
5570
6315
|
LSTMLayer,
|
|
5571
6316
|
Layer,
|
|
5572
6317
|
LayerNorm,
|
|
6318
|
+
LearnedPositionalEncoding,
|
|
5573
6319
|
LinearRegression,
|
|
5574
6320
|
LogisticRegression,
|
|
5575
6321
|
LossPlotter,
|
|
@@ -5586,18 +6332,21 @@ export {
|
|
|
5586
6332
|
NeuronN,
|
|
5587
6333
|
PCA,
|
|
5588
6334
|
Perceptron,
|
|
6335
|
+
PositionalEncoding,
|
|
5589
6336
|
RNN,
|
|
5590
6337
|
SGD,
|
|
5591
6338
|
SOM,
|
|
5592
6339
|
Seq2Seq,
|
|
5593
6340
|
SoftmaxRegression,
|
|
5594
6341
|
TCN,
|
|
6342
|
+
TSNE,
|
|
5595
6343
|
Trainer,
|
|
5596
6344
|
TransformerBlock,
|
|
5597
6345
|
VAE,
|
|
5598
6346
|
Value,
|
|
5599
6347
|
WeightInspector,
|
|
5600
6348
|
WeightMatrix,
|
|
6349
|
+
Word2Vec,
|
|
5601
6350
|
accuracy,
|
|
5602
6351
|
auc,
|
|
5603
6352
|
classificationReport,
|