Reinforcement Learning
Rehber Yapay Zeka RL · Multi-Agent

Multi-Agent RL
MARL & PettingZoo.

Tek ajandan çok ajanlı sistemlere geçiş. Cooperative, competitive ve mixed ortamlarda QMIX, MADDPG ve self-play ile ölçeklenebilir çok-ajan öğrenmesi.

00 Multi-Agent Sistemler

Gerçek dünya problemlerinin büyük çoğunluğu tek ajan değil, birden fazla karar vericinin etkileşimi içerir.

Tekil ajan RL, ortamın sadece bir karar vericiye sahip olduğunu varsayar. Oysa trafik sinyalizasyonu, çok oyunculu oyunlar, otonom araç filoleri, piyasa simülasyonları ve robot ekipleri gibi uygulamalarda birden fazla ajan eş zamanlı olarak gözlem yapar, eylem seçer ve çevreyi dönüştürür. Bu durum tekil RL'nin temel varsayımını kırar: diğer ajanlar da öğreniyor olduğu için ortam artık stasyoner değildir.

TürÖdül YapısıÖrnekler
CooperativeOrtak global ödül; tüm ajanlar aynı amacı paylaşırRobot koordinasyonu, multi-drone kurtarma
CompetitiveSıfır toplamlı; bir ajanın kazancı diğerinin kaybıSatranç, Go, poker, savaş oyunları
MixedKarma; takım içi işbirliği + takımlar arası rekabetFutbol, StarCraft II, çok ekipli müzakere
NON-STATIONARITY SORUNU

Ajan A'nın policy'si değiştikçe ajan B'nin gözlem-eylem-ödül dağılımı da değişir. Her ajanın bakış açısından ortam Markov değildir; standart tekil RL algoritmalarının yakınsama garantileri geçerliliğini yitirir.

01 MARL Formal Tanım

Stokastik oyun çerçevesi, multi-agent RL'nin matematiksel temelidir.

Multi-agent ortamı Stokastik Oyun (Markov Oyunu) olarak modellenir: G = (N, S, A, P, R, γ)

NAjan sayısı. i ∈ {1, …, N} indeksiyle her ajan belirtilir.
SGlobal state uzayı. Tüm ajanların ortak ortam durumunu temsil eder.
A = A¹×…×AᴺJoint action uzayı. Her adımda tüm ajanlar eş zamanlı eylem seçer; ajan sayısıyla üstel büyür.
P(s'|s,a)Joint transition fonksiyonu. Tüm ajanların eylem vektörüne bağlıdır.
Rⁱ(s,a)Ajan i'nin ödül fonksiyonu. Cooperative'de Rⁱ = R; competitive'de Σ Rⁱ = 0.

Nash Equilibrium: Hiçbir ajanın tek taraflı policy değişikliğiyle kazancını artıramayacağı durum. MARL'ın teorik hedefi Nash Equilibrium'a yakınsamaktır; pratikte hesaplama bakımından son derece maliyetlidir.

Partial Observability: Gerçek uygulamalarda her ajan yalnızca kısmi gözlem oᵢ alır. Oyun POSG (Partially Observable Stochastic Game) çerçevesine taşınır; politikalar yerel gözlem geçmişine dayalı olmak zorundadır.

marl_env.py
import numpy as np

class SimpleCoopEnv:
    """2 ajan, ortak grid hedefi. Ödül: +10 hedefe, -0.1 her adım."""
    def __init__(self, size=5):
        self.size  = size
        self.moves = {0:(0,1), 1:(0,-1), 2:(1,0), 3:(-1,0)}

    def reset(self):
        self.pos  = [np.array([0,0]), np.array([0,1])]
        self.goal = np.array([self.size-1, self.size-1])
        return [np.concatenate([p, self.goal]) for p in self.pos]

    def step(self, actions):
        for i, a in enumerate(actions):
            self.pos[i] = np.clip(
                self.pos[i] + np.array(self.moves[a]), 0, self.size-1)
        done = all(np.array_equal(p, self.goal) for p in self.pos)
        r    = 10.0 if done else -0.1
        return [np.concatenate([p, self.goal]) for p in self.pos], [r,r], done

02 Centralized Training, Decentralized Execution

CTDE paradigması, eğitimde global bilgiyi kullanır; çalışma zamanında her ajan yalnızca kendi gözlemleriyle karar verir.

MARL'ın temel paradoksu şudur: eğitimde ne kadar çok bilgi paylaşılırsa koordinasyon o kadar iyi öğrenilir; ancak çalışma zamanında her ajan yalnızca yerel gözlemlerine erişebilir. CTDE bu gerilimi çözer.

Eğitim zamanı: Critic, global state s ve tüm ajanların eylemlerini a = (a¹,…,aᴺ) girdi olarak alır. Gradyanlar çok daha doğru hesaplanır.

Çalışma zamanı: Her ajanın policy'si yalnızca kendi yerel gözlemine oᵢ erişir. Merkezi bir koordinatör yoktur.

Eğitim  Centralized Critic: Q(s, a¹, a², …, aᴺ) — global bilgi
  Policy gradyanlarını hesapla, actor ağırlıklarını güncelle
Deploy  πᵢ(aᵢ | oᵢ) — sadece yerel gözlem, merkezi iletişim YOK
CTDE YÖNTEMLERİ

QMIX, VDN, MAPPO, MADDPG, COMA — hepsi CTDE paradigmasını farklı biçimlerde uygular. Ortak nokta: eğitimde merkezi bilgi, çalışmada dağıtık karar verme.

03 QMIX ve VDN — Value Decomposition

Value decomposition, joint Q fonksiyonunu bireysel Q fonksiyonlarının kombinasyonu olarak öğrenerek ölçeklenebilirlik sağlar.

VDN: Q_joint(s,a) = Σᵢ Qᵢ(oᵢ,aᵢ). Toplamsal ayrıştırma; ajanlar arası karmaşık etkileşimleri modelleyemez.

QMIX: Joint Q değeri bireysel Q değerlerinin monoton kombinasyonudur: ∂Q_joint/∂Qᵢ ≥ 0. Bu kısıt "argmax yerel ∈ argmax global" garantisini korur. Hypernetwork ile üretilen ağırlıklar global state'e koşulludur.

qmix_mixer.py
import torch, torch.nn as nn

class QMIXMixer(nn.Module):
    def __init__(self, n_agents, state_dim, embed=32):
        super().__init__()
        self.n = n_agents; self.e = embed
        self.hw1 = nn.Sequential(
            nn.Linear(state_dim, embed), nn.ReLU(),
            nn.Linear(embed, n_agents * embed))
        self.hw2 = nn.Sequential(
            nn.Linear(state_dim, embed), nn.ReLU(),
            nn.Linear(embed, embed))
        self.hb1 = nn.Linear(state_dim, embed)
        self.hb2 = nn.Sequential(
            nn.Linear(state_dim, embed), nn.ReLU(), nn.Linear(embed, 1))

    def forward(self, qs, s):
        # qs: (B, n_agents)  s: (B, state_dim)
        bs = qs.shape[0]
        w1 = torch.abs(self.hw1(s)).view(bs, self.n, self.e)
        b1 = self.hb1(s).view(bs, 1, self.e)
        h  = torch.nn.functional.elu(
            torch.bmm(qs.unsqueeze(1), w1) + b1)
        w2 = torch.abs(self.hw2(s)).view(bs, self.e, 1)
        b2 = self.hb2(s).view(bs, 1, 1)
        return (torch.bmm(h, w2) + b2).squeeze()

04 MADDPG — Continuous Action Multi-Agent

MADDPG, DDPG'yi CTDE paradigmasıyla çok ajanlı sürekli eylem uzayına taşır.

Discrete action space'lerde QMIX güçlü çalışır. Ancak robotik manipülasyon, çok-drone navigasyon veya ekonomik simülasyonlar gibi uygulamalar sürekli eylem uzayı gerektirir. Her ajan i için ayrı bir actor πᵢ(aᵢ|oᵢ) ve centralized critic Qᵢ(x, a¹,…,aᴺ) eğitilir. Critic eğitim sırasında diğer ajanların eylemlerini de görür; non-stationarity'yi doğrudan ele alır.

maddpg.py
import torch, torch.nn as nn

class Actor(nn.Module):
    def __init__(self, obs_dim, act_dim, h=128):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(obs_dim, h), nn.ReLU(),
            nn.Linear(h, h),       nn.ReLU(),
            nn.Linear(h, act_dim), nn.Tanh())
    def forward(self, x): return self.net(x)

class CentralCritic(nn.Module):
    def __init__(self, total_obs, total_act, h=128):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(total_obs + total_act, h), nn.ReLU(),
            nn.Linear(h, h), nn.ReLU(),
            nn.Linear(h, 1))
    def forward(self, obs, acts):
        return self.net(torch.cat([obs, acts], dim=-1)).squeeze(-1)

def actor_loss(i, actors, critic, batch, N):
    """Ajan i için MADDPG policy kaybı."""
    obs  = [batch[f"o{j}"] for j in range(N)]
    acts = [actors[j](obs[j]) if j == i
            else actors[j](obs[j]).detach()
            for j in range(N)]
    return -critic(
        torch.cat(obs, -1), torch.cat(acts, -1)).mean()

05 Self-Play — AlphaZero'dan OpenAI Five'a

Self-play, bir ajanı kendi geçmiş versiyonlarıyla rekabet ettirerek kademeli olarak güçlendirir.

Self-play, dış insan verisi veya el yazımı heuristicler gerektirmeden süper insan performansına ulaşabilir.

AlphaZero: Satranç ve Go'da self-play + MCTS kombinasyonuyla sıfırdan Grandmaster seviyesi. Policy ve value fonksiyonunu paylaşan tek bir sinir ağı.

OpenAI Five: Dota 2'de 5v5 tam oyun. Her ajan 20.000 yıllık self-play deneyimi gördü. Reward shaping ve observation featurization kritik.

League Training (AlphaStar): main, exploiter ve league exploiter ajanların oluşturduğu pool ile stratejik cycling'e karşı koruma.

self_play_pool.py
import random, copy

class SelfPlayPool:
    """Geçmiş policy snapshot havuzu. Stratejik cycling'i azaltır."""
    def __init__(self, model, max_size=20, save_every=1000):
        self.pool       = [copy.deepcopy(model).eval()]
        self.max_size   = max_size
        self.save_every = save_every
        self.steps      = 0

    def step(self, model):
        self.steps += 1
        if self.steps % self.save_every == 0:
            snap = copy.deepcopy(model).eval()
            if len(self.pool) >= self.max_size: self.pool.pop(0)
            self.pool.append(snap)

    def opponent(self, current, p_latest=0.5):
        if random.random() < p_latest: return current
        return random.choice(self.pool)

06 PettingZoo API

PettingZoo, multi-agent ortamlar için Gymnasium'un çok-ajan karşılığıdır.

AEC API Ajanlar sırayla hareket eder — satranç, poker gibi sıra tabanlı oyunlar için. env.last() ile gözlem, env.step(action) ile eylem gönderilir.
Parallel API Tüm ajanlar eş zamanlı hareket eder. Her step'te dict olarak tüm eylemler gönderilir; dict olarak tüm ödüller alınır.
pettingzoo_demo.py
from pettingzoo.classic import chess_v6
from pettingzoo.butterfly import cooperative_pong_v5

# AEC API — satranç (sıra tabanlı)
env = chess_v6.env()
env.reset(seed=42)
for agent in env.agent_iter():
    obs, r, term, trunc, info = env.last()
    if term or trunc:
        action = None
    else:
        action = env.action_space(agent).sample(
            mask=obs["action_mask"])
    env.step(action)
env.close()

# Parallel API — cooperative pong (eş zamanlı)
env = cooperative_pong_v5.parallel_env()
obs, _ = env.reset(seed=42)
while env.agents:
    actions = {a: env.action_space(a).sample() for a in env.agents}
    obs, rews, terms, truncs, _ = env.step(actions)
env.close()
SuperSuit: supersuit.pettingzoo_env_to_vec_env_v1(env) ile PettingZoo ortamlarını Gymnasium uyumlu vektörleştirilmiş ortama dönüştürür; Stable-Baselines3 ve RLlib entegrasyonunu kolaylaştırır.

07 Communication in MARL

Differentiable iletişim kanalları, öğrenilmiş mesajlarla ajan koordinasyonunu güçlendirir.

CommNet: Her ajan komşularının hidden state ortalamasını kendi gizli katmanına ekler. Basit ve ölçeklenebilir, ancak yalnızca ortalama ile sınırlı.

TarMAC: Attention-tabanlı iletişim. Her ajan key-value çiftiyle mesajını yönlendirir; alıcılar yalnızca kendilerine yönelik mesajları okur.

DIAL (Differentiable Inter-Agent Learning): Eğitimde mesajlar sürekli değerler olarak backprop'tan geçer; çalışma zamanında diskretize edilir.

commnet.py
import torch, torch.nn as nn

class CommNetStep(nn.Module):
    """
    Her ajana diğerlerinin hidden ortalamasını mesaj olarak iletir.
    hidden_states shape: (n_agents, batch, hidden_dim)
    """
    def __init__(self, h):
        super().__init__()
        self.enc = nn.Linear(h, h)
        self.com = nn.Linear(h * 2, h)

    def forward(self, hs):
        n    = hs.shape[0]
        msgs = self.enc(hs)
        mean = (msgs.sum(0, keepdim=True) - msgs) / (n - 1 + 1e-8)
        return torch.tanh(self.com(torch.cat([hs, mean], dim=-1)))

08 Pratik: PettingZoo Chess ile Self-Play Eğitimi

chess_v6 AEC ortamında self-play döngüsü, snapshot havuzu ve winrate takibi.

terminal — kurulum
pip install "pettingzoo[classic]" chess torch
chess_selfplay.py
import torch, torch.nn as nn
import numpy as np
import copy, random
from pettingzoo.classic import chess_v6

class ChessNet(nn.Module):
    def __init__(self, obs_shape, n_act):
        super().__init__()
        flat = int(np.prod(obs_shape))
        self.net = nn.Sequential(
            nn.Flatten(),
            nn.Linear(flat, 512), nn.ReLU(),
            nn.Linear(512, 256),  nn.ReLU(),
            nn.Linear(256, n_act))

    def act(self, obs_t, mask):
        logits = self.net(obs_t)
        logits[~torch.BoolTensor(mask)] = -1e9
        return logits.argmax().item()


def play_game(env, p0, p1):
    env.reset()
    pols   = {"player_0": p0, "player_1": p1}
    totals = {"player_0": 0.0, "player_1": 0.0}
    for ag in env.agent_iter():
        obs, r, done, trunc, _ = env.last()
        totals[ag] += r
        if done or trunc:
            env.step(None); continue
        t = torch.FloatTensor(obs["observation"]).unsqueeze(0)
        env.step(pols[ag].act(t, obs["action_mask"]))
    return totals


def train(n_ep=300, pool_max=10, save_every=25):
    env    = chess_v6.env()
    env.reset()
    obs_sh = env.observation_space("player_0")["observation"].shape
    n_act  = env.action_space("player_0").n
    model  = ChessNet(obs_sh, n_act)
    pool   = [copy.deepcopy(model).eval()]
    w = l = d = 0

    for ep in range(n_ep):
        opp    = random.choice(pool)
        key    = random.choice(["player_0", "player_1"])
        p0, p1 = (model, opp) if key == "player_0" else (opp, model)
        res    = play_game(env, p0, p1)
        r      = res[key]
        if   r > 0: w += 1
        elif r < 0: l += 1
        else:        d += 1

        if (ep + 1) % save_every == 0:
            if len(pool) >= pool_max: pool.pop(0)
            pool.append(copy.deepcopy(model).eval())
            tot = w + l + d
            print(f"[{ep+1:3d}] W:{w} L:{l} D:{d}  WR:{w/max(tot,1):.1%}")

    env.close()
    torch.save(model.state_dict(), "chess_sp.pt")
    print("Kaydedildi: chess_sp.pt")

if __name__ == "__main__":
    train()
BEKLENEN SONUÇ

300 episode sonunda model erken dönem rastgele politikaya karşı %55–65 kazanma oranına ulaşmalıdır. Gerçek güçlü ajan için ResNet + MCTS mimarisi ve binlerce episode gerekir; bu örnek self-play döngüsünün iskelet yapısını gösterir.