Penyempurnaan DQN

Double DQN dan dueling head, beserta keterbatasannya.

Daftar isi

Dua penyempurnaan DQN yang paling dikenal, yaitu Double DQN dan dueling head, menargetkan dua kelemahan berbeda yang muncul setelah DQN dasar berhasil. Topik ini memperkenalkan keduanya pada tingkat konsep, lalu memperagakan masing-masing dengan demo terpisah pada observasi tetap. Pembaca akan melihat dueling head memecah Q(s,a)Q(s,a) menjadi V(s)V(s) dan A(s,a)A(s,a), serta Double DQN mengganti argmax target dari target network ke online network sehingga estimasi target berbeda dari vanilla DQN.

Bias maksimum dan overestimasi#

DQN dasar memperbarui target dengan y=r+γmax⁡a′q^(s′,a′;θ−)y = r + \gamma \max_{a'} \hat{q}(s',a';\theta^-). Operator max⁡\max bermasalah pada estimasi yang mengandung derau, sebab max⁡\max cenderung memilih aksi dengan estimasi tertinggi, yang sering kali terlalu optimis karena derau positif. Akibatnya, QQ yang dipelajari bias ke arah overestimasi, dan bias ini menyebar ke state sebelumnya melalui bootstrap. Pada lingkungan dengan banyak aksi atau reward yang berderau, overestimasi ini dapat menyesatkan kebijakan.

Double DQN memisahkan pemilihan aksi dari evaluasinya. Alih-alih memilih dan mengevaluasi dengan target network yang sama, Double DQN memilih aksi terbaik menurut online network arg⁡max⁡a′q^(s′,a′;θ)\arg\max_{a'} \hat{q}(s',a';\theta), lalu mengevaluasi nilai aksi tersebut dengan target network q^(s′,a′;θ−)\hat{q}(s',a';\theta^-). Karena online network biasanya lebih akurat pada kebijakan saat ini sementara target network memberi evaluasi yang lebih konservatif, pemisahan ini mengurangi overestimasi tanpa mengorbankan stabilitas target network.

Dueling head dan dekomposisi nilai#

Dueling head memecah keluaran jaringan menjadi dua, yaitu nilai state V(s)V(s) dan keunggulan aksi A(s,a)A(s,a), lalu menggabungkannya kembali menjadi Q(s,a)=V(s)+A(s,a)−Aˉ(s)Q(s,a) = V(s) + A(s,a) - \bar{A}(s). Pengurangan rata-rata Aˉ(s)\bar{A}(s) menjamin identifikasi yang unik, sebab tanpa itu pasangan VV dan AA dapat digeser dengan konstanta tanpa mengubah QQ. Dekomposisi ini membantu ketika banyak aksi memberi efek mirip, karena jaringan dapat belajar V(s)V(s) tanpa harus mengaitkannya ke setiap aksi.

Manfaat utama dueling muncul pada lingkungan dengan banyak aksi yang sebagian besar tidak relevan di state tertentu. Pada state di mana kebijakan jelas, baik dueling maupun DQN biasa memberi hasil mirip. Pada state di mana kebanyakan aksi setara, dueling mengalokasikan kapasitas ke V(s)V(s) yang umum, sehingga pembaruan pada satu aksi tidak menyesatkan estimasi state. Asumsi ini bersifat empiris dan hasilnya bergantung pada struktur lingkungan.

Demo dueling dan Double DQN#

Berkas penyempurnaan_dqn.py berikut memperagakan kedua penyempurnaan pada observasi tetap. Bagian pertama menjalankan forward pass jaringan dueling dan mencetak VV, AA, serta QQ hasil penggabungan. Bagian kedua membandingkan target vanilla DQN dengan target Double DQN pada batch dummy, di mana online network telah digeser beberapa langkah agar berbeda dari target network.

python penyempurnaan_dqn.py
import warnings; warnings.filterwarnings("ignore")
import numpy as np, torch
np.random.seed(0); torch.manual_seed(0)
import random; random.seed(0)
torch.set_printoptions(precision=4, sci_mode=False)

class DuelingQNet(torch.nn.Module):
    def __init__(self, obs_dim, n_actions, hidden=32):
        super().__init__()
        self.body = torch.nn.Sequential(
            torch.nn.Linear(obs_dim, hidden), torch.nn.ReLU(),
            torch.nn.Linear(hidden, hidden), torch.nn.ReLU(),
        )
        self.value_head = torch.nn.Linear(hidden, 1)
        self.adv_head = torch.nn.Linear(hidden, n_actions)
    def forward(self, x):
        h = self.body(x)
        v = self.value_head(h)
        a = self.adv_head(h)
        q = v + (a - a.mean(dim=1, keepdim=True))
        return q, v, a

class QNet(torch.nn.Module):
    def __init__(self, obs_dim, n_actions, hidden=32):
        super().__init__()
        self.net = torch.nn.Sequential(
            torch.nn.Linear(obs_dim, hidden), torch.nn.ReLU(),
            torch.nn.Linear(hidden, hidden), torch.nn.ReLU(),
            torch.nn.Linear(hidden, n_actions),
        )
    def forward(self, x):
        return self.net(x)

# Demo dueling forward pass
duel = DuelingQNet(4, 2, 32)
obs = torch.tensor([[0.1, -0.2, 0.05, 0.3]], dtype=torch.float32)
with torch.no_grad():
    q, v, a = duel(obs)
print("V(s)     =", v.flatten())
print("A(s, .)  =", a.flatten())
print("Q(s, .)  =", q.flatten())

# Demo Double vs vanilla DQN target pada batch dummy
GAMMA = 0.99
BATCH = 4
N_ACTIONS = 2
online = QNet(4, N_ACTIONS, 32)
target = QNet(4, N_ACTIONS, 32)
target.load_state_dict(online.state_dict())
# Latih online beberapa langkah agar bobotnya bergeser dari target,
# mensimulasikan target network yang ketinggalan beberapa langkah pembaruan.
opt = torch.optim.SGD(online.parameters(), lr=0.5)
obs_fix = torch.tensor([[0.1, -0.2, 0.05, 0.3]], dtype=torch.float32)
tgt_fix = torch.tensor([[1.0, 0.0]])
for _ in range(10):
    loss = torch.nn.MSELoss()(online(obs_fix), tgt_fix)
    opt.zero_grad(); loss.backward(); opt.step()

np.random.seed(1)
s = torch.tensor(np.random.randn(BATCH, 4).astype(np.float32))
sn = torch.tensor(np.random.randn(BATCH, 4).astype(np.float32))
r = torch.tensor([1.0, 0.5, 0.0, -1.0])
dn = torch.tensor([0.0, 0.0, 1.0, 0.0])

with torch.no_grad():
    qn_online_sn = online(sn)
    qn_target_sn = target(sn)
    argmax_online = qn_online_sn.argmax(dim=1)
    argmax_target = qn_target_sn.argmax(dim=1)
    print("argmax_online =", argmax_online.tolist())
    print("argmax_target =", argmax_target.tolist())
    # vanilla: target net argmax + eval
    y_vanilla = r + GAMMA * (1 - dn) * qn_target_sn.max(dim=1).values
    # double: argmax from online, eval with target
    qn_double = qn_target_sn.gather(1, argmax_online.unsqueeze(1)).squeeze(1)
    y_double = r + GAMMA * (1 - dn) * qn_double

print("y_vanilla =", y_vanilla)
print("y_double  =", y_double)
print("diff      =", f"{(y_vanilla - y_double).abs().mean().item():.4f}")
text Keluaran
V(s)     = tensor([0.0934])
A(s, .)  = tensor([-0.0543,  0.1070])
Q(s, .)  = tensor([0.0127, 0.1740])
argmax_online = [0, 0, 0, 0]
argmax_target = [1, 1, 1, 1]
y_vanilla = tensor([ 1.0942,  0.6892,  0.0000, -0.8908])
y_double  = tensor([ 1.0006,  0.5723,  0.0000, -1.0889])
diff      = 0.1021

Membaca demo dueling dan double#

Pada demo dueling, V(s)=0,0934V(s)=0{,}0934 adalah nilai state, sedangkan A(s,⋅)=[−0,0543,0,1070]A(s,\cdot)= [-0{,}0543, 0{,}1070] adalah keunggulan tiap aksi relatif terhadap rata-rata. Penggabungan Q(s,a)=V(s)+A(s,a)−Aˉ(s)Q(s,a)=V(s)+A(s,a)-\bar{A}(s) menghasilkan Q(s,⋅)=[0,0127,0,1740]Q(s,\cdot)=[0{,}0127, 0{,}1740], yang memang merupakan jumlah nilai-nilai tersebut setelah pengurangan rata-rata. Pemisahan ini memungkinkan jaringan belajar nilai state tanpa mengaitkannya ke setiap aksi, yang membantu ketika banyak aksi memberi efek mirip.

Pada demo Double DQN, argmax menurut online network adalah [0,0,0,0][0,0,0,0], sedangkan argmax menurut target network adalah [1,1,1,1][1,1,1,1]. Perbedaan ini langsung memengaruhi target: yvanilla=[1,0942,0,6892,0,0000,−0,8908]y_\text{vanilla}=[1{,}0942, 0{,}6892, 0{,}0000, -0{,}8908] versus ydouble=[1,0006,0,5723,0,0000,−1,0889]y_\text{double}=[1{,}0006, 0{,}5723, 0{,}0000, -1{,}0889], dengan rata-rata selisih mutlak 0,1021. Vanilla DQN mengevaluasi aksi terbaik menurut target network sendiri, yang cenderung overestimasi, sedangkan Double DQN memilih aksi dengan online network lalu mengevaluasinya dengan target network, sehingga estimasi target lebih konservatif.

Kesalahan umum#

Menganggap Double DQN selalu menghasilkan target lebih rendah. Penyebabnya, hasil demo yang menunjukkan ydouble<yvanillay_\text{double} < y_\text{vanilla} digeneralisasi. Akibatnya, bias arah dipakai sebagai diagnostik padahal arahnya bergantung pada kebetulan bobot. Perbaikannya, evaluasi Double DQN melalui rata-rata selisih pada banyak batch, bukan satu contoh, dan jangan menganggap arah tetap.

Lupa mengurangkan rata-rata keunggulan pada dueling head. Penyebabnya, Q(s,a)=V(s)+A(s,a)Q(s,a)=V(s)+A(s,a) tanpa pengurangan Aˉ(s)\bar{A}(s). Akibatnya, VV dan AA tidak teridentifikasi unik, sehingga pelatihan tidak stabil karena banyak pasangan memberi QQ yang sama. Perbaikannya, kurangi rata-rata AA sepanjang dimensi aksi, atau gunakan bentuk identifikasi lain seperti maksimum.

Ringkasan#

  • Double DQN memisahkan pemilihan aksi (arg⁡max⁡\arg\max dari online network) dari evaluasinya (nilai dari target network), mengurangi overestimasi akibat operator max⁡\max pada estimasi berderau.
  • Dueling head memecah Q(s,a)=V(s)+A(s,a)−Aˉ(s)Q(s,a)=V(s)+A(s,a)-\bar{A}(s), memungkinkan jaringan belajar nilai state tanpa mengaitkannya ke setiap aksi.
  • Pada demo, argmax online dan target berbeda sepenuhnya [0,0,0,0][0,0,0,0] versus [1,1,1,1][1,1,1,1], menghasilkan rata-rata selisih target 0,1021 antara vanilla dan Double DQN.

Referensi#