@aleph-ai/tinyaleph 1.5.7 → 1.6.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/core/alexander-module.js +1469 -0
- package/core/arithmetic-link-kernel.js +1338 -0
- package/core/emotion.js +565 -0
- package/core/gravity.js +714 -0
- package/core/hilbert.js +506 -3
- package/core/index.js +132 -4
- package/core/nonlocal.js +744 -0
- package/core/oracle.js +662 -0
- package/examples/01-hello-world.js +69 -0
- package/examples/02-basic-hash.js +90 -0
- package/examples/02-observer-stack.js +385 -0
- package/examples/03-quantum-coin.js +136 -0
- package/examples/05-symbolic-resonance.js +146 -0
- package/examples/06-symbol-database.js +150 -0
- package/examples/07-semantic-inference.js +223 -0
- package/examples/08-compound-symbols.js +219 -0
- package/examples/README.md +170 -0
- package/examples/ai/01-embeddings.js +155 -0
- package/examples/ai/02-semantic-memory.js +243 -0
- package/examples/ai/03-reasoning.js +243 -0
- package/examples/ai/04-knowledge-graph.js +279 -0
- package/examples/ai/05-llm-integration.js +333 -0
- package/examples/ai/06-agent.js +294 -0
- package/examples/ai/07-hybrid-ai.js +223 -0
- package/examples/ai/08-entropy-reasoning.js +259 -0
- package/examples/ai/09-concept-learning.js +271 -0
- package/examples/ai/10-prompt-primes.js +312 -0
- package/examples/ai/11-rag.js +332 -0
- package/examples/ai/12-neuro-symbolic.js +321 -0
- package/examples/ai/README.md +80 -0
- package/examples/arithmetic-topology/01-legendre-symbol.js +78 -0
- package/examples/arithmetic-topology/02-redei-symbol.js +126 -0
- package/examples/arithmetic-topology/03-alk-kuramoto.js +138 -0
- package/examples/arithmetic-topology/04-alexander-module.js +117 -0
- package/examples/arithmetic-topology/05-signature-memory.js +118 -0
- package/examples/arithmetic-topology/README.md +291 -0
- package/examples/bioinformatics/01-dna-encoding.js +108 -0
- package/examples/bioinformatics/02-central-dogma.js +162 -0
- package/examples/bioinformatics/03-protein-folding.js +206 -0
- package/examples/bioinformatics/04-dna-computing.js +192 -0
- package/examples/bioinformatics/05-molecular-binding.js +209 -0
- package/examples/book-operators-demo.js +155 -0
- package/examples/chat.js +105 -0
- package/examples/crt-homology/01-residue-encoding.js +87 -0
- package/examples/crt-homology/02-birkhoff-attention.js +100 -0
- package/examples/crt-homology/03-homology-loss.js +132 -0
- package/examples/crt-homology/04-crt-resoformer.js +132 -0
- package/examples/crt-homology/README.md +67 -0
- package/examples/crypto/01-password-hash.js +210 -0
- package/examples/crypto/02-key-derivation.js +210 -0
- package/examples/crypto/03-hmac.js +229 -0
- package/examples/crypto/04-file-integrity.js +263 -0
- package/examples/crypto/05-content-hash.js +263 -0
- package/examples/crypto/README.md +99 -0
- package/examples/demo-modular.js +223 -0
- package/examples/demo-two-layer.js +196 -0
- package/examples/discrete/01-integer-sine-table.js +120 -0
- package/examples/discrete/02-codebook-tunneling.js +118 -0
- package/examples/discrete/03-canonical-fusion.js +135 -0
- package/examples/discrete/04-tick-gate.js +139 -0
- package/examples/discrete/README.md +142 -0
- package/examples/emotion-demo.js +200 -0
- package/examples/formal-semantics/01-typed-terms.js +156 -0
- package/examples/formal-semantics/02-reduction.js +202 -0
- package/examples/formal-semantics/03-lambda-translation.js +206 -0
- package/examples/formal-semantics/04-enochian-language.js +257 -0
- package/examples/formal-semantics/README.md +98 -0
- package/examples/gravity-demo.js +190 -0
- package/examples/math/01-quaternions.js +237 -0
- package/examples/math/02-octonions.js +192 -0
- package/examples/math/03-prime-factorization.js +215 -0
- package/examples/math/04-vector-spaces.js +210 -0
- package/examples/math/05-gaussian-primes.js +234 -0
- package/examples/math/README.md +93 -0
- package/examples/nonlocal-demo.js +237 -0
- package/examples/oracle-demo.js +204 -0
- package/examples/physics/01-oscillator.js +177 -0
- package/examples/physics/02-lyapunov.js +201 -0
- package/examples/physics/03-collapse.js +183 -0
- package/examples/physics/04-kuramoto.js +212 -0
- package/examples/physics/05-entropy.js +226 -0
- package/examples/physics/05-sync-models.js +298 -0
- package/examples/physics/06-primeon-ladder.js +233 -0
- package/examples/physics/07-kuramoto-coupled-ladder.js +298 -0
- package/examples/physics/README.md +126 -0
- package/examples/quantum/01-prime-hunter.js +79 -0
- package/examples/quantum/02-entanglement-demo.js +79 -0
- package/examples/quantum/03-wave-analysis.js +63 -0
- package/examples/resonance/01-prime-hilbert-space.js +140 -0
- package/examples/resonance/02-prime-resonance-network.js +221 -0
- package/examples/resonance/03-resoformer.js +349 -0
- package/examples/resonance/04-resoformer-training.js +329 -0
- package/examples/resonance/05-language-model.js +484 -0
- package/examples/resonance/README.md +238 -0
- package/examples/run-examples.js +427 -0
- package/examples/scientific/01-single-qubit.js +185 -0
- package/examples/scientific/02-two-qubit.js +209 -0
- package/examples/scientific/03-quantum-circuits.js +270 -0
- package/examples/scientific/04-measurement.js +229 -0
- package/examples/scientific/05-algorithms.js +245 -0
- package/examples/scientific/06-random.js +225 -0
- package/examples/scientific/07-wavefunction.js +192 -0
- package/examples/scientific/README.md +118 -0
- package/examples/semantic/01-vocabulary.js +186 -0
- package/examples/semantic/02-similarity.js +263 -0
- package/examples/semantic/03-word-algebra.js +295 -0
- package/examples/semantic/04-clustering.js +348 -0
- package/examples/semantic/05-classification.js +386 -0
- package/examples/semantic/06-dna-encoding.js +228 -0
- package/examples/semantic/07-search.js +304 -0
- package/examples/semantic/08-qa-system.js +278 -0
- package/examples/semantic/README.md +116 -0
- package/examples/topology/01-108-invariant.js +81 -0
- package/examples/topology/02-trefoil-constants.js +112 -0
- package/examples/topology/03-gauge-symmetry.js +112 -0
- package/examples/topology/04-free-energy-dynamics.js +124 -0
- package/examples/topology/README.md +129 -0
- package/index.js +32 -0
- package/modular.js +63 -2
- package/package.json +8 -3
- package/physics/alk-kuramoto.js +817 -0
- package/physics/index.js +23 -2
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @example Text Clustering
|
|
3
|
+
* @description Group similar texts using hypercomplex embeddings
|
|
4
|
+
*
|
|
5
|
+
* TinyAleph enables unsupervised text clustering:
|
|
6
|
+
* - Documents are embedded as hypercomplex vectors
|
|
7
|
+
* - K-means or hierarchical clustering groups similar texts
|
|
8
|
+
* - Cluster centroids represent topic summaries
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { SemanticBackend, Hypercomplex } = require('../../modular');
|
|
12
|
+
|
|
13
|
+
// ===========================================
|
|
14
|
+
// SETUP
|
|
15
|
+
// ===========================================
|
|
16
|
+
|
|
17
|
+
const backend = new SemanticBackend({ dimension: 16 });
|
|
18
|
+
|
|
19
|
+
console.log('TinyAleph Text Clustering Example');
|
|
20
|
+
console.log('==================================\n');
|
|
21
|
+
|
|
22
|
+
// ===========================================
|
|
23
|
+
// SAMPLE DATA
|
|
24
|
+
// ===========================================
|
|
25
|
+
|
|
26
|
+
const documents = [
|
|
27
|
+
// Technology cluster
|
|
28
|
+
{ id: 1, text: 'Machine learning algorithms process large datasets', category: 'tech' },
|
|
29
|
+
{ id: 2, text: 'Artificial intelligence is transforming industries', category: 'tech' },
|
|
30
|
+
{ id: 3, text: 'Neural networks can recognize patterns in data', category: 'tech' },
|
|
31
|
+
{ id: 4, text: 'Deep learning models require significant compute power', category: 'tech' },
|
|
32
|
+
|
|
33
|
+
// Sports cluster
|
|
34
|
+
{ id: 5, text: 'The football team won the championship game', category: 'sports' },
|
|
35
|
+
{ id: 6, text: 'Basketball players practice dribbling skills', category: 'sports' },
|
|
36
|
+
{ id: 7, text: 'The athlete broke the world record in swimming', category: 'sports' },
|
|
37
|
+
{ id: 8, text: 'Soccer matches attract millions of fans worldwide', category: 'sports' },
|
|
38
|
+
|
|
39
|
+
// Food cluster
|
|
40
|
+
{ id: 9, text: 'Italian cuisine features pasta and fresh ingredients', category: 'food' },
|
|
41
|
+
{ id: 10, text: 'The chef prepared a delicious gourmet meal', category: 'food' },
|
|
42
|
+
{ id: 11, text: 'Fresh vegetables make healthy nutritious dishes', category: 'food' },
|
|
43
|
+
{ id: 12, text: 'Baking bread requires flour yeast and patience', category: 'food' },
|
|
44
|
+
|
|
45
|
+
// Finance cluster
|
|
46
|
+
{ id: 13, text: 'Stock market indices reached new highs today', category: 'finance' },
|
|
47
|
+
{ id: 14, text: 'Investment portfolios should be diversified', category: 'finance' },
|
|
48
|
+
{ id: 15, text: 'Interest rates affect mortgage payments', category: 'finance' },
|
|
49
|
+
{ id: 16, text: 'Cryptocurrency markets are highly volatile', category: 'finance' }
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
// Embed all documents
|
|
53
|
+
const embeddedDocs = documents.map(doc => ({
|
|
54
|
+
...doc,
|
|
55
|
+
state: backend.textToOrderedState(doc.text)
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
// ===========================================
|
|
59
|
+
// HELPER FUNCTIONS
|
|
60
|
+
// ===========================================
|
|
61
|
+
|
|
62
|
+
function similarity(state1, state2) {
|
|
63
|
+
let dot = 0, mag1 = 0, mag2 = 0;
|
|
64
|
+
for (let i = 0; i < state1.c.length; i++) {
|
|
65
|
+
dot += state1.c[i] * state2.c[i];
|
|
66
|
+
mag1 += state1.c[i] * state1.c[i];
|
|
67
|
+
mag2 += state2.c[i] * state2.c[i];
|
|
68
|
+
}
|
|
69
|
+
return dot / (Math.sqrt(mag1) * Math.sqrt(mag2) || 1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function distance(state1, state2) {
|
|
73
|
+
let sum = 0;
|
|
74
|
+
for (let i = 0; i < state1.c.length; i++) {
|
|
75
|
+
const diff = state1.c[i] - state2.c[i];
|
|
76
|
+
sum += diff * diff;
|
|
77
|
+
}
|
|
78
|
+
return Math.sqrt(sum);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ===========================================
|
|
82
|
+
// K-MEANS CLUSTERING
|
|
83
|
+
// ===========================================
|
|
84
|
+
|
|
85
|
+
console.log('K-Means Clustering:');
|
|
86
|
+
console.log('─'.repeat(50) + '\n');
|
|
87
|
+
|
|
88
|
+
function kMeansClustering(docs, k, maxIterations = 20) {
|
|
89
|
+
const dim = docs[0].state.c.length;
|
|
90
|
+
|
|
91
|
+
// Initialize centroids randomly from documents
|
|
92
|
+
const centroids = [];
|
|
93
|
+
const usedIndices = new Set();
|
|
94
|
+
while (centroids.length < k) {
|
|
95
|
+
const idx = Math.floor(Math.random() * docs.length);
|
|
96
|
+
if (!usedIndices.has(idx)) {
|
|
97
|
+
usedIndices.add(idx);
|
|
98
|
+
const centroid = Hypercomplex.zero(dim);
|
|
99
|
+
for (let i = 0; i < dim; i++) {
|
|
100
|
+
centroid.c[i] = docs[idx].state.c[i];
|
|
101
|
+
}
|
|
102
|
+
centroids.push(centroid);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let assignments = new Array(docs.length).fill(-1);
|
|
107
|
+
|
|
108
|
+
for (let iter = 0; iter < maxIterations; iter++) {
|
|
109
|
+
// Assign documents to nearest centroid
|
|
110
|
+
const newAssignments = [];
|
|
111
|
+
for (const doc of docs) {
|
|
112
|
+
let minDist = Infinity;
|
|
113
|
+
let cluster = 0;
|
|
114
|
+
for (let c = 0; c < k; c++) {
|
|
115
|
+
const d = distance(doc.state, centroids[c]);
|
|
116
|
+
if (d < minDist) {
|
|
117
|
+
minDist = d;
|
|
118
|
+
cluster = c;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
newAssignments.push(cluster);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check for convergence
|
|
125
|
+
if (JSON.stringify(newAssignments) === JSON.stringify(assignments)) {
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
assignments = newAssignments;
|
|
129
|
+
|
|
130
|
+
// Update centroids
|
|
131
|
+
for (let c = 0; c < k; c++) {
|
|
132
|
+
const members = docs.filter((_, i) => assignments[i] === c);
|
|
133
|
+
if (members.length > 0) {
|
|
134
|
+
for (let i = 0; i < dim; i++) {
|
|
135
|
+
centroids[c].c[i] = members.reduce((sum, m) => sum + m.state.c[i], 0) / members.length;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { centroids, assignments };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Run K-means with k=4 (matching our 4 categories)
|
|
145
|
+
const { centroids, assignments } = kMeansClustering(embeddedDocs, 4);
|
|
146
|
+
|
|
147
|
+
// Display clusters
|
|
148
|
+
console.log('Cluster assignments:\n');
|
|
149
|
+
for (let c = 0; c < 4; c++) {
|
|
150
|
+
const members = embeddedDocs.filter((_, i) => assignments[i] === c);
|
|
151
|
+
console.log(`Cluster ${c + 1} (${members.length} documents):`);
|
|
152
|
+
for (const member of members) {
|
|
153
|
+
console.log(` [${member.category}] "${member.text.substring(0, 45)}..."`);
|
|
154
|
+
}
|
|
155
|
+
console.log();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ===========================================
|
|
159
|
+
// CLUSTER PURITY
|
|
160
|
+
// ===========================================
|
|
161
|
+
|
|
162
|
+
console.log('═'.repeat(50));
|
|
163
|
+
console.log('Cluster Purity Analysis:');
|
|
164
|
+
console.log('═'.repeat(50) + '\n');
|
|
165
|
+
|
|
166
|
+
function calculatePurity(docs, assignments) {
|
|
167
|
+
const clusterCounts = new Map();
|
|
168
|
+
|
|
169
|
+
for (let i = 0; i < assignments.length; i++) {
|
|
170
|
+
const cluster = assignments[i];
|
|
171
|
+
const category = docs[i].category;
|
|
172
|
+
|
|
173
|
+
if (!clusterCounts.has(cluster)) {
|
|
174
|
+
clusterCounts.set(cluster, new Map());
|
|
175
|
+
}
|
|
176
|
+
const catCounts = clusterCounts.get(cluster);
|
|
177
|
+
catCounts.set(category, (catCounts.get(category) || 0) + 1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let correctTotal = 0;
|
|
181
|
+
for (const [cluster, counts] of clusterCounts) {
|
|
182
|
+
const maxCount = Math.max(...counts.values());
|
|
183
|
+
correctTotal += maxCount;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return correctTotal / docs.length;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const purity = calculatePurity(embeddedDocs, assignments);
|
|
190
|
+
console.log(`Overall cluster purity: ${(purity * 100).toFixed(1)}%\n`);
|
|
191
|
+
|
|
192
|
+
// ===========================================
|
|
193
|
+
// HIERARCHICAL CLUSTERING
|
|
194
|
+
// ===========================================
|
|
195
|
+
|
|
196
|
+
console.log('═'.repeat(50));
|
|
197
|
+
console.log('Hierarchical Clustering:');
|
|
198
|
+
console.log('═'.repeat(50) + '\n');
|
|
199
|
+
|
|
200
|
+
function hierarchicalClustering(docs) {
|
|
201
|
+
// Start with each document as its own cluster
|
|
202
|
+
let clusters = docs.map((doc, i) => ({
|
|
203
|
+
id: i,
|
|
204
|
+
members: [doc],
|
|
205
|
+
centroid: doc.state
|
|
206
|
+
}));
|
|
207
|
+
|
|
208
|
+
const history = [];
|
|
209
|
+
|
|
210
|
+
while (clusters.length > 1) {
|
|
211
|
+
// Find closest pair
|
|
212
|
+
let minDist = Infinity;
|
|
213
|
+
let mergeI = 0, mergeJ = 1;
|
|
214
|
+
|
|
215
|
+
for (let i = 0; i < clusters.length; i++) {
|
|
216
|
+
for (let j = i + 1; j < clusters.length; j++) {
|
|
217
|
+
const d = distance(clusters[i].centroid, clusters[j].centroid);
|
|
218
|
+
if (d < minDist) {
|
|
219
|
+
minDist = d;
|
|
220
|
+
mergeI = i;
|
|
221
|
+
mergeJ = j;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Merge clusters
|
|
227
|
+
const merged = {
|
|
228
|
+
id: clusters.length,
|
|
229
|
+
members: [...clusters[mergeI].members, ...clusters[mergeJ].members],
|
|
230
|
+
centroid: Hypercomplex.zero(16)
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Compute new centroid
|
|
234
|
+
const allMembers = merged.members;
|
|
235
|
+
for (let d = 0; d < 16; d++) {
|
|
236
|
+
merged.centroid.c[d] = allMembers.reduce((sum, m) => sum + m.state.c[d], 0) / allMembers.length;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
history.push({
|
|
240
|
+
level: history.length,
|
|
241
|
+
merged: [clusters[mergeI].id, clusters[mergeJ].id],
|
|
242
|
+
distance: minDist,
|
|
243
|
+
size: merged.members.length
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Remove old clusters, add merged
|
|
247
|
+
clusters = clusters.filter((_, i) => i !== mergeI && i !== mergeJ);
|
|
248
|
+
clusters.push(merged);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return history;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const dendrogram = hierarchicalClustering(embeddedDocs);
|
|
255
|
+
|
|
256
|
+
console.log('Dendrogram (merge history):');
|
|
257
|
+
console.log('Level Distance Size');
|
|
258
|
+
console.log('─'.repeat(30));
|
|
259
|
+
for (const merge of dendrogram.slice(-8)) {
|
|
260
|
+
console.log(`${String(merge.level).padStart(5)} ${merge.distance.toFixed(4).padStart(9)} ${String(merge.size).padStart(5)}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ===========================================
|
|
264
|
+
// CLUSTER CENTROIDS AS TOPICS
|
|
265
|
+
// ===========================================
|
|
266
|
+
|
|
267
|
+
console.log('\n' + '═'.repeat(50));
|
|
268
|
+
console.log('Cluster Centroids (Topic Representations):');
|
|
269
|
+
console.log('═'.repeat(50) + '\n');
|
|
270
|
+
|
|
271
|
+
// For each cluster, find which documents are most central
|
|
272
|
+
for (let c = 0; c < 4; c++) {
|
|
273
|
+
const members = embeddedDocs.filter((_, i) => assignments[i] === c);
|
|
274
|
+
if (members.length === 0) continue;
|
|
275
|
+
|
|
276
|
+
// Find most central document
|
|
277
|
+
let mostCentral = null;
|
|
278
|
+
let minAvgDist = Infinity;
|
|
279
|
+
|
|
280
|
+
for (const candidate of members) {
|
|
281
|
+
let avgDist = 0;
|
|
282
|
+
for (const other of members) {
|
|
283
|
+
avgDist += distance(candidate.state, other.state);
|
|
284
|
+
}
|
|
285
|
+
avgDist /= members.length;
|
|
286
|
+
|
|
287
|
+
if (avgDist < minAvgDist) {
|
|
288
|
+
minAvgDist = avgDist;
|
|
289
|
+
mostCentral = candidate;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
console.log(`Cluster ${c + 1} representative:`);
|
|
294
|
+
console.log(` "${mostCentral.text}"\n`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ===========================================
|
|
298
|
+
// NEW DOCUMENT CLASSIFICATION
|
|
299
|
+
// ===========================================
|
|
300
|
+
|
|
301
|
+
console.log('═'.repeat(50));
|
|
302
|
+
console.log('Classifying New Documents:');
|
|
303
|
+
console.log('═'.repeat(50) + '\n');
|
|
304
|
+
|
|
305
|
+
const newDocs = [
|
|
306
|
+
'Quantum computing uses qubits for calculations',
|
|
307
|
+
'The tennis player won the grand slam tournament',
|
|
308
|
+
'Homemade pizza with fresh tomatoes and cheese',
|
|
309
|
+
'Bond yields decreased after the central bank announcement'
|
|
310
|
+
];
|
|
311
|
+
|
|
312
|
+
for (const text of newDocs) {
|
|
313
|
+
const state = backend.textToOrderedState(text);
|
|
314
|
+
|
|
315
|
+
// Find nearest cluster
|
|
316
|
+
let minDist = Infinity;
|
|
317
|
+
let cluster = 0;
|
|
318
|
+
for (let c = 0; c < centroids.length; c++) {
|
|
319
|
+
const d = distance(state, centroids[c]);
|
|
320
|
+
if (d < minDist) {
|
|
321
|
+
minDist = d;
|
|
322
|
+
cluster = c;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Find dominant category in cluster
|
|
327
|
+
const clusterMembers = embeddedDocs.filter((_, i) => assignments[i] === cluster);
|
|
328
|
+
const catCounts = new Map();
|
|
329
|
+
for (const m of clusterMembers) {
|
|
330
|
+
catCounts.set(m.category, (catCounts.get(m.category) || 0) + 1);
|
|
331
|
+
}
|
|
332
|
+
const dominantCat = [...catCounts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] || 'unknown';
|
|
333
|
+
|
|
334
|
+
console.log(`"${text.substring(0, 45)}..."`);
|
|
335
|
+
console.log(` → Cluster ${cluster + 1} (likely: ${dominantCat})\n`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ===========================================
|
|
339
|
+
// KEY TAKEAWAYS
|
|
340
|
+
// ===========================================
|
|
341
|
+
|
|
342
|
+
console.log('═'.repeat(50));
|
|
343
|
+
console.log('KEY TAKEAWAYS:');
|
|
344
|
+
console.log('1. K-means clusters documents by embedding similarity');
|
|
345
|
+
console.log('2. Hierarchical clustering reveals nested structure');
|
|
346
|
+
console.log('3. Centroids represent topic summaries');
|
|
347
|
+
console.log('4. New documents can be classified to clusters');
|
|
348
|
+
console.log('5. Purity measures cluster quality');
|