Skip to content

Command Palette

Search for a command to run...

Açık Kaynak Pazartesi'si: SwiftAutograd, SwiftRL SPM Paketleri

11 dk okuma

Giriş

Swift programlama dilini günlük hayatımda neredeyse birçok programlama ihtiyacımı çözmek için kullanıyorum diyebilirim. Bunun en büyük sebeplerinden birisi Swift'in type-safe olması. Bunun yanı sıra Swift'in açık kaynak olması ve arkasında çok güçlü bir ekosistemin olması da beni Swift ekosisteminde bir geliştirici olarak çok kabiliyetli hissettiriyor. Bir önceki yazıda ClaudeSwiftSDK ile Swift'te agent framework'leri incelemiştik. Bu sefer farklı bir alana, reinforcement learning'e bakacağız.

Reinforcement learning alanında Python'un hakimiyeti tartışılmaz. PyTorch, TensorFlow, Gymnasium, Stable Baselines gibi araçların hepsi Python ekosisteminde yaşıyor. Ancak bir iOS uygulamasında, kullanıcının cihazında, sunucuya veri göndermeden bir RL agent'ı eğitmek istediğinizde Python bridge'leri, ONNX export'ları, CoreML conversion pipeline'ları gibi katmanlar devreye giriyor ve süreç karmaşıklaşıyor.

Bu boşluğu doldurmak için iki SPM paketi geliştirdim: SwiftGrad ve SwiftRL. SwiftGrad, Andrej Karpathy'nin micrograd projesinden ilham alan bir autograd engine. SwiftRL ise bu engine üzerine inşa edilmiş bir reinforcement learning kütüphanesi. İkisi birlikte, Swift'te sıfır bağımlılıkla on-device RL eğitimi yapabilmenizi sağlıyor.


SwiftGrad: Pure Swift Autograd Engine

Autograd Nedir

Autograd, "automatic differentiation" kelimelerinin kısaltması. Bir fonksiyonun türevini otomatik olarak hesaplayan bir sistem. Nöral ağları eğitmek için gradyan hesaplaması gerekiyor ve bu gradyanları elle yazmak hem hataya açık hem de pratik değil. Autograd bu sorunu çözüyor.

PyTorch'un autograd modülü tam olarak bunu yapıyor. Siz matematiksel işlemleri yazıyorsunuz, PyTorch arka planda bir hesaplama grafiği (computation graph) oluşturuyor. .backward() çağırdığınızda ise chain rule'u kullanarak tüm gradyanları otomatik hesaplıyor. Karpathy'nin micrograd projesi, bu mekanizmayı ~100 satır Python'da sıfırdan implement ediyor ve nasıl çalıştığını çok net gösteriyor. Karpathy'nin bu konudaki YouTube dersini izlemenizi önerebilirim. Autograd kavramını en temelden anlatan en iyi kaynaklardan birisi.

SwiftGrad, micrograd'ın Swift karşılığı. Aynı algoritmayı ~250 satır Swift'te implement ediyor.

Value: Hesaplama Grafiğinin Yapı Taşı

SwiftGrad'ın merkezinde Value class'ı var. Her Value bir skaler sayı tutuyor ve bu sayının hangi işlemle üretildiğini takip ediyor.

let a = Value(-4.0)
let b = Value(2.0)

let c = a + b          // c = -2.0
let d = a * b + b.power(3)  // d = -8.0 + 8.0 = 0.0
let e = c - d          // e = -2.0
let f = e.power(2)     // f = 4.0

f.backward()           // Tüm gradyanları hesapla

print(a.grad)  // df/da
print(b.grad)  // df/db

f.backward() çağrıldığında SwiftGrad hesaplama grafiğini topological sort ile sıralıyor, sonra ters sırada chain rule uygulayarak her node'un gradyanını hesaplıyor. PyTorch'un yaptığı şeyin aynısı, scalar seviyesinde.

İç Yapı

Gradyan hesaplaması her operasyonda bir closure olarak saklanıyor. Örneğin çarpma işlemi şöyle çalışıyor.

public func multiplying(_ other: Value) -> Value {
    let out = Value(data * other.data, children: [self, other], op: "*")
    out._backward = { [weak self, weak other, weak out] in
        guard let self, let other, let out else { return }
        self.grad += other.data * out.grad   // d(a*b)/da = b
        other.grad += self.data * out.grad   // d(a*b)/db = a
    }
    return out
}

Her operasyon (+, *, power, relu, tanh, log, exp) kendi _backward closure'ını tanımlıyor. backward() metodu çağrıldığında bu closure'lar ters sırada çalıştırılıyor ve gradyanlar chain rule ile accumulate ediliyor. weak capture'lar ise retain cycle'ları önlüyor. Bu, Python'dan Swift'e geçişte dikkat edilmesi gereken bir detay.

Nöral Ağ Katmanı

SwiftGrad, Value üzerine inşa edilmiş Neuron, Layer ve MLP (Multi-Layer Perceptron) class'larını da içeriyor. MLP, birden fazla katmanın ardışık olarak bağlandığı en temel nöral ağ mimarisi. Her katmandaki neuron'lar girdileri ağırlıklarla çarpıp bir activation fonksiyonundan geçiriyor ve bu şekilde ağ karmaşık örüntüleri öğrenebiliyor.

let model = MLP(inputSize: 3, layerSizes: [4, 4, 1])
let optimizer = SGD(parameters: model.parameters(), learningRate: 0.05)

let xs: [[Value]] = [
    [Value(2.0), Value(3.0), Value(-1.0)],
    [Value(3.0), Value(-1.0), Value(0.5)],
]
let targets = [1.0, -1.0]

for epoch in 0..<100 {
    let predictions = xs.map { model.forward($0) }
    let loss = Loss.mse(predicted: predictions, targets: targets)

    model.zeroGrad()
    loss.backward()
    optimizer.step()
}

callAsFunction sayesinde model(inputs) şeklinde Pythonic bir syntax kullanabiliyorsunuz. Son katman linear, diğer katmanlar ReLU activation kullanıyor. ReLU, negatif değerleri sıfıra çeviren basit ama etkili bir activation fonksiyonu. Ara katmanlarda ReLU kullanmak ağın doğrusal olmayan örüntüleri öğrenmesini sağlıyor. Son katmanın linear olması ise modelin çıktısını herhangi bir aralıkta üretebilmesine olanak tanıyor. Toplam ~330 satır Swift kodu ile tam bir nöral ağ eğitim pipeline'ı elde ediyorsunuz.


SwiftRL: On-Device Reinforcement Learning

SwiftGrad tek başına gradyan hesaplaması ve nöral ağ eğitimi için tasarlanmış bir autograd engine olarak çalışıyor. SwiftRL ise bu engine'in üzerine inşa edilmiş, reinforcement learning algoritmaları ve hazır ortamlar sunan ikinci bir paket. İki paket birlikte kullanıldığında, Swift'te uçtan uca bir RL eğitim pipeline'ı oluşturuyor.

Environment Protocol

SwiftRL'in temelinde Gymnasium convention'ını takip eden bir Environment protocol'ü var. Reinforcement learning'de bir agent, bir ortam (environment) içinde aksiyonlar alıyor, ortamdan gözlemler (observation) ve ödüller (reward) alıyor ve bu geri bildirimle davranışını geliştiriyor. Bu döngüyü standartlaştırmak için OpenAI'ın Gymnasium kütüphanesi bir convention belirledi ve SwiftRL de aynı convention'ı Swift protocol'ü olarak tanımlıyor.

public protocol Environment {
    var observationSize: Int { get }
    var actionCount: Int { get }
    mutating func reset() -> [Double]
    mutating func step(action: Int) -> StepResult
}

reset() ortamı başlangıç durumuna getiriyor ve ilk observation'ı döndürüyor. step(action:) bir aksiyon alıyor ve StepResult döndürüyor: yeni observation, reward ve episode'un bitip bitmediği bilgisi. Bu arayüz, OpenAI Gymnasium'un env.reset() / env.step() pattern'iyle birebir örtüşüyor.

SwiftRL'de bu protocol'ü conform eden üç hazır ortam bulunuyor. CartPole, bir araba üzerinde duran çubuğu dengede tutma problemi ve reinforcement learning literatüründe en çok kullanılan benchmark ortamlarından birisi. GridWorld, agent'ın bir grid üzerinde başlangıç noktasından hedefe en kısa yolu bulmaya çalıştığı bir navigasyon ortamı. Bandit ise multi-armed bandit problemi: agent birden fazla kol arasından en yüksek ödülü veren kolu keşfetmeye çalışıyor. Bu üç ortam, farklı RL algoritmalarını test etmek ve karşılaştırmak için yeterli çeşitliliği sağlıyor.

REINFORCE: Policy Gradient

Reinforcement learning'de iki temel yaklaşım bulunuyor: value-based ve policy gradient. Value-based yöntemler her durumun ne kadar değerli olduğunu hesaplıyor. Bunu bir labirentte her kavşağa "çıkışa olan tahmini mesafe" yazan tabelalar asmak gibi düşünebilirsiniz. Agent tabelaları okuyarak en kısa yolu buluyor. Policy gradient yöntemleri ise agent'ın davranış stratejisini (policy) doğrudan optimize ediyor. Bu da aynı labirentte tabelalar yerine agent'a "bu kavşakta sola dön" gibi kurallar öğretmek gibi çalışıyor. REINFORCE, Williams'ın 1992 tarihli makalesinde tanımlanan en temel policy gradient algoritması. Diğer RL yöntemlerinden farklı olarak, REINFORCE doğrudan policy'yi optimize ediyor. Yani agent'a "bu durumda hangi aksiyonu al" diyen fonksiyonu direkt olarak iyileştiriyor. Agent bir MLP üzerinden softmax dağılımı çıkarıyor. Softmax, ağın ham çıktılarını olasılıklara dönüştüren bir fonksiyon. Örneğin üç aksiyon varsa softmax bunları [0.1, 0.7, 0.2] gibi toplamı 1 olan olasılıklara çeviriyor. Agent bu olasılıklara göre rastgele bir aksiyon seçiyor, yani sample'lıyor. İyi sonuç veren aksiyonların olasılığı artıyor, kötü sonuç verenlerin olasılığı düşüyor.

Bir episode, agent'ın başlangıçtan bitiş durumuna kadar yaptığı tüm aksiyonların bütünü. Episode sonunda discounted return'ler hesaplanıyor. Discounted return, gelecekteki ödüllerin bugünkü değerini ifade ediyor. Tıpkı bugün alacağınız 100 liranın yarın alacağınız 100 liradan daha değerli olması gibi, yakın zamanda alınan ödüller uzak gelecekteki ödüllerden daha ağırlıklı hesaplanıyor. Policy bu return'lere göre güncelleniyor. Bu basitliği REINFORCE'u RL öğrenmek için ideal bir başlangıç noktası yapıyor.

loss = -Σ log(π(a|s)) * G_t

Bu formüldeki π(a|s), agent'ın s durumunda a aksiyonunu seçme olasılığı. log(π(a|s)) bu olasılığın logaritması ve gradyan hesaplamasını sayısal olarak daha kararlı hale getiriyor. G_t ise o adımdan itibaren kazanılan discounted return. Formülün bütünü şunu söylüyor: iyi sonuç veren aksiyonların (yüksek G_t) logaritmik olasılığını maksimize et. Başındaki eksi işareti ise maximization problemini bir minimization problemine çeviriyor, çünkü optimizer'lar loss'u minimize etmek üzere tasarlanmış.

SwiftRL'deki REINFORCE implementation'ı bu formülü doğrudan takip ediyor.

var agent = REINFORCE(
    observationSize: 4,
    hiddenSizes: [32],
    actionCount: 2,
    learningRate: 0.001,
    gamma: 0.99
)

var env = CartPole()

let rewards = agent.train(
    environment: &env,
    episodes: 1000,
    onEpisode: { episode, reward in
        if episode % 100 == 0 {
            print("Episode \(episode): reward = \(reward)")
        }
    }
)

Beş satır kurulum ve tek bir train çağrısıyla eğitim başlıyor. Bu çağrının arkasında az önce incelediğimiz formül adım adım çalışıyor. Her adımda agent, softmax fonksiyonuyla aksiyon olasılıklarını hesaplıyor ve seçtiği aksiyonun log olasılığını hafızasına kaydediyor. Episode sona erdiğinde toplanan ödüller gamma (0.99) katsayısıyla geriye doğru iskonto ediliyor ve her adım için G_t değeri ortaya çıkıyor. Bu iki bileşen, yani log(π(a|s)) ve G_t, çarpılıp toplanarak loss değerini oluşturuyor. backward() çağrısı bu loss üzerindeki gradyanları otomatik olarak hesaplıyor ve Son adımda Adam (Adaptive Moment Estimation) optimizer devreye giriyor. Adam, klasik SGD'nin (Stochastic Gradient Descent) geliştirilmiş bir versiyonu ve deep learning'de en yaygın kullanılan optimizer'lardan birisi. SGD her parametreyi aynı sabit hızla güncellerken, Adam her parametre için ayrı bir öğrenme hızı tutuyor ve gradyanların momentumunu takip ediyor. Bu sayede eğitim daha hızlı ve kararlı ilerliyor. Adam, hesaplanan gradyanları kullanarak policy ağırlıklarını güncelliyor. Tüm bu akış, yani softmax hesabından backward() çağrısına ve optimizer güncellemesine kadar olan sürecin tamamı, REINFORCE struct'ının update() metodu içinde implement edilmiş durumda. train metodu ise bu update() çağrısını her episode sonunda otomatik olarak tetikliyor.

DQN: Deep Q-Network

DQN (Deep Q-Network), DeepMind'ın 2015 yılında Nature dergisinde yayımladığı makalede tanıtılan ve Atari oyunlarını insan seviyesinde oynayabilen algoritma. REINFORCE'un aksine DQN value-based bir yöntem, yani her durum-aksiyon çifti için bir Q-value (kalite değeri) hesaplıyor ve en yüksek Q-value'a sahip aksiyonu seçiyor. DQN'in klasik Q-learning'den farkı iki temel yenilik getirmiş olması: experience replay buffer ve target network. Experience replay buffer, agent'ın geçmiş deneyimlerini bir havuzda biriktiriyor ve eğitim sırasında bu havuzdan rastgele örnekler çekerek öğreniyor. Bu, ardışık deneyimler arasındaki korelasyonu kırıyor ve eğitimi daha kararlı hale getiriyor. Target network ise Q-value hedeflerini hesaplamak için kullanılan ayrı bir ağ kopyası. SwiftRL'deki DQN bu iki yeniliği de implement ediyor.

var agent = DQN(
    observationSize: 2,
    hiddenSizes: [64, 32],
    actionCount: 4,
    learningRate: 0.001,
    gamma: 0.99,
    bufferCapacity: 10_000,
    batchSize: 32,
    targetUpdateFrequency: 100,
    epsilonSchedule: ExponentialDecay()
)

var env = GridWorld(size: 8)
let rewards = agent.train(environment: &env, episodes: 500)

Agent epsilon-greedy exploration stratejisi kullanıyor. Eğitimin başında agent neredeyse tamamen rastgele aksiyonlar alıyor, çünkü henüz ortam hakkında hiçbir şey bilmiyor. Zamanla epsilon değeri düşüyor ve agent rastgele keşif yapmak yerine öğrendiği Q-value'lara güvenmeye başlıyor. Bu keşif-sömürü (exploration-exploitation) dengesi reinforcement learning'in temel problemlerinden birisi. ExponentialDecay schedule'ı bu geçişin hızını kontrol ediyor.

Eğitim sırasında agent replay buffer'dan rastgele bir grup deneyim (minibatch) çekiyor. Her deneyim için agent'ın o anki Q-value tahmini ile gerçek hedef arasındaki farkı hesaplıyor. Basitçe söylemek gerekirse agent "bu aksiyonu alırsam ne kadar ödül kazanırım" diye tahmin ediyor, sonra gerçekte ne olduğuna bakıyor ve tahminini gerçeğe yaklaştıracak şekilde ağırlıklarını güncelliyor.

Target network ise her 100 adımda primary network'ten senkronize ediliyor. Bu ayrımın sebebi şu: eğer aynı ağı hem "şu anki tahminim ne" hem de "hedefim ne olmalı" soruları için kullansanız, her güncelleme hem tahmini hem hedefi aynı anda değiştirir ve eğitim kararsız hale gelir. Ayrı bir target network tutarak hedefleri sabitliyor ve eğitimi kararlı kılıyorsunuz.

Mimari

İki paketin birlikte oluşturduğu mimari şöyle görünüyor.

SwiftGrad (Autograd Engine)
├── Value              Skaler + hesaplama grafiği
├── Neuron/Layer/MLP   Nöral ağ bileşenleri
├── Loss (MSE/Hinge)   Kayıp fonksiyonları
└── SGD Optimizer

        ↓ bağımlılık

SwiftRL (RL Framework)
├── Core
│   ├── Environment protocol
│   ├── Agent protocol
│   └── ReplayBuffer
├── Algorithms
│   ├── REINFORCE (policy gradient)
│   └── DQN (value-based)
├── Environments
│   ├── CartPole, GridWorld, Bandit
├── Math
│   └── Softmax, logSoftmax, sampleCategorical
├── Optimizers
│   └── Adam
└── Utilities
    ├── EpsilonSchedule (Linear/Exponential)
    └── RewardTracker

SwiftGrad herhangi bir harici kütüphaneye ihtiyaç duymadan, sıfır bağımlılıkla çalışıyor. SwiftRL ise sadece SwiftGrad'a bağımlı ve başka hiçbir üçüncü parti pakete ihtiyaç duymuyor. İkisi birlikte pure Swift ile yazılmış, CoreML, ONNX veya Python bridge'i gibi ara katmanlara gerek kalmadan doğrudan cihaz üzerinde RL eğitimi yapabilmenizi sağlıyor. Bu da projenizi herhangi bir Apple platformunda, ek bir konfigürasyon olmadan SPM ile kullanabileceğiniz anlamına geliyor.


Demo Uygulamalar

Bu paketlerin pratikte nasıl çalıştığını göstermek için üç demo uygulaması geliştirdim. Hepsi SwiftUI ile görselleştirilmiş ve tamamı on-device çalışıyor.

GridWorld ile DQN

GridWorld Demo

Bu demoda agent, 8x8 boyutundaki bir grid üzerinde sol üst köşedeki (0,0) noktasından sağ alt köşedeki (7,7) hedefe ulaşmaya çalışıyor. Eğitimin başında epsilon değeri 1.0 olduğu için agent tamamen rastgele hareket ediyor ve çoğu zaman hedefe ulaşamadan zaman aşımına düşüyor. Episode'lar ilerledikçe epsilon düşüyor ve agent, rastgele keşif yerine öğrendiği Q-value'lara dayanarak en kısa yolu tercih etmeye başlıyor. Sağ taraftaki Training Monitor grafiğinde bu öğrenme sürecini gerçek zamanlı olarak izlemek mümkün. Reward değerinin negatiften pozitife doğru yükselmesi, agent'ın hedefe ulaşmayı öğrendiğinin bir göstergesi. Burada çalışan algoritma, DeepMind'ın Atari oyunlarında kullandığı DQN'in aynısı. Tek fark, çok daha küçük bir ölçekte ve tamamen cihaz üzerinde çalışıyor olması.

Snake ile DQN

Snake Demo

Bu demoda agent, klasik Snake oyununu oynamayı sıfırdan öğreniyor. Agent her adımda 11 feature'dan oluşan bir gözlem alıyor: ileriye, sağa ve sola bakarak tehlike olup olmadığını algılıyor, mevcut hareket yönünü biliyor ve yiyeceğin hangi yönde olduğunu görüyor. Bu gözlemler agent'ın çevresini anlamasını sağlayan temel bilgiler. DQN algoritması ile agent, deneme yanılma yoluyla yiyeceğe yönelmeyi ve duvarlara ya da kendi gövdesine çarpmaktan kaçınmayı öğreniyor. Bu demo, adaptif NPC davranışlarının cloud GPU'ya ihtiyaç duymadan doğrudan kullanıcının cihazında eğitilebileceğini gösteriyor. Bir oyun geliştiricisi olarak düşündüğünüzde, her kullanıcının cihazında farklı oynama stiline adapte olan NPC'ler oluşturmak bu yaklaşımla mümkün hale geliyor.

CartPole ile REINFORCE

CartPole Demo

CartPole, reinforcement learning literatüründe en klasik benchmark problemlerinden birisi. Bir ray üzerinde hareket eden arabanın tepesinde dik duran bir çubuk var ve agent bu çubuğu dengede tutmaya çalışıyor. Agent her adımda dört sürekli gözlem alıyor: arabanın pozisyonu, arabanın hızı, çubuğun açısı ve çubuğun açısal hızı. Bu dört bilgiye dayanarak arabayı sola veya sağa itmek arasında karar veriyor. Burada DQN yerine REINFORCE algoritması kullanılıyor, çünkü CartPole sürekli gözlem uzayına sahip ve policy gradient bu tür problemlerde iyi performans gösteriyor. Çubuk ne kadar uzun süre dengede kalırsa agent o kadar çok ödül topluyor. Eğitim ilerledikçe agent, çubuğun açısına ve hızına göre doğru yöne itme stratejisini öğreniyor ve çubuğu yüzlerce adım boyunca dengede tutmayı başarıyor.


Neden Önemli

Swift'te on-device RL eğitimi yapabilmenin önemini birkaç açıdan değerlendirmek mümkün.

Gizlilik. RL agent'ları kullanıcı davranışından öğreniyor. Bu davranış verisini sunucuya göndermek yerine cihazda tutabilmek, özellikle sağlık, finans ve çocuk uygulamaları gibi hassas alanlarda kritik bir avantaj. Agent kullanıcının etkileşimlerine adapte olurken hiçbir veri cihazı terk etmiyor.

Kişiselleştirme. Her kullanıcı farklı oynuyor, farklı etkileşimde bulunuyor. On-device eğitim sayesinde her kullanıcı için benzersiz bir agent eğitilebilir. Bir oyunda zorluk seviyesini dinamik olarak ayarlayan, kullanıcının oyun stiline adapte olan NPC'ler bu yaklaşımla mümkün hale geliyor.

Gecikme. Policy update'leri uygulamanın kendi process'inde gerçekleşiyor. Sunucu round-trip'i yok, ağ gecikmesi yok. Agent'ın kararları milisaniyeler içinde güncellenebiliyor.

Çevrimdışı çalışma. İnternet bağlantısı olmadan eğitim devam edebiliyor. Bu, özellikle mobil uygulamalar için önemli bir avantaj.

Pazar verilerine bakıldığında, AI in gaming alanının 2024 ile 2034 arasında 5.85 milyar dolarlık bir pazar büyüklüğüne ulaşması bekleniyor (Precedence Research). Apple'ın ML Research ekibinin on-device learning üzerine yayınladığı makaleler de bu yönelimi destekliyor.

Ancak şu ana kadar Swift ekosisteminde reinforcement learning için native bir kütüphane bulunmuyordu. CoreML yalnızca inference tarafını destekliyor, on-device training kapasitesi sunmuyor. Google'ın Swift for TensorFlow (S4TF) projesi genel amaçlı bir ML framework'ü olarak umut vadetmişti, ancak 2021'de geliştirmesi durduruldu ve üzerine bir RL kütüphanesi hiçbir zaman inşa edilmedi. SwiftGrad ve SwiftRL bu boşluğu doldurmayı hedefliyor ve paketler Swift Package Index'e de eklendi.


Sonuç

Bu iki paketin ortaya koyduğu şey, Swift'in sadece uygulama geliştirme dili olmadığı. Value class'ı ile autograd, Environment protocol'ü ile RL ortamları, callAsFunction ile Pythonic syntax sağlayan bu paketler, Swift'in dil özelliklerinin bu tür kütüphaneler için ne kadar uygun olduğunu gösteriyor.

SwiftGrad'ı Karpathy'nin micrograd dersini izledikten sonra incelemenizi önerebilirim. ~250 satır kodda autograd'ın nasıl çalıştığını Swift'in type safety'si ve operator overload'ları ile görmek hem eğitici hem de ilham verici. SwiftRL'i ise kendi oyun veya uygulama projelerinizde on-device training denemek için kullanabilirsiniz.

Her iki paketin kaynak kodu açık ve katkıya açık.


Kaynaklar

Paylaş:

A
Yazan
Software Engineer

Tartışma

Henüz yorum yok

İlk yorumu siz yapın!