Ana içeriğe geç

LSTM Modelleri: Uzun Kısa Süreli Bellek Ağlarına Kapsamlı Bir Rehber

LSTM ağlarının iç işleyişine hâkim olun; modern LLM’lerin temeli. Kapı mekanizmalarını, gradyanları keşfedin ve PyTorch ile bir duygu sınıflandırıcı oluşturun.
· 15 dk. oku

Yapay Zeka ile Keşfedin

Standart yinelemeli sinir ağları hafıza konusunda zorlanır. Diziler 20-30 zaman adımını aştıkça, gradyanlar geri yayılım sırasında ya sıfıra yaklaşır ya da patlar ve ağ girdinin uzak kısımlarını birbirine bağlama yeteneğini kaybeder. Hochreiter ve Schmidhuber bu sorunu, Uzun Kısa Süreli Bellek ağlarını tanıttıkları 1997 tarihli makalelerinde ele aldılar.

Bu rehberde, Python’da pratik uygulamaya geçmeden önce LSTM’nin iç yapısını adım adım ele alacağım. Son bölümlerde LSTM’leri Transformer’larla karşılaştırarak kullanım senaryonuza uygun mimariyi seçmenize yardımcı olacağım.

LSTM modellerinin temelini oluşturan sinir ağlarını eğitmek için PyTorch kullanımı konusunda uygulamalı deneyim edinmek üzere PyTorch ile Derin Öğrenmeye Giriş kursumuzu şiddetle öneririm; böylece PyTorch’u kullanarak sinir ağlarını eğitme pratiği kazanabilirsiniz.

LSTM Modeli Nedir?

LSTM, Long Short-Term Memory’nin kısaltmasıdır; girdiğin çok daha önceki kısımlarındaki bağlamın hâlâ önemli olduğu dizileri işlemek üzere tasarlanmış bir yinelemeli sinir ağı (RNN) türüdür.

LSTM’lerin temel fikri, ağ boyunca bilgi akışını düzenleyen bir kapı (gate) mekanizmasıdır. Tüm sinyallerin eşit şekilde geçmesine izin vermek yerine, kapılar bağlamın ne zaman tutulacağını ve ne zaman bırakılacağını öğrenir. 

Bu seçici bellek, LSTM’lerin yüzlerce zaman adımı boyunca bağımlılıkları takip etmesini sağlar; bu da dil modelleme, konuşma tanıma ve zaman serisi tahmini için faydalı olmuştur. Bu mimarinin neden var olduğunu anlamak için, standart RNN’lerde neyin bozulduğunu görmek yararlıdır.

RNN’lerde kaybolan gradyan sorunu

Yinelemeli ağlar bilgiyi gizli durum aracılığıyla bir zaman adımından sonrakine aktarır. Her adımda bu durum, öğrenilmiş ağırlıklarla çarpılır, güncellenir ve ileriye iletilir. Eğitmek için hata sinyallerini tüm bu adımlar boyunca geriye göndermek gerekir ve her geri geçiş sinyali biraz küçültür. Yeterince çok adım sonrasında geriye hiçbir şey kalmaz.

Fotokopinin fotokopisini çekmeye benzer. Birkaç kez yaparsanız kopya iyi görünür. Elli kez yaparsanız metni zar zor okuyabilirsiniz. RNN’ler bu duvara yaklaşık 20-30 zaman adımında çarpar; bu yüzden daha uzun dizilerde zorlanırlar.

LSTM’ler uzun vadeli bağımlılıkları nasıl çözer

LSTM’ler, standart gizli duruma paralel çalışan hücre durumu adı verilen ayrı bir bellek hattı ekler. Fark yapısaldır: Hücre durumundaki bilgi tekrar eden çarpımlar yerine toplamalar yoluyla akar; bu yüzden aynı şekilde bozulmaz.

Bu belleğe neyin girip çıktığını kontrol etmek için LSTM’ler kapılar kullanır. Her kapı, 0 ile 1 arasında değerler çıktılama öğrenen, bir filtre gibi davranan küçük bir sinir ağıdır. 0’a yakın bir değer bilgiyi engeller; 1’e yakın bir değer geçirir. Ağ, eğitim sırasında hangi kalıpların uzun diziler boyunca hatırlanmaya değer olduğunu, hangilerinin atılabileceğini öğrenir.

Bu kapı mekanizması LSTM’leri düz RNN’lerden ayırır. Tüm bilgiyi aynı yoldan zorla geçirmek yerine, LSTM’ler yüzlerce zaman adımı boyunca bağlamı seçerek koruyabilir. Bir sonraki bölümde bu kapıların tam olarak nasıl çalıştığını ve hücre durumunu güncellemek için nasıl birleştiğini ele alacağız.

LSTM model kullanım alanları

LSTM’ler, sıralı verinin önemli olduğu her yerde karşımıza çıkar. Alışıldık NLP ve tahmin uygulamalarının ötesinden birkaç örnek:

  • Bankalar, kural tabanlı sistemlerin kaçırdığı dolandırıcılık kalıplarını yakalamak için işlem dizilerini LSTM’lerden geçirir
  • Hastaneler, eşiğe dayalı uyarılardan daha hızlı kardiyak anormallikleri yakalamak için çift yönlü LSTM’lerle EKG akışlarını izler
  • Akıllı şehir platformları sensör akışlarından trafik ve enerji talebini tahmin eder, artışlar olmadan önce kaynakları ayarlar
  • Fabrikalar, bir şey bozulmadan önce bakımı planlamak için ekipman titreşimini zaman içinde takip eder

LSTM Mimarisi: Kapılar ve Bellek Hücreleri

Bu bölüm LSTM’nin iç yapısını ayrıntılı biçimde ele alır. Doğrudan uygulamaya geçmek istiyorsanız bir sonraki bölüme atlayın. Buradaki mimari ayrıntılar LSTM kullanmak için şart değildir, ancak hata ayıklarken veya ayar yaparken yardımcı olur.

LSTM’lerin nasıl çalıştığını anlamak için somut bir örnek üzerinden ilerleyeceğiz: "The clouds are dark..." dizisini işleyip bir sonraki kelimeyi tahmin etmek. Her bir bileşen, gerçek veriye ne yaptığını gördüğünüzde daha anlamlı hale gelir.

LSTM’lerde hücre durumu ve gizli durum

Bir LSTM ağ boyunca akan iki ayrı vektörü tutar: hücre durumu ve gizli durum.

Hücre durumu uzun vadeli depolama gibi davranır. Her adımda kontrollü değişikliklerle bilgiyi zaman adımları arasında taşır. Dizimizi işlerken, hücre durumu "hava durumuyla ilgili isim görüldü" bilgisini "clouds" sonrasında depolayıp bu bağlamı "are" boyunca koruyabilir, "dark" geldiğinde de bu kalıbı pekiştirir.

Gizli durum her zaman adımında hücrenin çıktısıdır. Tahminlere ve bir sonraki zaman adımının hesaplamalarına girer. "Dark" işlendiğinde, gizli durum, tahmin katmanının kullanabileceği "hava koşullarını tanımlayan sıfat" gibi bir şeyi kodlar.

LSTM Cell

Neden iki ayrı hat? Tek bir durum hem uzun vadeli bağlamı tutmalı hem de faydalı çıktılar üretmelidir. Bu hedefler çelişir: Kararlı bellek minimum değişim gerektirirken, faydalı çıktılar dönüşüm gerektirir. Ayrı tutmak, her bir hattın kendi amacına göre optimize olmasını sağlar.

Her zaman adımında hücre üç girdi alır ve iki çıktı üretir:

Girdiler:

  • Önceki hücre durumu (C_{t-1}): ağın şu ana kadar hatırladıkları

  • Önceki gizli durum (h_{t-1}): bir önceki zaman adımının çıktısı

  • Mevcut girdi (x_t): "clouds" veya "dark" gibi yeni belirteç

Çıktılar:

  • Güncellenmiş hücre durumu (C_t): bu belirteç işlendiğindeki bellek

  • Yeni gizli durum (h_t): bu zaman adımının çıktısı

LSTM kapıları nasıl hesaplar

Hücre içinde olan biteni dört bileşen kontrol eder. Üçü 0 ile 1 arasında değerler veren ve filtre gibi davranan kapılardır. Dördüncüsü ise yeni içerik öneren adaydır.

"Clouds"’un ardından "are" geldiğinde, her bileşenin bir görevi vardır:

  • Unutma kapısı (f_t): "clouds" bağlamını tutmalı mıyız? Bağlaç fiillerle takip edilen hava durumu isimleri genelde önemlidir, bu yüzden unutma kapısı hücre durumunun çoğunu korumak için 1’e yakın değerler üretir.

  • Giriş kapısı (i_t): "are" belleğe ne kadar katkı yapmalı? Bağlaç fiiller daha az öngörücü sinyal taşır, bu nedenle giriş kapısı daha düşük değerler verir.

  • Aday (C̃_t): "are" ne tür yeni bilgiler ekleyebilir? Aday içerik önerir, ancak giriş kapısı bunu aşağı ölçekler.

  • Çıkış kapısı (o_t): Gizli durumda ne görünmeli? Çıkış kapısı, hücre durumunu filtreleyerek h_t üretir.

How LSTM gates compute

Her bileşen çıktısını aynı şekilde hesaplar:

  1. h_{t-1} ve x_t tek bir vektörde birleştirilir

  2. Öğrenilmiş bir ağırlık matrisiyle çarpılır

  3. Sabit terim eklenir

  4. Bir aktivasyon fonksiyonu uygulanır

Kapılar değerleri 0-1 aralığına sıkıştıran sigmoid aktivasyonu kullanır. Aday ise tanh kullanır ve hücre durumuna hem artış hem azalış önerebilmek için -1 ile 1 arasında değerler çıktılar.

Denklemler

Unutma kapısı (sigmoid → 0’dan 1’e):

Giriş kapısı (sigmoid → 0’dan 1’e):

Çıkış kapısı (sigmoid → 0’dan 1’e):

Aday (tanh → -1’den +1’e):

Her bileşenin kendi ağırlık matrisi vardır (W_f, W_i, W_c, W_o); böylece girdi içindeki farklı kalıplara yanıt vermeyi öğrenirler.

Filtreleme nasıl çalışır

Kapı çıktıları vektörlerle pozisyon bazında çarpılır (öğe düzeyinde çarpım, ⊙ ile gösterilir). Her boyut bağımsız olarak filtrelenir.

Bir kapı birinci pozisyon için 0,9 üretirse, o boyut değerinin %90’ı korunur. Dördüncü pozisyon için 0,0 üretirse, o boyut silinir. "Clouds are" sonrasında "dark" işlenirken, unutma kapısı isim/hava durumu bağlamını tutan boyutlar için yüksek, alakasız daha önceki bağlamı tutan boyutlar için düşük değerler üretebilir.

Unut, ekle ve çıkış kapıları arasında bilgi akışı

Bileşenler, hücre durumunu güncellemek ve gizli durumu üretmek için üç adımda birleşir.

Adım 1: Unut

Unutma kapısı önceki hücre durumunu filtreler:

"Dark" işlenirken, unutma kapısı "clouds"dan gelen hava durumu bağlamını korurken, "The" artikeline ait izleri takip eden boyutları temizler.

Adım 2: Ekle

Giriş kapısı adayın önerisini ölçekler ve sonra filtrelenmiş hücre durumuna ekler:

Aday, "hava durumu ismini niteleyen sıfat" gibi bir öneride bulunabilir ve giriş kapısı bunun belleğe ne kadar güçlü yazılacağına karar verir. Bu toplamsal (çarpımsal değil) güncelleme, eğitim sırasında gradyan akışına yardımcı olur.

Adım 3: Çıkış

Hücre durumu tanh’ten geçirilir, ardından çıkış kapısı gizli duruma neyin dönüşeceğini filtreler:

Tanh değerleri sınırlı tutar. Çıkış kapısı, biriken belleğin hangi yönlerinin bu zaman adımındaki tahminlerde görünür olacağını seçer.

Tüm dizinin özeti:

İşte 4 boyutlu bir hücre durumu ile basitleştirilmiş bir izlek:

Adım

Girdi

Hücre durumu (basitleştirilmiş)

Ne olur

1

"The"

[0.1, 0.0, 0.2, 0.0]

Asgari depolama; artikeller az öngörücü sinyal taşır

2

"clouds"

[0.1, 0.7, 0.2, 0.3]

Giriş kapısı 2 ve 4. boyutlar için açılır; isim kalıbını depolar

3

"are"

[0.1, 0.6, 0.2, 0.3]

Unutma kapısı ≈ 0.9 bağlamı korur; bağlaç fiil az katkı yapar

4

"dark"

[0.1, 0.8, 0.5, 0.6]

Sıfat, 2, 3 ve 4. boyutlardaki sinyali güçlendirir

4. zaman adımına gelindiğinde 2-4. boyutlar bir kalıp biriktirmiştir. Gizli durum h_4 bir tahmin katmanına (LSTM’nin parçası değildir) girer ve sözlük olasılıkları üretir. Bu birikmiş kalıp, modelin "rain" veya "sky" gibi kelimeleri tercih etmesine yardımcı olur.

Ağ hava durumunu anlamaz. İstatistiksel korelasyonları öğrenir: Belirli girdi dizileri belirli çıktılardan önce gelme eğilimindedir.

Eğitim sırasında gradyan akışı

Hücre durumunun toplamsal yapısı, standart RNN’lere göre daha doğrudan bir gradyan yolu sağlar. Gradyanların neral ağlarda nasıl yayıldığına aşina değilseniz, geri yayılım eğitimi temel konuları kapsar.

Standart RNN’lerde gradyanlar her adımda ağırlık matrislerinden akar. Tekrarlanan çarpımlar bileşik etki yaratır: Ortalama 0,9 ağırlıklarla 100 adım sonra gradyan 0.9^100 ≈ 0.00003’e küçülür.

LSTM’ler hücre durumu boyunca alternatif bir yol sunar. C_t’nin C_{t-1}’e göre türevi:

∂C_t/∂C_{t-1} = f_t

Bu bir ağırlık matrisi değil, unutma kapısı değeridir. Unutma kapıları 1’e yakın kaldığında, gradyanlar daha az zayıflamayla geçer. Kapılar ortalama 0,95 ise, 100 adım sonra: 0.95^100 ≈ 0.006. Hâlâ azalmış, ama kullanılabilir.

Boyut

RNN

LSTM

Gradyan yolu

Yalnızca ağırlık matrisleri

Hücre durumu boyunca ek bir yol

100 adımlık gradyan

~10^-5 (kaybolmuş)

~10^-2 ile 10^-1 (kullanılabilir)

Pratik bellek

~10-20 zaman adımı

~50-200 zaman adımı

Pratik not: Unutma kapısı önyargıları (bias) sıklıkla 1,0 veya daha yüksek başlatılır; bu da başlangıç çıktısını "her şeyi hatırla" yönüne iter. Bu, ağın eğitimin erken safhasında uzun vadeli kalıpları öğrenmesine yardımcı olur.

LSTM’ler kaybolan gradyan sorununu ortadan kaldırmak yerine hafifletir. İyileşme, standart yolların yanında bir gradyan otoyolu sağlayan toplamsal hücre durumu güncellemesinden gelir.

Python ile LSTM Modelleri Nasıl Uygulanır

Önceki bölümde bir LSTM hücresinde olanlar ele alındı: bilgiyi filtreleyen kapılar, bağlamı koruyan hücre durumu ve çıktıları üreten gizli durum. Şimdi PyTorch’un nn.LSTM modülünün tüm bunları tek bir yapıda nasıl topladığını ve bunu film yorumlarını olumlu/olumsuz olarak sınıflandırmak için nasıl kullandığını göreceğiz.

Kodu çalıştırma ipucu: 50 epoch boyunca CPU’da eğitmek uzun sürer. Google Colab veya Kaggle Notebooks’u GPU hızlandırmasıyla kullanarak daha hızlı sonuç alın. Tam betik bir GitHub Gist olarak mevcuttur.

PyTorch ile bir LSTM Sınıflandırıcı Oluşturma

LSTM ile metin sınıflandırma şu deseni izler: kelimeleri vektörlere dönüştürün, LSTM’nin bunları sırasıyla işlemesine izin verin, ardından tahmin için son çıktıyı kullanın.

Kelimeleri vektörlere gömmek

Sinir ağları sürekli sayılar üzerinde çalışır, ancak kelimeler ayrık sembollerdir. "Terrible" kelimesini doğrudan matris çarpımına besleyemeyiz. Çözüm bir gömme (embedding) katmanıdır: her kelimenin öğrenilebilir bir vektöre eşlendiği bir arama tablosu.

self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=padding_idx)

Eğer embedding_dim=32 seçerseniz, her kelime 32 boyutlu bir vektöre dönüşür. Eğitimin başında bu vektörler rastgele olur. Model öğrendikçe, benzer duyguya sahip kelimeler bu vektör uzayında birbirine yaklaşır. "Terrible", "awful" ve "boring" birbirine yakın konumlanabilirken, "excellent" ve "amazing" başka yerde kümelenir.

padding_idx argümanı, dolgu belirteçlerinin sıfır vektörler olarak kalmasını söyler; böylece modeli etkilemezler.

LSTM mimarisini tanımlamak

self.lstm = nn.LSTM(
	input_size=embedding_dim,
	hidden_size=hidden_size,
	num_layers=num_layers,
	batch_first=True,
	dropout=dropout if num_layers > 1 else 0,
	bidirectional=bidirectional,
)

Bu tek satır, önceki bölümdeki tüm kapı mekanizmasını oluşturur. hidden_size hücre durumu ve gizli durumun ne kadar bilgi taşıyabileceğini belirler. Daha büyük değerler modelin daha fazla kalıbı takip etmesine izin verir, fakat hesaplama maliyetini artırır.

batch_first=True girdi şeklinin (batch, sequence_length, embedding_dim) olduğu anlamına gelir. LSTM her pozisyonu soldan sağa işler ve her adımda iç durumlarını günceller.

Diziyi işlemek

"This movie was absolutely terrible" işlendiğinde, LSTM tüm kelimeleri görmüş olur. Son gizli durumu modelin bu diziden çıkarmayı öğrendiği her şeyi biriktirmiştir. Duygu için, bu durum "sonda güçlü olumsuz bir sıfat, önceki nötr içeriği geçersiz kılar" gibi bir şeyi kodlayabilir.

def forward(self, x):
	embedded = self.embedding(x)
	lstm_out, (h_n, c_n) = self.lstm(embedded)
	h_final = h_n[-1]
	return self.fc(h_final)

LSTM hem zaman adımı başına çıktıları (lstm_out) hem de son durumları (h_n, c_n) döndürür. Sınıflandırma için yalnızca h_n[-1], yani son belirteçten sonraki gizli duruma ihtiyaç duyarız. Bunu doğrusal bir katman sınıf skorlarına dönüştürür.

Bir yorumu modelden izlemek

"This movie was terrible" sınıflandırıcıya girdiğinde:

  1. Her kelime gömmesini alır: (32,) şekilli dört vektör

  2. LSTM bunları sırayla işler. "This" bir miktar hücre durumu başlatır. "Movie" bunu biraz günceller. "Was" nötrdür ve az değiştirir. "Terrible" ise unutma kapısını alakasız bağlamı temizlemeye, giriş kapısını güçlü olumsuz sinyali yazmaya tetikler.

  3. Son gizli durum birikmiş kalıbı yakalar.

  4. Doğrusal katman bunu iki skora eşler: biri olumlu, diğeri olumsuz için

Model dili anlamaz. İstatistiksel kalıpları öğrenir: belirli kelime dizileri belirli etiketlerle ilişkilidir.

Tam model sınıfı

Aşağıda, adım adım oluşturduğumuz LSTMClassifier sınıfının tamamını görebilirsiniz:

import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
from datasets import load_dataset
from collections import Counter
import numpy as np
 
torch.manual_seed(42)
np.random.seed(42)
 
 
class LSTMClassifier(nn.Module):
    def __init__(
        Self,
        Vocab_size,
        Embedding_dim,
        Hidden_size,
        Output_size,
        num_layers=1,
        dropout=0.0,
        bidirectional=False,
        padding_idx=0,
    ):
        super().__init__()
        self.hidden_size = hidden_size
    	self.num_layers = num_layers
    	self.bidirectional = bidirectional
 
    	self.embedding = nn.Embedding(
	    vocab_size, embedding_dim, padding_idx=padding_idx
        )
    	self.lstm = nn.LSTM(
            input_size=embedding_dim,
            hidden_size=hidden_size,
            num_layers=num_layers,
            batch_first=True,
            dropout=dropout if num_layers > 1 else 0,
            bidirectional=bidirectional,
    	)	
 
    	fc_input_size = hidden_size * 2 if bidirectional else hidden_size
    	self.fc = nn.Linear(fc_input_size, output_size)
 
    def forward(self, x):
    	embedded = self.embedding(x)
    	lstm_out, (h_n, c_n) = self.lstm(embedded)
 
    	if self.bidirectional:
            h_final = torch.cat([h_n[-2], h_n[-1]], dim=1)
    	else:
            h_final = h_n[-1]
 
    	return self.fc(h_final)

Çift yönlü seçenek iki LSTM çalıştırır: biri soldan sağa, diğeri sağdan sola. Son gizli durumları birleştirilir ve modele her iki yönden bağlam sağlar. Bu, anlamın sonrasında gelenlere bağlı olduğu durumlarda yardımcı olur; örneğin "not bad" ifadesinde "not" ancak "bad" ile birlikte anlam kazanır.

Metni modele hazırlamak

LSTM’ler dize değil, tamsayı dizileri ister. Önişleme ham metni üç adımda dönüştürür: 

  1. Kelime hazinesi oluşturup kelimeleri indekslere eşleyin
  2. Her incelemeyi indeks dizisine dönüştürün
  3. Dizileri tekdüze uzunluğa kadar doldurun
def build_vocab(texts, max_vocab_size=10000):
	counter = Counter()
	for text in texts:
    		counter.update(text.lower().split())
 
	vocab = {"<pad>": 0, "<unk>": 1}
	for word, _ in counter.most_common(max_vocab_size - 2):
    		vocab[word] = len(vocab)
	return vocab

Dolguyu 0 indeksine, bilinmeyen kelimeleri 1 indeksine ayırırız. Sözlük boyutunu sınırlamak, anlamlı gömmeler öğrenmek için yeterli örneği olmayan nadir kelimeleri filtreler.

def tokenize_and_pad(texts, vocab, max_length=200):
	sequences = []
	for text in texts:
    		tokens = text.lower().split()
    		ids = [vocab.get(t, vocab["<unk>"]) for t in tokens[:max_length]]
    		ids = ids + [vocab["<pad>"]] * (max_length - len(ids))
    		sequences.append(ids)
	return torch.tensor(sequences, dtype=torch.long)

max_length’e kırpmak, LSTM’nin ne kadar geriye hatırlaması gerektiğini sınırlar. Daha uzun diziler daha fazla bağlamı korur, ancak hesaplama ve bellek maliyetini artırır. Film yorumlarında genellikle 100-200 belirteç ana duygu sinyallerini yakalar.

Doldurma, her dizinin aynı uzunlukta olmasını sağlayarak PyTorch’un bunları tek bir tensörde birleştirmesine olanak tanır. Dolgu belirteçleri gömme sonrası sıfır vektörlere dönüşür ve son duruma çok az katkı yapar.

def load_imdb_data(max_samples=10000, max_length=200, max_vocab=10000):
	dataset = load_dataset("imdb")
 
	train_data = dataset["train"].shuffle(seed=42)
	test_data = dataset["test"].shuffle(seed=42)
 
	train_texts = train_data["text"][:max_samples]
	train_labels = train_data["label"][:max_samples]
	test_texts = test_data["text"][: max_samples // 4]
	test_labels = test_data["label"][: max_samples // 4]
 
	vocab = build_vocab(train_texts, max_vocab)
 
	X_train = tokenize_and_pad(train_texts, vocab, max_length)
	X_test = tokenize_and_pad(test_texts, vocab, max_length)
	y_train = torch.tensor(train_labels, dtype=torch.long)
	y_test = torch.tensor(test_labels, dtype=torch.long)
 
	return X_train, y_train, X_test, y_test, vocab

Sözlüğü yalnızca eğitim verisinden oluşturmak bilgi sızıntısını önler. Nadir bir kelime sadece test incelemelerinde görünüyorsa, model onu eğitim sırasında gömmesini öğrenmiş olmak yerine bilinmeyen kabul etmelidir.

LSTM eğitimi ve hiperparametreler

Eğitim döngüsü, modelin ağırlıklarını tahminlerinin doğru değerlerden ne kadar saptığına göre günceller. LSTM’lerde bir ayrıntı diğerlerinden daha çok önem taşır: gradyan kırpma.

def train_model(model, train_loader, test_loader, n_epochs=5, lr=0.001):
	criterion = nn.CrossEntropyLoss()
	optimizer = torch.optim.Adam(model.parameters(), lr=lr)
 
	for epoch in range(n_epochs):
    		model.train()
    		total_loss, correct, total = 0, 0, 0
 
    		for batch_texts, batch_labels in train_loader:
        		optimizer.zero_grad()
        		outputs = model(batch_texts)
        		loss = criterion(outputs, batch_labels)
        		loss.backward()
            		torch.nn.utils.clip_grad_norm_(
	model.parameters(), 
	max_norm=5.0)
        		optimizer.step()
 
        		total_loss += loss.item()
        		_, predicted = outputs.max(1)
        		correct += (predicted == batch_labels).sum().item()
        		total += batch_labels.size(0)

Gradyan kırpma LSTM’ler için neden önemlidir

Daha önce, LSTM’lerin hücre durumuna yapılan toplamsal güncellemelerle kaybolan gradyanları nasıl hafiflettiğini açıkladık. Ancak patlayan gradyanlar risk olmaya devam eder. Gradyanlar çok büyüdüğünde, ağırlık güncellemeleri hedefi ıskalar ve eğitimi dengesizleştirir.

clip_grad_norm_ toplam gradyan büyüklüğünü sınırlar. Gradyanlar max_norm=5.0 değerini aşacak olursa, orantılı olarak aşağı ölçeklenir. Bu, gradyan sinyalini tamamen ortadan kaldırmadan güncellemeleri istikrarlı tutar.

    	model.eval()
    	test_correct, test_total = 0, 0
    	with torch.no_grad():
        	for batch_texts, batch_labels in test_loader:
            		outputs = model(batch_texts)
            		_, predicted = outputs.max(1)
            		test_correct += (predicted == batch_labels).sum().item()
            		test_total += batch_labels.size(0)
 
    	test_acc = test_correct / test_total
    	print(f"Epoch {epoch+1}: Loss={total_loss/len(train_loader):.4f}, "
          	f"Train={correct/total:.3f}, Test={test_acc:.3f}")
 
    return test_acc

model.eval() değerlendirme sırasında dropout’u devre dışı bırakır. torch.no_grad() ise ağırlıkları güncellemediğimiz için gradyan hesaplamasını atlar; bu da bellek tasarrufu sağlar ve çıkarımı hızlandırır.

Hiperparametre seçimi

X_train, y_train, X_test, y_test, vocab = load_imdb_data(
	max_samples=10000, max_length=100, max_vocab=5000
)
 
train_loader = DataLoader(TensorDataset(X_train, y_train), batch_size=64, shuffle=True)
test_loader = DataLoader(TensorDataset(X_test, y_test), batch_size=64)
 
model = LSTMClassifier(
	vocab_size=len(vocab),
	embedding_dim=32,
	hidden_size=32,
	output_size=2,
	padding_idx=vocab["<pad>"],
)
 
final_acc = train_model(model, train_loader, test_loader, n_epochs=50, lr=0.001)

Bu değerler çeşitli ödünleşimleri yansıtır:

  • hidden_size=32: Hızlı denemeler için yeterince küçük. Üretim modelleri genellikle 128-512 kullanır; ancak boyut arttıkça kazançlar azalır ve eğitim yavaşlar.

  • embedding_dim=32: Gizli boyutla eşleştirmek yaygındır ama şart değildir. Daha büyük gömmeler daha ince kelime ayrımları yakalar; parametre sayısını artırır.

  • max_length=100: Duygu sinyallerinin çoğu incelemelerin erken kısımlarında görünür. Daha uzun diziler nüanslı durumlarda yardımcı olur; ancak max_length iki katına çıktığında bellek kullanımı dört katına çıkabilir.

  • batch_size=64: Daha büyük yığınlar daha kararlı gradyan tahminleri sağlar. Daha küçük yığınlar yerel minimumlardan çıkmaya yardımcı olabilir. Tipik aralık 32-128’dir.

  • lr=0.001: Adam’ın varsayılanı çoğu LSTM görevi için çalışır. Kayıp salınıyorsa 0,0001 deneyin. Öğrenme durduysa 0,01 deneyin.

  • n_epochs=50: Modelin yakınsamasi için yeterli. Eğitim doğruluğu artmaya devam ederken test doğruluğu plato yapıyor veya düşüyorsa aşırı uyum sinyali verir.

Model ne öğrenir

Bu yapılandırmayı çalıştırmak şu gibi çıktılar üretir:

...
Epoch 46: Loss=0.4126, Train=0.812, Test=0.789
Epoch 47: Loss=0.4054, Train=0.815, Test=0.791
Epoch 48: Loss=0.3998, Train=0.818, Test=0.788
Epoch 49: Loss=0.3921, Train=0.821, Test=0.793
Epoch 50: Loss=0.3867, Train=0.824, Test=0.795

Eğitim doğruluğu yaklaşık %82’ye ulaşırken test doğruluğu %80 civarında dengelenir. Model hangi kelime kalıplarının duyguyu tahmin ettiğini öğrenir. Sonda yer alan güçlü sıfatlar ağırlık taşır. "Not" gibi olumsuzlayıcılar kutbu tersine çevirir. "Waste of time", "highly recommend" gibi ifadeler güçlü sinyallere dönüşür. Bunların hiçbiri programlanmaz; eğitim verisindeki korelasyonlardan ortaya çıkar.

Asgari ayarla ve küçük bir modelle %80 doğruluk makuldür. Eğitim ve test doğruluğu arasındaki küçük fark, modelin ciddi aşırı uyum olmadan iyi genelleştirdiğini gösterir. Dropout eklemek, GloVe gibi önceden eğitilmiş gömmeler kullanmak veya daha fazla veriyle eğitmek doğruluğu %80’lerin sonuna taşır.

LSTM Model Performansını İyileştirme

Önceki bölümlerdeki temel LSTM mimarisi işe yarar; ancak farklı bir model ailesine geçmeden de doğruluğu artırabilecek çeşitli teknikler vardır. Bu yaklaşımlar binlerce üretim dağıtımı ve araştırma makalesi boyunca doğrulanmıştır.

Çift yönlü LSTM’ler

Standart LSTM’ler dizileri soldan sağa işler ve yalnızca geçmiş belirteçlerden bağlam kurar. Çift yönlü LSTM’ler (BiLSTM) iki paralel geçiş yapar: biri ileri, biri geri. Her zaman adımındaki nihai temsil, öncesini ve sonrasını birleştirir.

self.lstm = nn.LSTM(
	input_size=embedding_dim,
	hidden_size=hidden_size,
	bidirectional=True,  # Run both directions
	batch_first=True,
)
# Output size doubles: hidden_size * 2

Duygu analizi için, olumsuzlama sözcüğü değiştirdiği kelimeden sonra geldiğinde bu önemlidir: "the movie was not bad" ifadesinde "bad"’in varlığını bilmek için ileri bağlam, "not"’un önce geldiğini bilmek için geri bağlam gerekir. 

BiLSTM’ler, adlandırılmış varlık tanıma (NER), duygu sınıflandırma ve konuşma tanıma gibi görevlerde tek yönlü modellere kıyasla tutarlı şekilde daha iyi performans gösterir; tipik kazançlar %5-15’tir.

Ödünleşim: Çıkarım zamanında tüm diziye ihtiyaç duyarsınız. Belirteçleri teker teker alan akış (streaming) uygulamaları çift yönlü işlemeyi kullanamaz.

Dikkat (attention) eklemek

Dikkat mekanizması modele, belirli bir tahmin için hangi zaman adımlarının daha önemli olduğunu ağırlıklandırma imkânı verir. Tüm diziyi tek bir son gizli duruma sıkıştırmak yerine, dikkat tüm gizli durumların ağırlıklı bir bileşimini hesaplar.

class AttentionLayer(nn.Module):
	def __init__(self, hidden_size):
    		super().__init__()
    		self.attention = nn.Linear(hidden_size, 1)
	
	def forward(self, lstm_outputs):
    	# lstm_outputs: (batch, seq_len, hidden_size)
    		scores = self.attention(lstm_outputs)  # (batch, seq_len, 1)
    		weights = torch.softmax(scores, dim=1)
    		context = torch.sum(weights * lstm_outputs, dim=1)
    		return context

"The acting was wooden, the plot made no sense, but the cinematography was stunning" gibi bir incelemede, dikkat mekanizması tüm kelimeleri eşit görmek yerine duygu taşıyan ifadelere odaklanmayı öğrenir. Model "wooden", "no sense" ve "stunning" için daha yüksek ağırlıklar atarken "the" ve "was"’ı aşağı ağırlıklandırır.

Dikkat mekanizması hesaplama yüküne asgari düzeyde ek yük getirir ve sınıflandırma görevlerinde sıklıkla %2-5 doğruluk artışı sağlar. Çeviri gibi dizi-diziye problemlerinde ise kodlayıcı ve kod çözücü arasındaki dikkat, iyi performans için neredeyse zorunludur.

LSTM katmanlarını istiflemek

Tek bir LSTM katmanı tek bir soyutlama düzeyindeki kalıpları yakalar. Birden çok katmanın istiflenmesi, ağın hiyerarşik özellikler öğrenmesini sağlar: ilk katman yerel ifade kalıplarını yakalarken, daha üst katmanlar bunları belge düzeyinde anlayışa birleştirir.

self.lstm = nn.LSTM(
	input_size=embedding_dim,
	hidden_size=hidden_size,
	num_layers=3,  # Stack three layers
	dropout=0.3,   # Dropout between layers (not within)
	batch_first=True,
)

Derinlik için yönergeler:

  •  1 katman: Basit görevler, küçük veri kümeleri, hızlı iterasyon
  • 2 katman: Çoğu üretim uygulaması
  • 3-4 katman: Karmaşık zamansal bağımlılıklar, büyük veri kümeleri
  • 4’ün ötesi: Azalan getiriler, eğitimde kararsızlık

Her ek katman eğitim süresini kabaca ikiye katlar. Sığ başlayın ve sadece doğrulama metrikleri plato yaptığında derinlik ekleyin.

Düzenlileştirme teknikleri

LSTM’ler özellikle küçük veri kümelerinde kolayca aşırı uyum gösterir. Üç düzenlileştirme tekniği birlikte iyi çalışır:

  • Dropout
  • Gradyan kırpma
  • Erken durdurma

Dropout

Dropout eğitimi sırasında nöronları rastgele sıfırlar ve ağı öğrenmeyi birden fazla yola dağıtmaya zorlar. Dropout’u LSTM katmanları arasında uygulayın; yinelemeli bağlantılar içinde uygulamak (zamansal öğrenmeyi bozabileceği için) önerilmez.

# PyTorch applies dropout between stacked layers automatically
self.lstm = nn.LSTM(..., num_layers=2, dropout=0.3)
 
# For single-layer LSTMs, add dropout after
self.dropout = nn.Dropout(0.3)

Dropout oranları 0,2-0,5 aralığında çoğu görev için çalışır. Daha yüksek değerler ciddi aşırı uyumda yardımcı olur ancak yakınsamayı yavaşlatır.

Gradyan kırpma

Gradyan kırpma, geri yayılım sırasında gradyanların büyüklüklerini sınırlar. LSTM’ler kaybolan gradyanları hafifletse de, özellikle uzun dizilerde veya derin ağlarda patlayan gradyanlar hâlâ görülebilir.

torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)

1,0 ile 5,0 arasındaki değerler tipiktir. Eğitim kaybı sıçrıyorsa veya NaN üretiyorsa, eşiği düşürün.

Erken durdurma

Erken durdurma, doğrulama kaybını izler ve iyileşme durduğunda eğitimi sonlandırır. Bu, modelin genelleme pahasına eğitim verisini ezberlemesini önler.

patience = 5
best_val_loss = float('inf')
epochs_without_improvement = 0
 
for epoch in range(max_epochs):
	train_loss = train_one_epoch(model, train_loader)
	val_loss = evaluate(model, val_loader)
	
	if val_loss < best_val_loss:
    		best_val_loss = val_loss
    		epochs_without_improvement = 0
    		torch.save(model.state_dict(), 'best_model.pt')
	else:
    		epochs_without_improvement += 1
	if epochs_without_improvement >= patience:
        	break

LSTM’lerde eğitim döngülerinin, değerlendirme metriklerinin ve yaygın sorunların hata ayıklamasının eksiksiz bir yürüyüşü için PyTorch ile NLP rehberine bakın.

Önceden eğitilmiş gömmeler

Rastgele gömme başlatma işe yarar; ancak GloVe veya Word2Vec gibi önceden eğitilmiş gömmeler modele bir başlangıç avantajı sağlar. Bu gömmeler milyarlarca kelimeden öğrenilmiş anlamsal ilişkileri kodlar: "king" ve "queen" birbirine yakın, "terrible" ve "awful" da öyle.

import numpy as np
 
def load_glove_embeddings(glove_path, vocab, embedding_dim):
	embeddings = np.random.randn(len(vocab), embedding_dim) * 0.01
	
	with open(glove_path, 'r') as f:
    	for line in f:
        	parts = line.split()
        	word = parts[0]
        	if word in vocab:
            		embeddings[vocab[word]] = np.array(parts[1:], dtype=np.float32)
	
	return torch.tensor(embeddings, dtype=torch.float32)
 
# Load and freeze or fine-tune
pretrained = load_glove_embeddings('glove.6B.100d.txt', vocab, 100)
model.embedding.weight.data.copy_(pretrained)
model.embedding.weight.requires_grad = True  # Fine-tune during training

Önceden eğitilmiş gömmeler, eğitim verisi sınırlı olduğunda en çok yardımcı olur. Milyonlarca örnekle, rastgele başlatılmış gömmeler eninde sonunda yetişir. 10 bin–100 bin örnekli çoğu pratik uygulamada, önceden eğitilmiş başlatmadan %3-8 doğruluk artışı bekleyin.

Teknikleri birleştirmek

Bu yaklaşımlar bir arada kullanılabilir. Üretim düzeyinde bir metin sınıflandırıcı şunları kullanabilir:

  • Çift yönlü bağlam için BiLSTM
  • Hiyerarşik özellikler için 2 istifli katman
  • Son gizli durum yerine dikkat havuzu (attention pooling)
  • Eğitim sırasında ince ayar yapılan GloVe gömmeleri
  • Katmanlar arasında 0,3 dropout
  • 5,0’da gradyan kırpma
  • 5 epoch sabırla erken durdurma

Her ekleme karmaşıklığı ve eğitim süresini artırır. Önce temel tek katmanlı bir LSTM ile başlayın, performansı ölçün; ardından kendi görevinizde kazanım sağlayanı görmek için teknikleri teker teker ekleyin.

LSTM vs Transformer: Doğru Mimarinin Seçilmesi

LSTM’ler 1990’ların sonundan 2010’ların ortalarına kadar dizi modellemede başı çekti. 2017 tarihli "Attention Is All You Need" makalesi tabloyu değiştirdi. Transformer’lar şimdi çoğu büyük dil modelini güçlendiriyor; ancak LSTM’ler yok olmadı. Farklı nişleri dolduruyorlar.

LSTM sınırlamaları

Önceki bölümlerde LSTM’lerin dizileri belirteç belirteç nasıl işlediğini ve kapı mekanizmasının neden işe yaradığını ele aldık. Ölçek büyüdüğünde bu ardışık bağımlılık darboğaz olur. GPU’nuzda binlerce çekirdek olabilir; ancak gizli durum zinciri tamamlanırken çoğu atıl kalır. 500 belirteçlik bir girdi, art arda 500 işlem demektir.

Transformer’larda bu kısıt yoktur. Öz-dikkat, tüm pozisyonlar arasındaki ilişkileri aynı anda hesaplar; böylece aynı 500 belirteçlik dizi paralel olarak işlenir. Büyük yığın boyutlarına sahip modern donanımda bu, eğitimde 10-100 kat hızlanmaya dönüşür.

Dizi uzunluğu tavanı da önemlidir. LSTM’lerin düz RNN’lere kıyasla gradyanları daha iyi koruduğunu gördük; fakat "daha iyi" her zaman "mükemmel" demek değildir. Performans, göreve bağlı olarak 200-500 belirteç civarında hâlâ düşer. 

Transformer’lar her pozisyon çifti arasındaki dikkati doğrudan hesaplar; böylece 1. belirteç 1000. belirtece aynı kolaylıkla dikkat edebilir. Ödünleşim bellek kullanımıdır: dikkat matrisleri dizi uzunluğuyla karesel büyür.

Kapı aktivasyonları yorumlamayı da zorlaştırır. Değerler 0 ile 1 arasında net sınırlar olmadan değişir. Transformer’lardaki dikkat ağırlıkları da kusursuz açıklamalar değildir; fakat her çıktı için hangi giriş belirteçlerinin önemli olduğuna dair daha net bir sinyal verirler.

Ne zaman LSTM, ne zaman Transformer

Transformer mantıklıdır, eğer:

  • Eğitim verisi 100 bin örneği aşıyor ve GPU erişiminiz varsa
  • Görev yüzlerce belirtece yayılan bağımlılıklar içeriyorsa
  • İterasyon döngüleri için eğitim hızı önemliyse
  • GPT gibi önceden eğitilmiş modelleri kullanım durumunuza göre ince ayarlayabiliyorsanız

LSTM’ler hâlâ uygundur, eğer:

  • Hedef cihaz sıkı bellek kısıtlarına sahipse (uç, gömülü, mobil)
  • Veri kıtsa (10 bin örneğin altında) ve Transformer aşırı uyumu endişe yaratıyorsa
  • Tam girdi gelmeden önce kısmi diziler üzerinde tahmin yapılması gerekiyorsa
  • Gecikme bütçeleri hafif modeller gerektiriyorsa

Zaman serisi tahmini ise ortada bir yerde durur. Güncel kıyaslamalar, tek değişkenli ve düzenli kalıplı veride her iki mimarinin de benzer performans gösterdiğini ortaya koyuyor. Birden çok özelliğin karmaşık biçimde etkileştiği durumlarda Transformer’lar öne çıkarken, veri kıt veya hesaplama kısıtlı olduğunda LSTM’ler ayakta kalır.

Hibrit tasarımlar iki yaklaşımı harmanlar. Bazı mimariler LSTM katmanlarını dikkat mekanizmalarıyla istifler. 2024’te yayımlanan xLSTM, üstel kapılama ve matris belleğiyle özgün tasarımı yeniden ele alır ve daha küçük ölçeklerde Transformer’larla rekabetçi sonuçlar gösterir.

Boyut

LSTM

Transformer

İşleme

Ardışık (belirteç belirteç)

Paralel (tüm pozisyonlar bir arada)

Eğitim hızı

Daha yavaş (sınırlı paralelleşme)

GPU’larda daha hızlı

Uzun menzilli bağımlılıklar

200-500 belirtecin ötesinde bozulur

Binlerce belirteci kaldırır

Bellek ölçeklemesi

Dizi uzunluğuyla O(n)

Dikkat matrisleri için O(n²)

Küçük veri performansı

İyi (daha az parametre)

Aşırı uyuma yatkın

Uç cihaza dağıtım

Uygun (daha düşük bellek ayak izi)

Zorlayıcı (büyük modeller)

Akış/gerçek zaman

Doğal destek

Tam dizi gerekir

Önceden eğitilmiş ekosistem

Sınırlı

Geniş (BERT, GPT, vb.)

Karar genellikle kısıtlamalara bağlıdır. Sınırsız hesaplama ve veri mi var? Çoğu NLP görevinde Transformer’lar kazanır. 10 bin eğitim örneği olan bir mikrodenetleyici mi? LSTM’ler pratik kalır.

Sonuç

LSTM’ler 1997’de ortaya çıktıklarında gerçek bir sorunu çözdüler. Hochreiter ve Schmidhuber’in tanıttığı kapı mekanizması, ağların birkaç düzine belirteçten daha uzun dizilerden nihayet öğrenebilmesini sağladı. Bu mimari, yaklaşık iki onıl boyunca makine çevirisi, konuşma tanıma ve zaman serisi tahminine güç verdi.

Günümüzde Transformer’lar büyük ölçekli NLP görevlerinin çoğunu hallediyor; ancak LSTM’ler modası geçmiş değil. Hâlâ uç cihaz dağıtımı, akış uygulamaları ve eğitim verisinin sınırlı olduğu durumlar için mantıklıdır.

Yeni bir dizi modelleme projesine başlıyorsanız, önce basit bir LSTM deneyin. Performansını ölçün, zayıf noktalarını anlayın; sonra sorunun gerçekten bir Transformer gerektirip gerektirmediğine karar verin. Bu rehberdeki kavramlar (kapılama, hücre durumu, gradyan akışı) daha karmaşık mimarileri anlamaya doğrudan aktarılır.

Uygulamalı pratik için, ek LSTM uygulamalarını yönlendirmeli alıştırmalarla ele alan Yinelemeli Sinir Ağları kursumuzu öneririm.

LSTM SSS

LSTM ile düz RNN arasındaki fark nedir?

Düz RNN’ler uzun dizilerde zorlanır çünkü gradyanlar geri yayılım sırasında küçülür ve uzak bağlamdan öğrenme yeteneği kaybolur. LSTM’ler bilgi akışını kontrol eden kapı mekanizmalarına sahip bir hücre durumu ekler; böylece ağ, yalnızca 20-30 değil, yüzlerce zaman adımı boyunca ilgili bağlamı koruyabilir.

Transformer yerine ne zaman LSTM kullanmalıyım?

Eğitim veriniz sınırlıysa (10 bin örneğin altında), mobil veya gömülü gibi bellek kısıtlı cihazlarda çalışmanız gerekiyorsa, tam dizi gelmeden akış verisi üzerinde gerçek zamanlı tahminler gerekiyorsa veya daha büyük modelleri dışlayan sıkı gecikme gereksinimleriniz varsa LSTM’ler daha iyi çalışır.

PyTorch LSTM’de hidden_size ne anlama gelir?

hidden_size parametresi, hem gizli durum hem de hücre durumu vektörlerinin boyutunu belirler. Daha büyük değerler modelin daha fazla kalıbı takip etmesine izin verir; ancak hesaplama ve bellek kullanımını artırır. 32-512 aralığı yaygındır; üretim metin uygulamalarında 128-256 tipiktir.

LSTM’imin aşırı uyumunu nasıl önlerim?

LSTM’inizin aşırı uyumunu önlemek için katmanlar arasında dropout uygulayın (0,2-0,5 oranları), patlayan gradyanları önlemek için gradyan kırpma kullanın, doğrulama kaybı iyileşmeyi bıraktığında eğitimi durduran erken durdurmayı devreye alın ve veri kümeniz küçükse GloVe gibi önceden eğitilmiş gömmeleri düşünün.

Çift yönlü LSTM nedir ve ne zaman kullanmalıyım?

Çift yönlü bir LSTM, dizi üzerinde iki geçiş yapar: biri ileri, biri geri. Bu, her pozisyona her iki yönden de bağlam verir; bu da anlamın gelecekteki belirteçlere bağlı olduğu durumlarda yardımcı olur ("not bad" gibi, burada "not"’un "bad"’ı görmesi gerekir). Çıkarım zamanında tam diziye sahip olduğunuz sınıflandırma ve etiketleme görevlerinde kullanın; akış uygulamalarında kaçının.

Bexruz (Bex) Tuychiev

Yazar

Bexruz (Bex) Tuychiev

2 yılı aşkın deneyime sahip bir veri bilimi içerik üreticisiyim ve Medium’da en büyük takipçi kitlelerinden birine sahibim. Yapay zekâ ve makine öğrenimi üzerine, biraz da alaycı bir üslupla yazılmış, ayrıntılı makaleler kaleme almayı seviyorum; çünkü onları biraz daha az sıkıcı hale getirmek için bir şeyler yapmak gerekiyor. 130’un üzerinde makale ve bir de DataCamp kursu ürettim; bir diğeri de hazırlık aşamasında. İçeriklerim 5 milyondan fazla kişi tarafından görüldü; bunların 20 bini Medium ve LinkedIn’de takipçim oldu. 

Konular

İlgili