Word embedding (Word2Vec, GloVe)
Önkoşul:Bag-of-words'ten gömmeye
Kanca
- dersin sonunda bir hedef koymuştuk: kelimeleri, anlamca yakın olanların YAKIN olduğu yoğun vektörlere çevirmek. Bu derste o hedefi gerçekleştiriyoruz — ve sonuç, “kral - erkek + kadın = kraliçe” gibi ŞAŞIRTICI bir vektör aritmetiğine varıyor.
Sezgi
Word embedding, her kelimeyi, CÜMLE İÇİNDE hangi kelimelerle birlikte göründüğüne bakarak öğrenilen yoğun bir vektöre çevirir. Fikir: benzer bağlamlarda geçen kelimeler (kral/kraliçe, köpek/kedi) benzer vektörler alır — bu, “bir kelimeyi komşularıyla tanırsın” fikrinin matematiğe dökülmüş hâli.
Bir kelimeye tıkla — en yakın komşularını gör. Sonra “kral − erkek + kadın” analojisini dene.
Mekanizma
Küçük ama semantik YAPISI olan bir Türkçe corpus üretip embedding öğretelim:
Semantik yapılı bir corpus
# 4. dersin sonundaki hedefi hatırla: kelimeleri, ANLAMCA yakın olanların YAKIN
# olduğu yoğun vektörlere çevirmek. Bunun için önce (şablonlarla, doğru Türkçe
# ünlü uyumu kurallarıyla) küçük ama semantik YAPISI olan bir corpus üretelim.
UNLU_KALIN_DUZ, UNLU_KALIN_YUVARLAK = set("aı"), set("ou")
UNLU_INCE_DUZ, UNLU_INCE_YUVARLAK = set("ei"), set("öü")
TUM_UNLULER = UNLU_KALIN_DUZ | UNLU_KALIN_YUVARLAK | UNLU_INCE_DUZ | UNLU_INCE_YUVARLAK
SERT_UNSUZLER = set("çfhkpsşt")
def son_unlu(kelime):
for h in reversed(kelime):
if h in TUM_UNLULER:
return h
return "a"
def lokatif_ek(kelime):
h = son_unlu(kelime)
ek = "de" if h in (UNLU_INCE_DUZ | UNLU_INCE_YUVARLAK) else "da"
return kelime + ("t" + ek[1:] if kelime[-1] in SERT_UNSUZLER else ek)
def yonelme_ek(kelime):
h = son_unlu(kelime)
ek = "e" if h in (UNLU_INCE_DUZ | UNLU_INCE_YUVARLAK) else "a"
return kelime + (("y" + ek) if kelime[-1] in TUM_UNLULER else ek)
def dir_ek(kelime):
h = son_unlu(kelime)
ek = {"a": "dır", "ı": "dır", "o": "dur", "u": "dur", "e": "dir", "i": "dir", "ö": "dür", "ü": "dür"}[h]
return kelime + ("t" + ek[1:] if kelime[-1] in SERT_UNSUZLER else ek)
ROYAL_M, ROYAL_F = ["kral", "prens"], ["kraliçe", "prenses"]
GENDER_M, GENDER_F = ["erkek", "adam"], ["kadın", "kız"]
ANIMALS = ["köpek", "kedi", "aslan", "kartal", "balık", "kuş", "at", "tilki"]
PLACES = ["saray", "orman", "ev", "okul", "bahçe", "şehir", "köy", "deniz", "dağ", "nehir"]
VERBS_INTRANS = ["yaşar", "gider", "gelir", "uyur", "koşar", "oynar", "güler", "düşünür", "bekler", "oturur"]
FOODS = ["ekmek", "elma", "su", "çorba", "et", "pilav", "süt", "meyve"]
VERBS_EAT = ["yer", "içer"]
PROFESSIONS = ["doktor", "öğretmen", "asker", "çiftçi", "balıkçı", "tüccar", "avcı", "ressam"]
ADJ = ["mutlu", "üzgün", "kızgın", "yorgun", "meraklı", "cesur", "akıllı", "güçlü", "genç", "yaşlı"]
cumleler = []
for r in ROYAL_M:
for g in GENDER_M:
cumleler += [f"{r} güçlü bir {dir_ek(g)}", f"{r} genç bir {dir_ek(g)}"]
for r in ROYAL_F:
for g in GENDER_F:
cumleler += [f"{r} güçlü bir {dir_ek(g)}", f"{r} genç bir {dir_ek(g)}"]
# kral/kraliçe "yaşlı, ülkeyi yönetir"; prens/prenses "genç, eğitim alır" --
# bu ayrım, kral-prens'in birbirine AŞIRI benzemesini önler.
for r in ["kral", "kraliçe"]:
for p in ["saray", "şehir"]:
cumleler += [f"{r} {lokatif_ek(p)} yaşar", f"{r} {yonelme_ek(p)} gider"]
cumleler += [f"{r} tahtta {v}" for v in ["güler", "düşünür", "bekler", "oturur"]]
cumleler += [f"{r} yaşlı ve bilgedir", f"{r} ülkeyi yönetir"]
for r in ["prens", "prenses"]:
for p in ["saray", "bahçe"]:
cumleler += [f"{r} {lokatif_ek(p)} yaşar", f"{r} {yonelme_ek(p)} gider"]
cumleler += [f"{r} sarayda {v}" for v in ["güler", "oynar", "koşar", "öğrenir"]]
cumleler += [f"{r} genç ve enerjiktir", f"{r} eğitim alır"]
for g in GENDER_M + GENDER_F:
for p in random.sample(PLACES, 4):
cumleler += [f"{g} {lokatif_ek(p)} yaşar", f"{g} {yonelme_ek(p)} gider"]
cumleler += [f"{g} {v}" for v in random.sample(VERBS_INTRANS, 4)]
cumleler += [f"{g} çok {dir_ek(a)}" for a in random.sample(ADJ, 3)]
for a in ANIMALS:
cumleler += [f"{a} {lokatif_ek(p)} yaşar" for p in random.sample(PLACES, 3)]
cumleler += [f"{a} {v}" for v in random.sample(VERBS_INTRANS, 3)]
cumleler += [f"{a} çok {dir_ek(adj)}" for adj in random.sample(ADJ, 2)]
for subj in GENDER_M + GENDER_F + ROYAL_M + ROYAL_F + PROFESSIONS:
cumleler += [f"{subj} {f} {random.choice(VERBS_EAT)}" for f in random.sample(FOODS, 3)]
for prof in PROFESSIONS:
cumleler += [f"{prof} {lokatif_ek(p)} çalışır" for p in random.sample(PLACES, 3)]
cumleler += [f"{prof} çok {dir_ek(a)}" for a in random.sample(ADJ, 3)]
cumleler += [f"{prof} {v}" for v in random.sample(VERBS_INTRANS, 3)]
random.shuffle(cumleler)
tum_kelimeler = [c.split() for c in cumleler]
dagarcik = sorted(set(w for kl in tum_kelimeler for w in kl))
kelime_indeks = {k: i for i, k in enumerate(dagarcik)}
print(f"Corpus: {len(cumleler)} cümle, {len(dagarcik)} benzersiz kelime.")
print("Örnek cümleler:", cumleler[:3])Corpus: 300 cümle, 92 benzersiz kelime.
Örnek cümleler: ['çiftçi yaşar', 'çiftçi uyur', 'doktor şehirde çalışır']Ortak-geçiş sayımı, PPMI, SVD
# Word2Vec/GloVe'un ruhuna yakın, ama daha BASİT bir yöntem: ortak-geçiş (co-occurrence)
# sayıp PPMI ile ağırlıklandırmak, sonra SVD ile boyutu küçültmek.
PENCERE = 3
V = len(dagarcik)
ortak_gecis = np.zeros((V, V))
for kl in tum_kelimeler:
for i, kelime in enumerate(kl):
for j in range(max(0, i - PENCERE), min(len(kl), i + PENCERE + 1)):
if i != j:
ortak_gecis[kelime_indeks[kelime], kelime_indeks[kl[j]]] += 1
toplam = ortak_gecis.sum()
satir_toplam = ortak_gecis.sum(axis=1, keepdims=True)
sutun_toplam = ortak_gecis.sum(axis=0, keepdims=True)
beklenen = (satir_toplam @ sutun_toplam) / toplam
with np.errstate(divide="ignore", invalid="ignore"):
pmi = np.log((ortak_gecis + 1e-10) / (beklenen + 1e-10))
ppmi = np.maximum(pmi, 0) # SADECE pozitif PMI -- "Positive" PMI
U, S, Vt = np.linalg.svd(ppmi, full_matrices=False)
BOYUT = 50
embedding = U[:, :BOYUT] * S[:BOYUT]
print(f"\nOne-hot boyutu: {V} -> Embedding boyutu: {BOYUT} (YOĞUN, seyrek değil)")
One-hot boyutu: 92 -> Embedding boyutu: 50 (YOĞUN, seyrek değil)Şimdi 4. dersteki one-hot deneyiyle DOĞRUDAN karşılaştıralım:
En yakın komşular
def kosinus(a, b):
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-10))
def en_yakin(kelime, k=5):
v = embedding[kelime_indeks[kelime]]
benzerlikler = embedding @ v / (np.linalg.norm(embedding, axis=1) * np.linalg.norm(v) + 1e-10)
en_iyi = np.argsort(-benzerlikler)[1 : k + 1]
return [(dagarcik[i], round(float(benzerlikler[i]), 3)) for i in en_iyi]
print("\nEn yakın komşular (4. dersteki one-hot ile KARŞILAŞTIR):")
for k in ["kral", "kraliçe", "köpek", "doktor"]:
print(f" {k}: {en_yakin(k)}")
print("\n4. derste 'kral'-'kraliçe' benzerliği 0.00 idi. Şimdi (yukarıda) kraliçe, kralın EN YAKIN komşularından biri!")
En yakın komşular (4. dersteki one-hot ile KARŞILAŞTIR):
kral: [('kraliçe', 0.922), ('tahtta', 0.357), ('bir', 0.351), ('ve', 0.315), ('prens', 0.308)]
kraliçe: [('kral', 0.922), ('bir', 0.349), ('prenses', 0.341), ('tahtta', 0.336), ('ve', 0.312)]
köpek: [('aslan', 0.557), ('kız', 0.397), ('balık', 0.369), ('kedi', 0.314), ('yaşar', 0.311)]
doktor: [('çiftçi', 0.469), ('öğretmen', 0.465), ('balıkçı', 0.462), ('tilki', 0.4), ('içer', 0.374)]
4. derste 'kral'-'kraliçe' benzerliği 0.00 idi. Şimdi (yukarıda) kraliçe, kralın EN YAKIN komşularından biri!- derste “kral”-“kraliçe” benzerliği 0.00 idi. Şimdi kraliçe, kralın EN YAKIN komşusu (0.92)! Çoğu kişi “bu bilgiyi biri ELLE öğretmiş olmalı” sanır. Yanlış, çünkü hiçbir yerde “kral ve kraliçe benzerdir” YAZMADIK — bu benzerlik, SADECE ikisinin benzer cümle bağlamlarında geçmesinden ORTAYA ÇIKTI.
Matematik
PPMI ve embedding'lerin vektör aritmetiği
| Sembol | Anlamı |
|---|---|
| ve kelimelerinin BİRLİKTE (aynı pencere içinde) geçme olasılığı | |
| Eğer bağımsız olsalardı BEKLENEN birlikte geçme olasılığı | |
| PPMI | Gerçek birlikte-geçiş, BEKLENENDEN ne kadar fazla — sadece pozitif değerler tutulur |
PPMI matrisine SVD uygulamak (boyut küçültmek), Word2Vec’in matematiksel olarak YAKLAŞIK eşdeğeri olduğu gösterilmiştir — iki farklı yöntem, benzer bir hedefe ulaşır.
Kod
Şimdi bu derste en çok merak edilen kısma: vektör aritmetiğine.
kral - erkek + kadın = ?
# Embedding'lerin en çarpıcı özelliği: VEKTÖR ARİTMETİĞİ bile anlamlı hale geliyor.
def analoji(a, b, c, k=3):
va, vb, vc = embedding[kelime_indeks[a]], embedding[kelime_indeks[b]], embedding[kelime_indeks[c]]
hedef = va - vb + vc
benzerlikler = embedding @ hedef / (np.linalg.norm(embedding, axis=1) * np.linalg.norm(hedef) + 1e-10)
haric = {kelime_indeks[a], kelime_indeks[b], kelime_indeks[c]}
siralanmis = [i for i in np.argsort(-benzerlikler) if i not in haric][:k]
return [(dagarcik[i], round(float(benzerlikler[i]), 3)) for i in siralanmis]
print("\nkral - erkek + kadın = ?")
print(" ", analoji("kral", "erkek", "kadın"))
print("prens - erkek + kadın = ?")
print(" ", analoji("prens", "erkek", "kadın"))
print("\nİKİ analoji de doğru cevabı (kraliçe, prenses) İLK sırada buluyor!")
print("Not: bu küçük (öğretici amaçlı) bir corpus'tan öğrenildi. Gerçek Word2Vec/GloVe,")
print("milyarlarca kelimelik metinle eğitilir ve çok daha GÜVENİLİR analojiler üretir.")
kral - erkek + kadın = ?
[('kraliçe', 0.573), ('tahtta', 0.333), ('öğretmen', 0.231)]
prens - erkek + kadın = ?
[('prenses', 0.502), ('genç', 0.282), ('sarayda', 0.242)]
İKİ analoji de doğru cevabı (kraliçe, prenses) İLK sırada buluyor!
Not: bu küçük (öğretici amaçlı) bir corpus'tan öğrenildi. Gerçek Word2Vec/GloVe,
milyarlarca kelimelik metinle eğitilir ve çok daha GÜVENİLİR analojiler üretir.İKİ analoji de (“kral→kraliçe” ve “prens→prenses”) doğru cevabı İLK sırada buluyor. Bu, embedding uzayının SADECE “benzer kelimeler yakın” değil, “ilişkiler YÖN olarak da tutarlı” bir yapı öğrendiğini gösteriyor — kral’dan kraliçe’ye giden yön, prens’ten prenses’e giden yönle (kabaca) AYNI.
Görselleştirme için 2B'ye indirgeme
# EmbeddingUzayi bileşeni için: tam embedding matrisi + 2B görselleştirme projeksiyonu.
U2, S2, _ = np.linalg.svd(embedding - embedding.mean(axis=0), full_matrices=False)
projeksiyon_2b = (U2[:, :2] * S2[:2]).tolist()
kategori_haritasi = {}
for k in ROYAL_M: kategori_haritasi[k] = "kraliyet-erkek"
for k in ROYAL_F: kategori_haritasi[k] = "kraliyet-kadın"
for k in GENDER_M: kategori_haritasi[k] = "cinsiyet-erkek"
for k in GENDER_F: kategori_haritasi[k] = "cinsiyet-kadın"
for k in ANIMALS: kategori_haritasi[k] = "hayvan"
for k in PROFESSIONS: kategori_haritasi[k] = "meslek"
veri = {
"dagarcik": dagarcik,
"embedding": np.round(embedding, 4).tolist(),
"projeksiyon2B": [[round(x, 4) for x in nokta] for nokta in projeksiyon_2b],
"kategori": [kategori_haritasi.get(k, "diger") for k in dagarcik],
}
print(f"\nDışa aktarılacak: {len(dagarcik)} kelime x {BOYUT} boyut + 2B projeksiyon.")
Dışa aktarılacak: 92 kelime x 50 boyut + 2B projeksiyon.Nerede işe yarar
Word embedding, modern NLP’nin neredeyse HER yerinde temel bir bileşendir:
- Word2Vec ve GloVe, bu fikrin İKİ farklı (ama matematiksel olarak akraba) uygulamasıdır — Word2Vec sinir ağıyla TAHMİN ederek, GloVe ortak-geçiş istatistikleriyle DOĞRUDAN öğrenir.
- Bu notebook’taki PPMI+SVD yöntemi, GloVe’un ruhuna YAKINDIR — ikisi de ortak-geçiş istatistiklerinden yola çıkar.
- 6-8. derste göreceğimiz RNN/LSTM, GİRDİ olarak tam olarak bu embedding vektörlerini alacak — artık kelimeler, ağın işleyebileceği anlamlı sayılara dönüştü.
Bu 3 hatayı yaparsın:
- Embedding’lerin “mükemmel” olduğunu sanmak — küçük corpus’larda (bu dersteki gibi) analojiler bazen YANLIŞ çıkabilir; bu bir corpus BOYUTU sorunudur.
- Word embedding’i “kelimelerin anlamını SÖZLÜK gibi kodladığını” sanmak — aslında sadece DAĞILIMSAL (hangi bağlamlarda geçtiği) bir benzerlik yakalar.
- Kosinüs benzerliğini “aynı kelime” ile karıştırmak — yüksek benzerlik, İLİŞKİLİ olmak anlamına gelir, eş anlamlı olmak ZORUNDA değildir (zıt anlamlılar bile yüksek benzerlik alabilir, çünkü benzer bağlamlarda geçerler).
Kendini test et
1. Notebook'ta 'kral' ve 'kraliçe' embedding'lerinin birbirine yakın çıkmasının nedeni nedir?
- Kod içinde bu ikisinin benzer olduğu elle belirtildi
- İkisi de corpus'ta BENZER cümle bağlamlarında (saray, taht, güçlü/genç bir X) geçtiği için -- embedding, bu dağılımsal benzerlikten kelimenin anlamsal yakınlığını ÇIKARSADI (doğru cevap)
- Kelimelerin harfleri benzer olduğu için
- Rastgele bir sonuç
Neden: Embedding öğrenimi, HİÇBİR anlam bilgisini elle vermez -- sadece hangi kelimelerin birlikte/benzer bağlamlarda geçtiğini sayar; kral ve kraliçe benzer bağlamlarda geçtiği için embedding'leri de birbirine yakın çıkar.
2. 'kral - erkek + kadın ≈ kraliçe' analojisi neyi gösteriyor?
- Embedding uzayının rastgele olduğunu
- Embedding uzayının sadece "hangi kelimeler benzer" değil, kelimeler arasındaki İLİŞKİLERİN de (örn. "cinsiyet yönü") tutarlı bir geometrik yapı olarak kodlandığını (doğru cevap)
- Kral ve kraliçenin aynı kelime olduğunu
- Vektör çıkarma işleminin anlamsız olduğunu
Neden: Bu analoji, "kral'dan kraliçeye giden vektör yönü" ile "erkekten kadına giden vektör yönü"nün YAKLAŞIK aynı olduğunu gösteriyor -- yani embedding uzayı, kelimeler arası ilişkileri de yönlü bir yapı olarak öğrenmiş.
3. PPMI + SVD yöntemi ile Word2Vec/GloVe arasındaki ilişki nedir?
- Birbirleriyle hiç ilgileri yoktur
- PPMI+SVD, GloVe'un ruhuna yakındır (ikisi de ortak-geçiş istatistiklerinden yola çıkar) ve matematiksel olarak Word2Vec'e YAKLAŞIK eşdeğer olduğu gösterilmiştir (doğru cevap)
- PPMI+SVD, Word2Vec'ten sonra icat edilmiştir
- Sadece PPMI+SVD gerçek embedding üretir
Neden: PPMI tabanlı ortak-geçiş matrisine SVD uygulamanın, Word2Vec'in skip-gram ile negative sampling yönteminin matematiksel olarak yaklaşık bir eşdeğeri olduğu gösterilmiştir -- farklı yollar, benzer bir hedefe ulaşır.
Özet
Özet
- Word embedding, kelimeleri, benzer bağlamlarda geçenlerin yakın olduğu yoğun vektörlere çevirir.
- Bu benzerlik, elle YAZILMAZ -- ortak-geçiş istatistiklerinden ORTAYA ÇIKAR.
- PPMI + SVD, GloVe'un ruhuna yakın, basit ama etkili bir embedding öğrenme yöntemidir.
- Embedding uzayı, sadece benzerliği değil, "kral→kraliçe" gibi YÖNLÜ ilişkileri de vektör aritmetiğiyle kodlar.
- Bu vektörler, 6-8. derste göreceğimiz RNN/LSTM gibi modellerin GİRDİSİNİ oluşturacak.