Kitaplar / Yöntem ile Makine Öğrenmesi / Bölüm 4 — Derin öğrenme
Bölüm 4 — Derin öğrenme
Şimdiye kadarki modellerde kuralı biz tasarladık (bir eşik, bir mesafe, bir ağaç). Sinir ağlarında ise yalnızca yapıyı kurarız; milyonlarca küçük ayarı (ağırlığı) veri belirler. Bu bölümde YZ biriminin iki özelliğini kullanacağız: tensörler (sayı tabloları) ve otomatik türev. kayıp.GeriYay çağrısı, kaybın her ağırlığa göre türevini kendisi hesaplar. Optimize edici (Adam) de her ağırlığı kaybı azaltacak yöne biraz kaydırır. Beş programın hepsi bu döngüyü kullanır:
opt.SıfırlaGrad { eski türevleri sil }
kayıp := ... { ileri hesap: tahmin et, hatayı ölç }
kayıp.GeriYay { geri yayılım: her ağırlığın türevi }
opt.Adım { ağırlıkları güncelle }16. Çok katmanlı algılayıcı (sinir ağı)
Fikir. Bir nöron girdilerinin ağırlıklı toplamını alır ve sonucu doğrusal olmayan bir fonksiyondan (aktivasyon) geçirir. Nöronlar katmanlara dizilir; her katmanın çıktısı bir sonrakinin girdisidir. Lojistik regresyon tek bir nörondur ve yalnızca düz bir sınır çizebilir. Araya gizli bir katman eklenince ağ çember gibi eğri sınırlar da öğrenebilir.
program ÇokKatmanlıAlgılayıcı
{ Çok katmanlı algılayıcı (MLP): katman katman dizilmiş "nöronlar".
Her katman çıktı = aktivasyon(girdi @ W + b) hesaplar. Doğrusal olmayan aktivasyon (ReLU, tanh)
sayesinde ağ eğri karar sınırları öğrenebilir. Ağırlıklar geri yayılım + Adam ile güncellenir.
Görev: bir noktanın yarıçapı 1 olan çemberin içinde mi dışında mı olduğunu öğrenmek. }
kullan YZ
değişken
noktalar, etiket, deneme: liste
X, Y, tahmin: tensör
model, opt: herhangi
geçmiş: liste
i, isabet: tamsayı
px, py: ondalık
başla
Tohum(7)
noktalar := []; etiket := []
1'den 200'e kadar her i için
başla
px := Rastgele(4000) / 1000 - 2 { [-2, 2) aralığında }
py := Rastgele(4000) / 1000 - 2
noktalar.Ekle([px, py])
eğer px * px + py * py < 1 ise etiket.Ekle([1]) değilse etiket.Ekle([0])
bitir
X := Tensör(noktalar)
Y := Tensör(etiket)
{ 2 girdi → 16 gizli nöron (ReLU) → 1 çıktı (sigmoid: "içeride olma olasılığı") }
model := Ardışık([Yoğun(2, 16, 'relu'), Yoğun(16, 1, 'sigmoid')])
opt := Adam(model.Parametreler, 0.05)
geçmiş := Eğit(model, X, Y, 'ikiliÇaprazEntropi', opt, 400, 100)
tahmin := model.Tahmin(X)
isabet := 0
0'dan 199'a kadar her i için
eğer (tahmin[i, 0] > 0.5) = (etiket[i][0] = 1) ise isabet := isabet + 1
yaz('Eğitim doğruluğu: %' + isabet / 2, 1)
yaz('Parametre sayısı: ' + model.ParametreSayısı, 1)
yaz('Yeni noktalar:', 1)
deneme := [[0, 0], [0.5, 0.5], [0.9, 0], [1.2, 0], [1.5, 1.5]]
tahmin := model.Tahmin(Tensör(deneme))
0'dan 4'e kadar her i için
yaz(Biçimle(' {} içeride olma olasılığı {:.2}', deneme[i], tahmin[i, 0]), 1)
bitir.Programın çıktısı şöyledir:
Epok 1/400 kayıp = 0.564626
Epok 100/400 kayıp = 0.021568
Epok 200/400 kayıp = 0.006376
Epok 300/400 kayıp = 0.003043
Epok 400/400 kayıp = 0.001746
Eğitim doğruluğu: %100.0
Parametre sayısı: 65
Yeni noktalar:
[0, 0] içeride olma olasılığı 1.00
[0.5, 0.5] içeride olma olasılığı 1.00
[0.9, 0] içeride olma olasılığı 0.31
[1.2, 0] içeride olma olasılığı 0.00
[1.5, 1.5] içeride olma olasılığı 0.00Ne öğrendik. 65 ağırlıklı küçük bir ağ, kimse ona çember formülünü söylemeden eğitim noktalarının hepsini doğru sınıflandırdı. Ama [0.9, 0] noktasına dikkat etmekte yarar vardır: nokta aslında içeridedir, ağ ise %31 der. Eğitim doğruluğunun %100 olması modelin kusursuz olduğu anlamına gelmez. Sınıra yakın bölgede az örnek vardı ve ağ sınırı biraz içeriden çizdi. Gerçek başarıyı her zaman ayrı tutulmuş bir test kümesiyle ölçmek gerekir.
Alıştırma. Gizli katmanı Yoğun(2, 2, 'relu') yapınız. İki nöron bir çember çizmeye yetiyor mu? Sonra 200 yerine 1000 nokta kullanıp [0.9, 0] tahmininin nasıl değiştiğine bakınız.
17. Evrişimli sinir ağı (CNN)
Fikir. Bir resimde “kenar” nerede olursa olsun kenardır. CNN bu yüzden her yerde aynı küçük filtreyi kullanır: filtre resmin üzerinde kaydırılır ve her konumda altındaki yamayla çarpılıp toplanır. Sonuç bir öznitelik haritasıdır: filtrenin aradığı desen nerede varsa orada büyük değer çıkar. Havuzlama bu haritayı küçültür. Gerçek ağlarda filtreler elle seçilmez, veriden öğrenilir.
program EvrişimliAğ
{ Evrişimli sinir ağı (CNN) iki temel işlem üzerine kurulur:
1) Evrişim: küçük bir filtre (çekirdek) görüntünün üzerinde kaydırılır; her konumda filtre ile altındaki
yama çarpılıp toplanır. Aynı filtre her yerde kullanıldığı için desen nerede olursa olsun bulunur.
2) Havuzlama: harita küçük bloklara bölünür, her bloktan en büyük değer alınır.
Bölüm A elle seçilmiş bir kenar filtresini uygular. Bölüm B bir filtreyi veriden öğrenir:
yamaları bir matrisin satırlarına dizersek evrişim tek bir matris çarpımına dönüşür (yama @ filtre). }
kullan YZ
değişken
resim, harita, havuz, filtre: ondalık dizisi dizisi
sinyal, hedef, yama: ondalık dizisi
yamalar: liste
P, H, w: tensör
opt: herhangi
kayıp: tensör
i, adım: tamsayı
fonksiyon Evrişim(R, F: ondalık dizisi dizisi): ondalık dizisi dizisi
değişken i, j, a, b: tamsayı; satır: ondalık dizisi; t: ondalık
başla
sonuç := []
0'dan Uzunluk(R) - Uzunluk(F)'e kadar her i için
başla
satır := []
0'dan Uzunluk(R[0]) - Uzunluk(F[0])'a kadar her j için
başla
t := 0
0'dan Uzunluk(F) - 1'e kadar her a için
0'dan Uzunluk(F[0]) - 1'e kadar her b için
t := t + R[i + a][j + b] * F[a][b]
satır.Ekle(t)
bitir
sonuç.Ekle(satır)
bitir
bitir
fonksiyon Göster(M: ondalık dizisi dizisi)
değişken s: ondalık dizisi
başla
M içindeki her s için
yaz(' ' + Birleştir(Eşle(s, fonksiyon(v: ondalık) => Biçimle('{:4.0}', v)), ' '), 1)
bitir
başla
yaz('--- A) Hazır filtreyle dikey kenar bulma ---', 1)
{ 6×6 gri tonlu resim: solu koyu (0), sağı açık (9) }
resim := []
1'den 6'ya kadar her i için resim.Ekle([0, 0, 9, 9, 9, 9])
filtre := [[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]] { soldan sağa parlaklık artışına tepki verir }
harita := Evrişim(resim, filtre)
yaz('Öznitelik haritası (4×4):', 1); Göster(harita)
{ 2×2 en büyük değer havuzlaması }
havuz := []
0'dan 1'e kadar her i için
havuz.Ekle([Maks([harita[2*i][0], harita[2*i][1], harita[2*i+1][0], harita[2*i+1][1]]),
Maks([harita[2*i][2], harita[2*i][3], harita[2*i+1][2], harita[2*i+1][3]])])
yaz('Havuzlamadan sonra (2×2):', 1); Göster(havuz)
yaz('', 1)
yaz('--- B) Filtreyi veriden öğrenmek ---', 1)
Tohum(3)
sinyal := []
1'den 30'a kadar her i için sinyal.Ekle(Rastgele(10))
{ Gizli kural: hedef = sinyalin [1, -2, 1] filtresiyle evrişimi. Ağ bu filtreyi bilmiyor. }
hedef := []; yamalar := []
0'dan Uzunluk(sinyal) - 3'e kadar her i için
başla
yama := [sinyal[i], sinyal[i + 1], sinyal[i + 2]]
yamalar.Ekle(yama)
hedef.Ekle(1 * yama[0] - 2 * yama[1] + 1 * yama[2])
bitir
P := Tensör(yamalar) { 28 × 3: her satır bir yama }
H := Tensör(hedef)
w := RastgeleTensör(3, boş, 0.5, doğru) { öğrenilecek filtre }
opt := Adam([w], 0.1)
1'den 300'e kadar her adım için
başla
opt.SıfırlaGrad
kayıp := OKH(P @ w, H)
kayıp.GeriYay
opt.Adım
eğer adım = 1 veya adım mod 100 = 0 ise
yaz(Biçimle('Adım {:3}: kayıp {:9.4} filtre ({:.3}, {:.3}, {:.3})', adım, kayıp.Değer, w[0], w[1], w[2]), 1)
bitir
bitir.Programın çıktısı şöyledir:
--- A) Hazır filtreyle dikey kenar bulma ---
Öznitelik haritası (4×4):
27 27 0 0
27 27 0 0
27 27 0 0
27 27 0 0
Havuzlamadan sonra (2×2):
27 0
27 0
--- B) Filtreyi veriden öğrenmek ---
Adım 1: kayıp 98.3248 filtre (0.147, 0.525, -0.366)
Adım 100: kayıp 0.0003 filtre (0.999, -1.997, 0.998)
Adım 200: kayıp 0.0000 filtre (1.000, -2.000, 1.000)
Adım 300: kayıp 0.0000 filtre (1.000, -2.000, 1.000)Ne öğrendik. A bölümünde filtre, koyudan açığa geçilen sütunlarda 27, düz bölgelerde 0 verdi; havuzlamadan sonra bile “kenar solda” bilgisi korunuyor. B bölümü CNN’lerin asıl gücünü gösteriyor: rastgele başlayan filtre, yalnızca girdi–çıktı örneklerine bakarak gizli kuralı [1, -2, 1] tam olarak buldu. Gerçek bir CNN aynı şeyi yüzlerce filtre ve birkaç katmanla yapar. İlk katmanlar kenarları, sonrakiler gözler ve tekerlekler gibi daha karmaşık desenleri öğrenir.
Alıştırma. A bölümünde filtreyi yatay kenar bulacak biçimde ([[-1, -1, -1], [0, 0, 0], [1, 1, 1]]) değiştiriniz. Bu resimde ne çıkar, neden? Sonra üst yarısı koyu, alt yarısı açık bir resim kurup tekrar deneyiniz.
18. Yinelemeli sinir ağı (RNN)
Fikir. Cümleler, ses ve fiyat grafikleri birer dizidir; anlamları sıraya bağlıdır. RNN diziyi eleman eleman okur ve her adımda bir “gizli durum” günceller. Bu durum, ağın o ana kadar okuduklarından aklında tuttuğu özettir. Her adımda aynı ağırlıklar kullanılır, bu yüzden ağ her uzunlukta diziyi okuyabilir.
program YinelemeliAğ
{ Yinelemeli sinir ağı (RNN): bir diziyi eleman eleman okur ve bir "gizli durum" h taşır.
Her adımda h ← tanh(x_t · Wx + h @ Wh + b) hesaplanır; h, o ana kadar okunanların özetidir.
Dizinin sonunda çıktı h'den üretilir: ŷ = h @ Wç + bç.
Görev: 5 sayılık bir diziyi oku, sonunda İLK sayıyı söyle. Bunu yapabilmesi için ağın
ilk sayıyı 4 adım boyunca gizli durumunda saklamayı öğrenmesi gerekir. }
kullan YZ
sabit
N: tamsayı = 64 { örnek sayısı }
UZUNLUK: tamsayı = 5 { dizi uzunluğu }
GİZLİ: tamsayı = 8 { gizli durum boyutu }
değişken
diziler: ondalık dizisi dizisi
sütun: liste
Xt: liste { Xt[t]: tüm örneklerin t. öğesi, N × 1 tensör }
hedef: tensör
Wx, Wh, b, Wç, bç: tensör
opt: herhangi
kayıp, tahmin: tensör
i, t, adım: tamsayı
fonksiyon İleri(girdiler: liste; n: tamsayı): tensör
değişken h: tensör; t: tamsayı
başla
h := Sıfırlar(n, GİZLİ)
0'dan UZUNLUK - 1'e kadar her t için
h := (girdiler[t] @ Wx + h @ Wh + b).Tanh
dön h @ Wç + bç
bitir
fonksiyon Sütunlar(D: ondalık dizisi dizisi): liste
değişken t: tamsayı
başla
sonuç := []
0'dan UZUNLUK - 1'e kadar her t için
sonuç.Ekle(Tensör(Eşle(D, fonksiyon(d: ondalık dizisi) => [d[t]])))
bitir
başla
Tohum(11)
diziler := []
1'den N'ye kadar her i için
başla
sütun := []
1'den UZUNLUK'a kadar her t için sütun.Ekle(Rastgele(200) / 100 - 1) { [-1, 1) }
diziler.Ekle(sütun)
bitir
Xt := Sütunlar(diziler)
hedef := Tensör(Eşle(diziler, fonksiyon(d: ondalık dizisi) => [d[0]]))
Wx := RastgeleTensör(1, GİZLİ, 0.5, doğru)
Wh := RastgeleTensör(GİZLİ, GİZLİ, 0.3, doğru)
b := Sıfırlar(GİZLİ, boş, doğru)
Wç := RastgeleTensör(GİZLİ, 1, 0.5, doğru)
bç := Sıfırlar(1, boş, doğru)
opt := Adam([Wx, Wh, b, Wç, bç], 0.02)
1'den 600'e kadar her adım için
başla
opt.SıfırlaGrad
kayıp := OKH(İleri(Xt, N), hedef)
kayıp.GeriYay { zaman içinde geri yayılım: hata 5 adımın hepsine geri dağıtılır }
opt.Adım
eğer adım = 1 veya adım mod 150 = 0 ise
yaz(Biçimle('Adım {:3}: kayıp {:.4}', adım, kayıp.Değer), 1)
bitir
yaz('Hiç görülmemiş diziler:', 1)
diziler := [[0.8, -0.3, 0.5, -0.9, 0.1], [-0.6, 0.9, 0.9, 0.2, -0.4], [0.1, 0.7, -0.8, 0.6, 0.9]]
tahmin := İleri(Sütunlar(diziler), 3)
0'dan 2'ye kadar her i için
yaz(Biçimle(' {} → ağın cevabı {:.2} (doğrusu {})', diziler[i], tahmin[i, 0], diziler[i][0]), 1)
bitir.Programın çıktısı şöyledir:
Adım 1: kayıp 0.2896
Adım 150: kayıp 0.0004
Adım 300: kayıp 0.0012
Adım 450: kayıp 0.0006
Adım 600: kayıp 0.0001
Hiç görülmemiş diziler:
[0.8, -0.3, 0.5, -0.9, 0.1] → ağın cevabı 0.79 (doğrusu 0.8)
[-0.6, 0.9, 0.9, 0.2, -0.4] → ağın cevabı -0.59 (doğrusu -0.6)
[0.1, 0.7, -0.8, 0.6, 0.9] → ağın cevabı 0.11 (doğrusu 0.1)Ne öğrendik. Ağ ilk sayıyı dört adım boyunca “hafızasında” taşımayı öğrendi ve hiç görmediği dizilerde de doğru cevap verdi. Kaybın 300. adımda kısa süre yükselmesi normaldir: Adam bazen bir vadiden taşar, sonra geri döner. Düz RNN’lerin bilinen zayıflığı uzun dizilerdir. Hata geriye doğru yayılırken her adımda küçülür (kaybolan gradyan) ve ağ uzak geçmişi unutur. LSTM ve GRU hücreleri bu sorun için geliştirildi.
Alıştırma. UZUNLUK sabitini 15 yapınız. Kayıp aynı hızla düşüyor mu? Sonra görevi “dizinin toplamını söyle” olarak değiştiriniz; hangisi RNN için daha kolay, neden?
19. Otokodlayıcı
Fikir. Otokodlayıcı girdiyi çıktıda yeniden üretmeyi öğrenir; hedef girdinin kendisi olduğu için etiket gerekmez. İşin püf noktası ortadaki dar boğazdır. Kodlayıcı girdiyi birkaç sayılık bir koda sıkıştırır, çözücü bu koddan girdiyi geri kurar. Boğazdan sığmak için ağ verinin özünü bulmak zorundadır. Bu yüzden otokodlayıcılar sıkıştırmada, gürültü temizlemede ve “bu örnek tuhaf” demek gereken anomali tespitinde kullanılır.
program Otokodlayıcı
{ Otokodlayıcı: girdiyi kendisine kopyalamayı öğrenen bir ağ — ama ortada dar bir boğaz vardır.
Kodlayıcı girdiyi küçük bir koda sıkıştırır, çözücü koddan girdiyi geri kurar.
Etiket gerekmez: hedef girdinin kendisidir. Boğazdan geçebilmek için ağ verinin özünü yakalamak zorundadır.
Klasik deney: 8 farklı "tek-sıcak" vektörü 3 sayıya sıkıştırmak (8 = 2³, yani ikili kod yeterli). }
kullan YZ
değişken
X, kod, geri: tensör
kodlayıcı, çözücü, opt: herhangi
kayıp: tensör
i, adım, isabet: tamsayı
başla
Tohum(21)
X := BirSıcak([0, 1, 2, 3, 4, 5, 6, 7], 8) { 8 × 8 birim matris }
kodlayıcı := Ardışık([Yoğun(8, 3, 'sigmoid')])
çözücü := Ardışık([Yoğun(3, 8)])
opt := Adam(kodlayıcı.Parametreler + çözücü.Parametreler, 0.1)
1'den 1500'e kadar her adım için
başla
opt.SıfırlaGrad
kayıp := ÇaprazEntropi(çözücü.İleri(kodlayıcı.İleri(X)), X) { ÇaprazEntropi softmaksı kendisi uygular }
kayıp.GeriYay
opt.Adım
eğer adım = 1 veya adım mod 500 = 0 ise
yaz(Biçimle('Adım {:4}: yeniden kurma kaybı {:.4}', adım, kayıp.Değer), 1)
bitir
kod := kodlayıcı.İleri(X)
geri := Softmaks(çözücü.İleri(kod))
yaz('Girdi → 3 sayılık kod → geri kurulan', 1)
isabet := 0
0'dan 7'ye kadar her i için
başla
yaz(Biçimle(' {} → ({:.2}, {:.2}, {:.2}) → {}', Birleştir(Eşle(Aralık(8), fonksiyon(k: tamsayı) => eğer k = i ise '1' değilse '0'), ''),
kod[i, 0], kod[i, 1], kod[i, 2], ArgMaks(geri)[i]), 1)
eğer ArgMaks(geri)[i] = i ise isabet := isabet + 1
bitir
yaz('Doğru geri kurulan: ' + isabet + ' / 8', 1)
bitir.Programın çıktısı şöyledir:
Adım 1: yeniden kurma kaybı 2.1617
Adım 500: yeniden kurma kaybı 0.0050
Adım 1000: yeniden kurma kaybı 0.0015
Adım 1500: yeniden kurma kaybı 0.0007
Girdi → 3 sayılık kod → geri kurulan
10000000 → (0.01, 0.35, 0.34) → 0
01000000 → (0.01, 1.00, 1.00) → 1
00100000 → (0.00, 0.00, 1.00) → 2
00010000 → (0.01, 1.00, 0.00) → 3
00001000 → (1.00, 0.01, 0.99) → 4
00000100 → (0.99, 0.99, 0.01) → 5
00000010 → (0.99, 0.99, 0.98) → 6
00000001 → (0.89, 0.01, 0.01) → 7
Doğru geri kurulan: 8 / 8Ne öğrendik. Ağ, kimse ona öğretmeden 3 bitlik ikili koda çok benzeyen bir sıkıştırma icat etti: kodların çoğu 0 ve 1’lerden oluşuyor. Sekizinci köşe (0, 0, 0) yerine ilk vektör için (0,01, 0,35, 0,34) gibi bir ara nokta seçti; çözücü bunu da ayırt edebildiği için bir sorun yoktur. Bu örnekte kodlayıcı ve çözücü iki ayrı Ardışık modeldir. Böylece eğitimden sonra kodlayıcıyı tek başına kullanıp her girdinin kodunu alabiliyoruz.
Alıştırma. Boğazı 2 nöron yapınız (Yoğun(8, 2, 'sigmoid') ve Yoğun(2, 8)). 8 vektörün hepsi yine geri kurulabiliyor mu? Kodlar nasıl yerleşiyor?
20. Transformer ve dikkat mekanizması
Fikir. RNN bir cümleyi sırayla okur ve uzak kelimeleri unutabilir. Dikkat mekanizmasında ise her kelime cümledeki bütün kelimelere aynı anda bakar ve hangisine ne kadar önem vereceğine kendisi karar verir. Her kelime üç vektör üretir: sorgu (“ne arıyorum?”), anahtar (“bende ne var?”) ve değer (“ne aktarırım?”). Sorgusu bir anahtara ne kadar benzerse o kelimenin değerinden o kadar çok pay alır. ChatGPT ve Claude gibi dil modellerinin temelindeki Transformer, bu işlemi katman katman yığarak kurulur.
program DikkatMekanizması
{ Transformer'ların kalbi: ölçeklenmiş nokta çarpımı dikkati.
Her kelime üç vektör üretir: Sorgu (Q: "ne arıyorum?"), Anahtar (K: "bende ne var?"), Değer (V: "ne aktarırım?").
ağırlıklar = softmaks(Q Kᵀ / √d) çıktı = ağırlıklar @ V
Bir kelimenin sorgusu hangi kelimenin anahtarına benziyorsa, o kelimenin değerinden daha çok pay alır.
Böylece her kelime cümlenin geri kalanından bağlamını toplar.
Bölüm A: elle seçilmiş küçük vektörlerle "o" zamirinin kime baktığını görelim.
Bölüm B: Q ve K izdüşümlerini veriden öğrenerek dikkatin bir görevi çözmesini sağlayalım. }
kullan YZ, Matematik
değişken
kelimeler: metin dizisi
E, Wq, Wk, Q, K, V, A: tensör
i, adım: tamsayı
kayıp: tensör
opt: herhangi
girdi, hedef, çıkış: tensör
fonksiyon Dikkat(Q, K: tensör): tensör
başla
dön Softmaks(Q @ K.T / Kök(K.Şekil[1]))
bitir
başla
yaz('--- A) "kedi sütü içti çünkü o acıkmıştı" ---', 1)
kelimeler := ['kedi', 'sütü', 'içti', 'çünkü', 'o', 'acıkmıştı']
{ 4 boyutlu temsiller: [canlı mı, nesne mi, eylem mi, zamir mi] }
E := Tensör([[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.2, 1.0, 0.0],
[0.0, 0.0, 0.3, 0.0],
[0.3, 0.0, 0.0, 1.0],
[0.4, 0.0, 1.0, 0.0]])
{ Elle kurulmuş izdüşümler: zamir özelliği olan kelimeler "canlı" bir şey arasın }
Wq := Tensör([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 0], [3, 0, 0, 0]])
Wk := Tensör([[3, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]])
Q := E @ Wq
K := E @ Wk
A := Dikkat(Q, K)
yaz('Dikkat ağırlıkları (satır: bakan kelime, sütun: bakılan kelime)', 1)
yaz(' ' + Birleştir(Eşle(kelimeler, fonksiyon(k: metin) => Biçimle('{:10}', k)), ''), 1)
0'dan 5'e kadar her i için
yaz(Biçimle('{:11} ', kelimeler[i]) + Birleştir(Eşle(Aralık(6), fonksiyon(k: tamsayı) => Biçimle('{:10.2}', A[i, k])), ''), 1)
yaz('"o" en çok şuna bakıyor: ' + kelimeler[ArgMaks(A.Satır(4))], 1)
yaz('', 1)
yaz('--- B) Dikkati öğrenmek: "her konum bir sonraki konuma baksın" ---', 1)
{ 4 konum; her konumun temsili kendi konumunun tek-sıcak vektörü. Hedef: her konum,
bir sonraki konumun değerini getirsin (sonuncu başa dönsün). Wq ve Wk rastgele başlar. }
Tohum(8)
girdi := BirSıcak([0, 1, 2, 3], 4)
{ V'nin her sütunu ayrı bir eğitim örneği (aynı konumlar, farklı değerler).
Tek örnekle ağ "hileli" karışımlar bulabilir; çeşitli örnekler gerçek kuralı zorlar. }
V := Tensör([[5, 2, 0], [1, 8, 4], [7, 3, 9], [3, 6, 1]])
hedef := Tensör([[1, 8, 4], [7, 3, 9], [3, 6, 1], [5, 2, 0]])
Wq := RastgeleTensör(4, 4, 0.5, doğru)
Wk := RastgeleTensör(4, 4, 0.5, doğru)
opt := Adam([Wq, Wk], 0.1)
1'den 300'e kadar her adım için
başla
opt.SıfırlaGrad
çıkış := Dikkat(girdi @ Wq, girdi @ Wk) @ V
kayıp := OKH(çıkış, hedef)
kayıp.GeriYay
opt.Adım
eğer adım = 1 veya adım mod 100 = 0 ise
yaz(Biçimle('Adım {:3}: kayıp {:.4} 1. örneğin çıktıları {}', adım, kayıp.Değer,
Eşle(Aralık(4), fonksiyon(k: tamsayı) => Yuvarla(çıkış[k, 0], 2))), 1)
bitir
A := Dikkat(girdi @ Wq, girdi @ Wk)
yaz('1. örneğin hedefi [1, 7, 3, 5]. Öğrenilen dikkat ağırlıkları:', 1)
0'dan 3'e kadar her i için
yaz(Biçimle(' konum {} → ', i) + Birleştir(Eşle(Aralık(4), fonksiyon(k: tamsayı) => Biçimle('{:6.2}', A[i, k])), ''), 1)
{ Öğrenilen şey değerler değil bakılacak yer: yepyeni değerlerle de çalışır }
V := Tensör([[10.0], [20.0], [30.0], [40.0]])
çıkış := A @ V
yaz('Yeni değerler [10, 20, 30, 40] → ' + Eşle(Aralık(4), fonksiyon(k: tamsayı) => Yuvarla(çıkış[k, 0], 1)), 1)
bitir.Programın çıktısı şöyledir:
--- A) "kedi sütü içti çünkü o acıkmıştı" ---
Dikkat ağırlıkları (satır: bakan kelime, sütun: bakılan kelime)
kedi sütü içti çünkü o acıkmıştı
kedi 0.17 0.17 0.17 0.17 0.17 0.17
sütü 0.17 0.17 0.17 0.17 0.17 0.17
içti 0.13 0.13 0.22 0.16 0.13 0.22
çünkü 0.16 0.16 0.18 0.16 0.16 0.18
o 0.87 0.01 0.01 0.01 0.04 0.06
acıkmıştı 0.13 0.13 0.22 0.16 0.13 0.22
"o" en çok şuna bakıyor: kedi
--- B) Dikkati öğrenmek: "her konum bir sonraki konuma baksın" ---
Adım 1: kayıp 7.1553 1. örneğin çıktıları [3.73, 4.06, 3.93, 3.99]
Adım 100: kayıp 0.0001 1. örneğin çıktıları [1.0, 7.0, 3.0, 5.0]
Adım 200: kayıp 0.0000 1. örneğin çıktıları [1.0, 7.0, 3.0, 5.0]
Adım 300: kayıp 0.0000 1. örneğin çıktıları [1.0, 7.0, 3.0, 5.0]
1. örneğin hedefi [1, 7, 3, 5]. Öğrenilen dikkat ağırlıkları:
konum 0 → 0.00 1.00 0.00 0.00
konum 1 → 0.00 0.00 1.00 0.00
konum 2 → 0.01 0.01 0.00 0.99
konum 3 → 1.00 0.00 0.00 0.00
Yeni değerler [10, 20, 30, 40] → [20.0, 30.0, 39.7, 10.0]Ne öğrendik. A bölümünde “o” zamiri ağırlığının %87’sini “kedi”ye verdi, yani kimin acıktığını bağlamdan çıkardı. Sorgusu boş olan kelimeler (kedi, sütü) herkese eşit (1/6 ≈ 0,17) baktı. B bölümünde ağ, rastgele başlayan Wq ve Wk’den “bir sonrakine bak” desenini öğrendi. Öğrenilen şey değerler değil nereye bakılacağı olduğu için desen yepyeni değerlerle de çalışıyor. Programdaki yoruma da dikkat etmekte yarar vardır: tek bir örnekle eğitilen ağ, hedefi tutturan ama genellemeyen “hileli” karışımlar bulabilir. V’yi tek sütuna indirip deneyerek bunu kendiniz görebilirsiniz. Gerçek bir Transformer bu işlemi birden fazla “kafa” ile paralel yapar, araya Yoğun katmanlar koyar ve onlarca kez üst üste yığar.
Alıştırma. B bölümünde hedefi “bir önceki konuma bak” olarak değiştiriniz (hedef satırlarını bir aşağı kaydırınız). Ardından V’yi tek sütun [[5], [1], [7], [3]] yapıp eğitiniz. Öğrenilen ağırlıklar ve yeni değerlerle sonuç nasıl değişiyor?