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 yakın olduğu yoğun vektörlere çevirmek. Bu derste o hedefi gerçekleştiriyoruz — ve sonuç, “kral - erkek + kadın = kraliçe” gibi şaşırtıcı bir vektör aritmetiğine varıyor.
Sezgi
Word embedding, her kelimeyi, cümle içinde 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 yapısı 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 yakın
# 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 yapısı 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şırı 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 basit 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şılaştır):")
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 yakın komşularından biri!")
En yakın komşular (4. dersteki one-hot ile karşılaştır):
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 yakın komşularından biri!- derste “kral”-“kraliçe” benzerliği 0.00 idi. Şimdi kraliçe, kralın en yakın 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” yazmadık — bu benzerlik, sadece ikisinin benzer cümle bağlamlarında geçmesinden ortaya çıktı.
Matematik
PPMI ve embedding'lerin vektör aritmetiği
| Sembol | Anlamı |
|---|---|
| ve kelimelerinin birlikte (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şık 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 aritmetiği 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İki analoji de doğru cevabı (kraliçe, prenses) ilk 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üvenilir 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)]
İki analoji de doğru cevabı (kraliçe, prenses) ilk 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üvenilir analojiler üretir.İki analoji de (“kral→kraliçe” ve “prens→prenses”) doğru cevabı ilk 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) aynı.
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 iki farklı (ama matematiksel olarak akraba) uygulamasıdır — Word2Vec sinir ağıyla tahmin ederek, GloVe ortak-geçiş istatistikleriyle doğrudan öğrenir.
- Bu notebook’taki PPMI+SVD yöntemi, GloVe’un ruhuna yakındır — ikisi de ortak-geçiş istatistiklerinden yola çıkar.
- 6-8. derste göreceğimiz RNN/LSTM, girdi 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 yanlış çıkabilir; bu bir corpus boyutu sorunudur.
- Word embedding’i “kelimelerin anlamını sözlük gibi kodladığını” sanmak — aslında sadece dağılımsal (hangi bağlamlarda geçtiği) bir benzerlik yakalar.
- Kosinüs benzerliğini “aynı kelime” ile karıştırmak — yüksek benzerlik, ilişkili 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ı çıkarsadı (doğru cevap)
- Kelimelerin harfleri benzer olduğu için
- Rastgele bir sonuç
Neden: Embedding öğrenimi, hiçbir 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 ilişkilerin 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şık 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şık 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 yazılmaz -- ortak-geçiş istatistiklerinden ortaya çıkar.
- 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 girdisini oluşturacak.