Derin Pekiştirmeli Öğrenmede Teknikler: Policy Gradient

Taşıyıcının üzerindeki çubuğu dengede tutmayan çalışan 4 yapay zeka modeli görüyorsunuz. Sırasıyla, rastgele oynayan model, 20 tur oyunu oynayıp öğrenmiş derin pekiştirmeli öğrenme modeli ardından aynısının 125 ve 500 tur denemiş halleri.

Rastgele Oynayan Model
20 Tur Oyundan Öğrenen Model
125 Tur Oyundan Öğrenen Model
500 Tur Oyundan Öğrenen Model

Derin pekiştirmeli öğrenmede kullanılan çeşitli teknikler var, bunlardan “Q-learning” ile ilgili bir tanesini bu linkte bulabilirsiniz. Bu yazıda, ben Salih Durhan ve çalışma arkadaşım Ahmet Can Özbek, “policy gradient” tekniğini bir uygulamayla anlatacağız.

Konuyu doğrudan gym kütüphanesindeki bir problemi çözme üzerine kurgulayalım, önce gerekli kütüphaneler:

import time
import numpy as np
import scipy
import gym
from tensorflow import keras
import matplotlib.pyplot as plt
%matplotlib inline

Gym kütüphanesindeki “cartpole” oyununu kullacağız. Bu oyunda aşağıda gördüğünüz gibi bir taşıyıcı üzerinde serbest bir mafsalla bağlı çubuk var. Taşıyıcıyı sürtünmesiz bir ortamda sağa ya da sola kaydırarak dengede tutmaya çalışıyoruz.

Oyunda her bir adımda aşağıda sıralanmış 4 gözlemimiz (yani 4 boyutlu bir durum -state- vektörümüz) var, sırasıyla “taşıyıcı pozisyonu, “taşıyıcı hızı”, “çubuk açısı” ve “çubuk açısal hızı”. Oyun rastgele bir durum vektörüyle başlıyor ve biz taşıyıcıyı sağa sola hareket ettirerek çubuğu dengede (çubuk açısı 12 dereceyi geçmeden ve taşıyıcıyı ekrandan çıkartmadan) tutmaya çalışıyoruz. Her bir oyun en fazla 500 adım oynanıyor ve çubuğu düşürmediğimiz her adım için 1 puan ödül alıyoruz, detaylar burada.

Pekiştirmeli öğrenme akışı için aşağıdaki resim hemen her yerde kullanılır, biz de kullanalım:

Bu akışta, her bir $t$ anında oyuncu (agent) durum vektörüne ($S_t$) bakarak bir aksiyon alır ($A_t$), aldığı aksiyona karşılık çevre (environment) oyuncuya bir ödül ($R_{t+1}$) ve yeni bir durum vektörü ($S_{t+1})$) verir. Oyuncu olarak bizim işimiz ödül peşinde koşarak en iyi yolu (policy) bulmak. “Policy gradient” yöntemi

  • Rastgele aksiyonlar seçerek başla
  • Oynadıkça yüksek ödüllü hareketleri daha sık yap

olarak özetlenebilir. Bunun için öncelikle oyunu, daha sonra da sağa ya da sola gitme kararlarını verecek agent’ı basit bir yapay sinir ağı olarak tanımlayalım.

env = gym.make("CartPole-v1")

def get_model():
    model_input = keras.layers.Input(shape=(4,))
    x = keras.layers.Dense(10, activation='relu')(model_input)
    x = keras.layers.Dense(5, activation='relu')(x)
    model_output = keras.layers.Dense(2, activation='softmax')(x)
    
    model = keras.models.Model(inputs=model_input, outputs=model_output)
    
    return model

Gördüğünüz gibi yapay sinir ağımız 4 boyutlu bir girdi ( durum -state- vektörü) alıp, bunu sırasıyla 10 ve 5 perseptrondan oluşan iki katmandan geçirip çıktı olarak one-hot-encoded iki sonuç ([1,0] sol, [0,1] sağ) veriyor.

Modeli eğitmeye başlamadan önce, ödüller konusunu değerlendirmeliyiz. Diyelim oyunu 5 adım oynadık, 6. adımda çubuk düştü, o zaman her bir adımda seçtiğimiz ödüller $$[1,1,1,1,1]$$ olacak. 1. adımda aldığımız kararın da ödülü 1, 5. adımda aldığımız kararın da ödülü 1 ama aslında 5. adımdan hemen sonra çubuğu düşürdük. Doğrusu 1. adımda aldığımız karardan sonra 4 adım daha çubuk düşmemiş, 2. adımdaki kararda 3 adım daha çubuk düşmemiş olduğuna göre ödüllerimizi $$[5, 4, 3, 2, 1]$$ olarak almak da oldukça mantıklı. Bu temelde $t$ anında aldığımız kararın ödülüne oyun sonuna kadar alınan bütün ödülleri eklemek demek. Ödülleri bu şekilde belirlemekte de bir eksik var, oyundaki adımlar uzadıkça başlardaki hareketler orantısız biçimde yüksek ödüller alıyor olacaklar ilk hamlede yaptığımız bir seçim 100 hamle sonrasından da haketmediği bir pay alacak. Bunun önüne geçmek için de “indirimli ödül” (discounted reward) kullanacağız, $t$ anındaki aksiyon $t+1$ anındaki aksiyonun ödülünün $0.9$ katını alacak böylelikle çok uzak adımlardaki ödüllerden aldığı pay hızla küçülecek. Buradaki $0.9$’i uygulamaya göre farklı da seçebiliriz, önemli olan $(0,1)$ aralığında olması.

Bizim 5 adımlı oyun örneğimizde $0.9$ indirimli ödüller $$[4.0951, 3.439 , 2.71 , 1.9 , 1]$$ olacak örneğin. 5. adımdan sonra ödül olmadığı için orada ödül yalnızca kendi ödülü $1$. 4. adımın 1 kendi ödülü var, 0.9 ödülde 5. adımdan alıyor, toplam $1.9$. 3. adım 1 kendi ödülü, 0.9 4.adımdan, 0.81 5.adımdan alıyor, toplam $2.71$.

Ödül konusu burada da bitmiyor, indirimli ödüllerimiz hep pozitif ama oyunun son hamlelerinde yanlış seçimler yapıyor olmalıyız ki oyun bitiyor. Ayrıca aralarından çok uzunluk farkı olan oyunlarda ödüller uzun oyunlar lehine taraflı olacak. Bunu gidermek için de indirimli ödülleri normalize edebiliriz, ortalama ödülü çıkartıp standart sapmasına bölerek. Hepsini bir araya koyarsak kodumuz:

ef discount_rewards(r, gamma=0.95, normalize=False):
    r = np.array(r)
    discounted_reward= np.zeros(r.shape) 
    running_add = 0
    for t in reversed(range(0, r.size)):        
        running_add = running_add * gamma + r[t] # sum * y (gamma) + reward
        discounted_reward[t] = running_add
    
    if normalize is True:
        discounted_reward = discounted_reward - np.mean(discounted_reward)
        discounted_reward = discounted_reward / np.std(discounted_reward)
        
    return discounted_reward

Bu fonksiyonda indirim faktörü belirtilmediğinde $0.95$ alınıyor ve normalize=True ile çağrıldığında indirimli ödülü normalize ediyor. Artık modelimizi eğitebiliriz:

N_EPISODES = 500
ACTIONS = [0, 1]
model = get_model()
model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.01), loss=keras.losses.categorical_crossentropy)

N_EPISODES, oyunu kaç kere oynayacağımız, aksiyonlarımız 0 (sol) ve 1 (sağ), öğrenme hızı 0.01 ve kayıp fonksiyonumuz da kategorik çapraz entropi. Aşağıda bu oyunu oynayarak modeli eğitiyoruz.

# Loss fonksiyonu ve toplam ödülleri liste olarak sakla
record_loss = list()
record_sum_rewards = list()

for e in range(N_EPISODES):
    #Oyunu başlat
    first_state = env.reset()
    #Başlangıçta oyunun bitme işaretini yanlış olarak belirle
    done = False 
    # Durumları, ödülleri ve aksiyonları liste olarak sakla
    states = list()
    rewards = list()
    actions = list()
    # Başlangıç durumunu listeye ekle
    states.append(first_state)
    # Başlangıç durumu ile eğitime başla
    state = first_state
    
    # Oyundan bitti (done) işareti gelene kadar döngüyü çalıştır
    while not done:
        # Modelin aksiyon tahminini al
        p = model.predict(state[np.newaxis, ...])
        # Modelin aksiyon olasılığı çıktısı ile rastgele bir aksiyon seç
        # Model 0.7 ihtimalle 0, 0.3 ihtimalle 1 gibi
        sampled_action = np.random.choice(ACTIONS, size=None, p=p[0])
        # Seçilen aksiyonu yürüt, sonuçları al
        state, reward, done, info = env.step(sampled_action)
        # Ödül ve durumu kaydet
        rewards.append(reward)
        states.append(state)
        actions.append(sampled_action)
    
    # Model Eğitimi
    # Girdi
    x = np.array(states[:-1])
    # Çıktı
    y = keras.utils.to_categorical(actions, num_classes=2)  
    # Normalize edilmiş indirimli ödülleri ağırlık alarak modeli eğit
    dr = discount_rewards(rewards, normalize=True)
    loss = model.train_on_batch(x, y, sample_weight=dr)
    
    # episode end
    record_loss.append(loss)
    record_sum_rewards.append(len(rewards))
    print(e, 'loss:', loss, 'len(rewards):', len(rewards), ',p:', p[0], ', total_avg:', np.mean(record_sum_rewards))

Bu uygulamada incelikli bir kaç noktaya da değinelim. Öncelikle, modelin çıktılarını olasıksal olarak uyguluyoruz. Model sol ve sağ aksiyonları için $[0.8, 0.2]$ olasılıklar verdiğinde, en yüksek olasılıklı aksiyonu seçebilirdik hep ama bu durumda modelin yeni yollar denemesini engellemiş olurduk. Sürekli iyi çalıştığı bilinen yolları kullanmak yeni çözümler bulmaya engel olabilir, buna Pekiştirmeli öğrenmede “exploitation vs exploration” problemi denir. Bu yüzden modelin her dediğini birebir yapmaktansa olasılıksal olarak yeni yollara sapmak daha verimli. İkincisi, ödülleri model eğitiminde örnek aksiyonlara “ağırlık” olarak atıyoruz. Böylece yüksek ödüllü örnekler daha çok ağırlığa sahip oluyor ve doğru aksiyonlara böyle ulaşıyoruz. Dahası, ödül fonksiyonumuzla ödülleri normalize ettiğimiz için, oyunların sonlarına doğru yaptığımız seçimler negatif ödül alıyor, böylece model bu hamleleri yapmamayı da öğreniyor. Bunları loss fonksiyonu içerisinde, ödülleri çapraz entropi ile çarparak da yapabilirdik, mesela bu linkteki gibi.

Model çıktılarını görselleştirdiğimizde, 100 oyundan itibaren modelimizin oyunu iyice çözdüğünü görüyoruz.

fig = plt.figure()
plt.plot(range(len(record_sum_rewards)), record_sum_rewards); plt.grid(True)
plt.xlabel("Episodes"); plt.ylabel("Rewards")

En son olarak da sırasıyla

İlk yorum yapan olun

Bir yanıt bırakın

E-posta hesabınız yayımlanmayacak.


*