C · Embedding
“How much holiday do I get?” ve “Annual paid leave depends on the length of service.” cümleleri aynı konudan söz ediyor ama tek bir ortak kelimeleri yok; anahtar kelime araması bu ikisini eşleştiremez. Peki bir model eşleştirebilir mi? Bu sayfada metni sayılara çevirip ölçüyoruz.
Metin girer, 1024 sayı çıkar
4-embed.tsher chunk’ı bge-m3 modeline gönderir; geri 1024 sayılık bir liste, yani bir vektör gelir. Bu, metnin embedding’i.npm run step -- 4, sonradata/4-vectors.jsondosyasını açın.Sayıların tek tek anlamı yok
1024 sayının hiçbiri “leave” ya da “date” demek değil. Anlam, iki vektörün birbirine göre konumunda: yakın anlam, yakın vektör.
Yakınlığa kim karar verdi?
Biz değil, model. bge-m3, çok sayıda “bu iki metin aynı şeyi söylüyor” örneğiyle eğitilmiş bir neural network. Hangi metinleri yakın koyacağını eğitimde öğrendi; ağırlıkları artık donmuş.
Soru da aynı modelden geçer
Soru da 1024 sayıya döner; yoksa chunk’larla karşılaştırılamaz. Model aynı olmalı: başka bir modelin vektörleri başka bir uzaydadır.
Yakınlığı ölçmek: cosine
İki vektörü aralarındaki açıya göre karşılaştırır. Uzunluğa bakmaz, yöne bakar; sonuç −1 ile 1 arasında, 1 = aynı yön. Retriever’ımız da Chroma koleksiyonumuz da bunu kullanıyor: cosine benzerliği.
L2: uçlar arası mesafe
İki noktanın düz çizgi uzaklığı. Dikkat, ölçek ters: küçük sayı = yakın.
Dot: çarp ve topla
Karşılıklı sayıları çarpıp toplar. a = (3, 0) ve b = (2, 2) için: 3×2 + 0×2 = 6. Kodun yaptığı bu kadar. Cosine’in aksine uzunluklara bölmez.
Resimde dot: gölge
b’nin ucundan a’ya dik bir çizgi çekin. b’nin a üzerindeki gölgesi 2. a’nın uzunluğu çarpı gölge: 3 × 2 = 6, yine aynı sayı. Bizim vektörlerin uzunluğu 1.000 (
4-embed.tslog’u). O zaman dot ile cosine arasında ne fark kalır?src/lib/similarity.tsiçindeki boşdot()fonksiyonunu yazın. Kontrol:npm run similar -- --metric dot. Her çift için hata değil, bir sayı görünmeli.
Log’da ilk chunk’ın (bir duyuru) ilk 6 sayısına ve vektörün uzunluğuna bakın. 60 chunk’ın hiçbiri yeniden hesaplanmadı. B sayfasındaki npm run question onları zaten embed etti ve vektörleri .cache/ klasörüne kaydetti.
$ npm run step -- 4
━━ STEP 4/8 · EMBED IN data/3-chunks.json (60 chunks) WHAT send each chunk's text to bge-m3 (Ollama /api/embed, 24 per call) OUT 60 vectors · 1024 numbers each · 0 computed, 60 from cache announcement-meal-card@2025#1 → [-0.0356, 0.0001, -0.0278, -0.0149, -0.0330, -0.0674, … ] length (norm) of that vector: 1.000 TIME 55 ms → data/4-vectors.jsonCosine ve L2, src/lib/similarity.ts içinde birkaçar satır:
/** * What it does: scores how close two vectors are by their angle, ignoring length. * * Cosine similarity: the angle between two vectors, ignoring their length. * 1 = same direction (same meaning), 0 = no relation in theory (real texts rarely go that low), −1 = opposite. * @example cosine([1, 0], [2, 0]) // 1 — same direction, different length */export function cosine(a: number[], b: number[]): number { let dot = 0; let na = 0; let nb = 0; for (let i = 0; i < a.length; i++) { dot += a[i]! * b[i]!; na += a[i]! * a[i]!; nb += b[i]! * b[i]!; } return dot / (Math.sqrt(na) * Math.sqrt(nb));}/** * What it does: measures the straight-line distance between two vectors, so smaller is closer. * * Euclidean (L2) distance: the straight-line distance between the two points. * 0 = the same point; bigger = further apart. Note the direction: SMALLER is closer. * @example l2([0, 0], [3, 4]) // 5 */export function l2(a: number[], b: number[]): number { let sum = 0; for (let i = 0; i < a.length; i++) sum += (a[i]! - b[i]!) ** 2; return Math.sqrt(sum);}Embedding bitti: elimizde ne var?
Bölüm başlığı “Embedding bitti: elimizde ne var?”60 chunk, her biri 1024 sayı. Aramayı bu sayılara emanet etmeden önce: model neyi yakın, neyi uzak koydu?
Önce birkaç çift
npm run similarhazırladığımız 5 cümle çiftini embed eder ve her çiftin cosine benzerliğini yazar.npm run similar -- --metric dotvenpm run similar -- --metric l2çalıştırın.Beklenen: dil engel değil
İngilizce soru, Türkçe kural: 0.752. Ortak kelime yok, dil bile farklı; model anlama bakarak yakın koydu. İlgisiz çift 0.503 aldı: “ilgisiz” bile 0 değil. Skorlar mutlak değil, görelidir.
Şaşırtan: paraphrase düşük kaldı
“How much holiday do I get?” ve “Annual paid leave …”: aynı fikir, başka kelimeler: buna paraphrase denir. Skor 0.599, ilgisiz çiftin yalnızca 0.096 üstünde. Model eşleştiriyor ama kıl payı. E sayfasında bir “holiday” sorusunda izin tablosu ancak 5. sıraya çıkıyor; rerank bunu düzeltiyor.
Tuzak: olumsuzluk
“… can work remotely” ile “… cannot work remotely”: 0.853. Zıt anlam, çok yakın vektör. Embedding konuyu yakalar, evet mi hayır mı olduğunu kaçırabilir. Farkı, cevabı yazan model metni okuyarak görmeli.
npm run similar -- "How much holiday do I get?" "How many days of annual leave do I get?", sonra kendi olumsuzluk çiftinizi deneyin.İkiz tablolar
Yurt içi (“domestic”) ve yurt dışı (“international”) harcırah soruları: 0.860, beşinin en yükseği. Farklı tablo, farklı sayı; vektörler neredeyse aynı. Kaliteyi ölçmek sayfasında bu türden 3 soru var; birinde doğru tablo ancak 2. sıraya çıkıyor.
Neden harita çiziyoruz?
1024 boyutu göremeyiz. Düz bir harita, modelin chunk’larımızla ne yaptığını gösterir. Sorunları görmemize yardım eder. Bazı metinler dokümanlar arasında tekrar eder. Bir doküman tek başına durur. Bir soru cevabından uzağa düşer. Harita gözümüz için; arama onu hiç kullanmaz.
1024 boyutu 2'ye indirmek
npm run map, PCA ile 60 chunk’ı düz bir haritaya çizer. PCA, chunk’ların en çok ayrıştığı iki yönü tutar. Yatay yön yayılımın %12,3’ünü, dikey yön %8,8’ini tutuyor. Yaklaşık %79 bu haritada yok.Ne görüyoruz?
Türkçe doküman ayrı duruyor. Dil engel değilse neden? Tek Türkçe doküman o, tek BT dokümanı da o. Harita hangisinin onu ayırdığını söyleyemez. Üstte altı “Purpose and Scope” bölümü yan yana: aynı türde metinler.
Yıldız: günün sorusu
Yıldız, travel-expenses ve benefits chunk’larının yanına düştü. Cevap olan hr-leave §3 uzakta. Düz bir harita gerçek mesafeleri koruyamaz. 1024 boyutun hepsinde ölçen
6-retrieve.ts, §3’ü birinci sıraya koyuyor. Harita görmeye yardım eder, karar vermez.npm run map -- "How often should I change my password?", sonradata/map.html.
$ npm run similar
━━ STEP 4b · SIMILAR — which texts did the model put close together? WHAT embed both texts, measure cosine (1 = same direction)
0.599 ████████████░░░░░░░░ paraphrase How much holiday do I get? Annual paid leave depends on the length of service.
0.752 ███████████████░░░░░ EN ↔ TR How often must I change my password? Şifreler 90 günde bir değiştirilmelidir.
0.860 █████████████████░░░ twin tables How much is the domestic per diem? How much is the international per diem?
0.853 █████████████████░░░ negation trap Employees on probation can work remotely. Employees on probation cannot work remotely.
0.503 ██████████░░░░░░░░░░ unrelated Your meal card is loaded every month. USB sticks are not allowed.$ npm run map
━━ STEP 4b · MAP — our chunks in two dimensions IN data/4-vectors.json (60 vectors × 1024) WHAT PCA: the 2 directions along which the chunks differ most · the question projected onto them OUT PC1 keeps 12.3% · PC2 8.8% of the spread ★ "How many working days of annual leave does an employee with 7 years of service get?" lands next to: travel-expenses@2025#8, travel-expenses@2025#5, benefits@2025#5 (on the 2-D map — step 6 measures in all 1024) TIME 74 ms → data/map.html (open it in a browser) + data/map.jsondata/map.html dosyasını tarayıcıda açın: noktaların üzerine gelince chunk’ın metni görünür. Yukarıdaki çizim aynı dosyanın koordinatlarıyla (data/map.json) çizildi.
Boş dot(), similarity.ts dosyasının sonunda:
// ── YOUR TURN ───────────────────────────────────────────────────────────────────────────────────// Write the sibling of cosine(): the dot product. Multiply the vectors element by element and sum.// Unlike cosine it does NOT divide by the lengths. Check: npm run similar -- --metric dot → a score for each pair.// Question to answer afterwards: step 4 printed "norm 1.000" for every vector — so for OUR vectors,// how does dot() compare to cosine()?
/** * What it does: multiplies two vectors item by item and adds up the results. * * Dot product: sum of a[i] × b[i]. * @example dot([1, 2, 3], [4, 5, 6]) // 32 */export function dot(a: number[], b: number[]): number { throw new Error("dot() is not written yet — see the YOUR TURN note above it in src/lib/similarity.ts");}1 · Vektöre bakın. npm run step -- 4, sonra data/4-vectors.json: her chunk’ın vector alanında 1024 sayı var, embedModel alanında modelin adı. Bruno’da 00-ollama → embed one sentence isteğini gönderin: aynı işi elle yapar. input içindeki bir kelimeyi değiştirip tekrar gönderin.
2 · Benzerini yazın. src/lib/similarity.ts içinde dot() boş. cosine() içindeki döngüde dot zaten hesaplanıyor; uzunluklara bölmeden aynısını yazın. Kontrol: npm run similar -- --metric dot → her çift için dot() is not written yet hatası değil, bir sayı görünmeli.
3 · Ölçüleri karşılaştırın. Dot çıktınızı cosine çıktısıyla (yukarıdaki log) yan yana koyun. Sonra npm run similar -- --metric l2 çalıştırın. Neden böyle? İpucu: 4-embed.ts log’undaki uzunluk.
4 · Kendi çiftinizi deneyin. npm run similar -- "How much holiday do I get?" "How many days of annual leave do I get?". Sonra olumsuzluk tuzağını kendi cümlelerinizle kurun.
5 · Haritada kendi sorunuz. npm run map -- "How often should I change my password?" ve data/map.html: yıldız bu kez nereye düştü?
Geride kaldıysanız: npm run catchup -- 4.
Sık sorulan
Bölüm başlığı “Sık sorulan”similar.ts ne işe yarıyor?
Pipeline’ın bir adımı değil, bakmak için bir araç. İki metni embedding modeline gönderir ve vektörlerinin ne kadar yakın olduğunu yazar. Hazır beş çift var; biri tuzak. Çiftler modele hiçbir şey öğretmez: model sabittir ve başka hiçbir adım bu çiftleri okumaz. Tuzak bugün yanılıyorsa yarın da yanılır. Bunu ancak başka bir model ya da rerank gibi sonraki bir kontrol değiştirir.
Dot neden cosine ile aynı çıktı?
Cosine, dot sonucunu iki vektörün uzunluğuna böler. bge-m3 vektörlerinin uzunluğu 1.000: bölme 1’e bölmek demek, sonuç değişmez. Pek çok vector DB bu yüzden uzunluğu 1 olan vektörlerde dot’u “hızlı cosine” olarak sunar.
Dense ve sparse ne demek?
Bizim vektörler dense: 1024 sayının hepsi dolu, anlamı taşıyor. Sparse bir vektörde sözlükteki her kelime için bir boyut vardır ve çoğu sıfırdır; bu kelime eşleşmesidir (BM25 ailesi). Hybrid arama ikisini birleştirir.
Hangi embedding modelini seçmeliyim?
Dil desteğine (bizde İngilizce ve Türkçe), vektör boyutuna (her chunk için depolama), okuyabildiği metin uzunluğuna ve çalıştığı yere (yerel mi, API mi: veri nereye gidiyor?) bakın. Genel sıralama tabloları fikir verir; karar kendi soru setinizde ölçülerek verilir (Kaliteyi ölçmek).
Modeli değiştirirsem ne olur?
Bütün chunk’lar yeniden embed edilir; eski vektörler başka bir uzayda kalır. Kod bunu engelliyor: 4-embed.ts hangi modeli kullandığını data/4-vectors.json içine yazar, 6-retrieve.ts soru için başka bir model görürse durur ve 4-embed.ts ile 5-store.ts dosyalarını yeniden çalıştırmanızı ister.
Cosine, L2 ve dot ayrı yöntemler mi?
Evet, yakınlığı ölçmenin üç ayrı yolu. Cosine açıya bakar. L2 iki vektörün uçları arasındaki mesafeyi ölçer; küçük olan daha yakındır. Dot sayıları çarpıp toplar. Bizim vektörlerin hepsinin uzunluğu 1, bu yüzden üçü de aynı sırayı verir. Uzunlukları farklı vektörlerde sonuçları ayrışabilir.