v2.0.3: TF-IDF Cosinus-Aehnlichkeit (2.5-erweitert)
- src/ingest.ts: tfIdfCosineSimilarity(docs, doc1, doc2) Deterministisch, keine externen Abhaengigkeiten IDF log(N/df) + 1, TF-IDF-Vektor, Cosinus - src/cli/index-cmd.ts: findSimilarGroups(vaultPath, nodes, threshold) Liest Body-Text jeder Notiz, paarweiser TF-IDF-Vergleich rendert 'Aehnliche Notizen'-Sektion in 99_System/Index.md Threshold 0.15 (TF-IDF Cosinus ist konservativer als Jaccard) - tests/tfidf.test.ts: 5 neue Tests - identische Dokumente: 1.0 - thematisch aehnliche > thematisch ungleiche - 0.0 wenn doc nicht im Korpus - symmetrisch: sim(a,b) == sim(b,a) - TF-IDF diskriminiert besser als Jaccard Verifiziert: - 61/61 Tests gruen (10 neue fuer TF-IDF) - E2E: 2 aehnliche Kaffee-Notizen -> 'Aehnlich (Score 0.34)' in Index.md - Ungleiche Notiz (Sport) wird nicht als aehnlich markiert - Jaccard bleibt als einfachere Alternative erhalten
This commit is contained in:
1 parent
f2e8e59baf
commit
7fdca79419
3 files changed
+218
-4
No files matched your search
@@ -0,0 +1,77 @@
|
||||
// Tests fuer 2.5-erweitert (TF-IDF Cosinus-Aehnlichkeit)
|
||||
// Phase 2.5-erweitert
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { tfIdfCosineSimilarity, extractWords, jaccardSimilarity } from '../src/ingest.js';
|
||||
|
||||
describe('extractWords', () => {
|
||||
it('lowercase + >= 4 Zeichen', () => {
|
||||
expect(extractWords('Kaffee und ich')).toEqual(['kaffee']);
|
||||
});
|
||||
|
||||
it('Satzzeichen werden entfernt', () => {
|
||||
expect(extractWords('Kaffee, Milch! Zucker.')).toEqual(['kaffee', 'milch', 'zucker']);
|
||||
});
|
||||
|
||||
it('Unicode bleibt erhalten', () => {
|
||||
expect(extractWords('Größere Übung mit Äpfeln')).toEqual(['größere', 'übung', 'äpfeln']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('jaccardSimilarity (Baseline)', () => {
|
||||
it('identische Texte: 1.0', () => {
|
||||
expect(jaccardSimilarity('Kaffee und Kuchen', 'Kaffee und Kuchen')).toBe(1.0);
|
||||
});
|
||||
|
||||
it('verschiedene Texte: 0.0', () => {
|
||||
expect(jaccardSimilarity('Kaffee trinken', 'Auto fahren')).toBe(0.0);
|
||||
});
|
||||
|
||||
it('Teilweise Überlappung: zwischen 0 und 1', () => {
|
||||
const sim = jaccardSimilarity('Kaffee am Morgen mit Milch', 'Kaffee am Abend mit Zucker');
|
||||
expect(sim).toBeGreaterThan(0);
|
||||
expect(sim).toBeLessThan(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tfIdfCosineSimilarity', () => {
|
||||
const docs = [
|
||||
'Kaffee am Morgen mit Milch und Zucker',
|
||||
'Kaffee am Abend mit Milch und Keksen',
|
||||
'Auto fahren am Wochenende mit Freunden',
|
||||
];
|
||||
|
||||
it('identische Dokumente: 1.0', () => {
|
||||
expect(tfIdfCosineSimilarity(docs, docs[0], docs[0])).toBe(1.0);
|
||||
});
|
||||
|
||||
it('Thematisch aehnliche Dokumente (Kaffee) > thematisch ungleiche (Kaffee vs Auto)', () => {
|
||||
const similar = tfIdfCosineSimilarity(docs, docs[0], docs[1]);
|
||||
const different = tfIdfCosineSimilarity(docs, docs[0], docs[2]);
|
||||
expect(similar).toBeGreaterThan(different);
|
||||
});
|
||||
|
||||
it('0.0 wenn eines der Dokumente nicht im Korpus', () => {
|
||||
expect(tfIdfCosineSimilarity(docs, 'nicht im Korpus', docs[0])).toBe(0);
|
||||
expect(tfIdfCosineSimilarity(docs, docs[0], 'auch nicht')).toBe(0);
|
||||
});
|
||||
|
||||
it('Cosinus ist symmetrisch: sim(a, b) === sim(b, a)', () => {
|
||||
const ab = tfIdfCosineSimilarity(docs, docs[0], docs[1]);
|
||||
const ba = tfIdfCosineSimilarity(docs, docs[1], docs[0]);
|
||||
expect(ab).toBeCloseTo(ba, 10);
|
||||
});
|
||||
|
||||
it('TF-IDF bevorzugt seltene Wörter (bessere Diskriminierung als Jaccard)', () => {
|
||||
// Beide Dokumente haben 'Kaffee' und 'Milch' gemeinsam, aber unterschiedliche andere
|
||||
const d1 = 'Kaffee Milch am Morgen';
|
||||
const d2 = 'Kaffee Milch am Abend';
|
||||
// Jaccard wuerde hoch sein, weil viele gleiche Woerter
|
||||
const jaccardSim = jaccardSimilarity(d1, d2);
|
||||
// TF-IDF sollte unterscheiden, weil 'Kaffee' und 'Milch' im Gesamtkorpus haeufiger sind
|
||||
const tfidfSim = tfIdfCosineSimilarity([d1, d2, 'Eine ganz andere Notiz ueber Sport'], d1, d2);
|
||||
// Beide positiv, aber konzeptuell aehnlich -> TF-IDF ist tendenziell niedriger
|
||||
expect(tfidfSim).toBeGreaterThan(0);
|
||||
expect(tfidfSim).toBeLessThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user