阐述、总结【动手学强化学习】章节内容的学习情况,复现并理解代码。
文章目录
一、算法背景
1.1算法目标
给定“黑盒”环境,求解最优policy
1.2问题
前序章节中以MC或TD方法构建model-free算法,以求解“黑盒”模型下的最优policy,但在action value(q(s,a))估计以Q_table的表格形式存储记录,在状态、动作空间较小时能够很好适应,但状态、动作空间扩大以后,算法运行时将承载巨大的存储压力。更甚者,当状态或者动作连续的时候,就有无限个状态动作对,我们更加无法使用这种表格形式来记录各个状态动作对的q(s,a)值。
1.3解决方法
- 🌟函数拟合
需要用函数拟合的方法来估计q(s,a)值,即将这个复杂的q(s,a)值表格视作数据,使用一个参数化的函数q(s,a,θ)来拟合这些数据。
θ为拟合函数的参数。
这种函数拟合的方法存在一定的精度损失,因此被称为值函数近似方法(function approximation)。
✅即将介绍的 DQN 算法便可以用来解决连续状态下离散动作的问题。
二、DQN算法
- 🌟算法类型
环境依赖:❌model-based ✅model-free
价值估计:✅non-incremental ❌incremental(当replay_buffer数据的数量超过一定值后,才进行Q网络训练,并进行Q值估计)
价值表征:❌tabular representation ✅function representation(不再基于Q-table方式存储q(s,a)值,而是采用“函数拟合”)
学习方式:❌on-policy ✅off-policy
策略表征:✅value-based ❌policy-based
2.1必要说明
Q网络建模
本节算法需要通过值函数近似的方法进行Q(s,a)的估计,相对于线性函数拟合,深度神经网络在函数拟合方面有更高的精度,因此鉴于神经网络具有强大的表达能力,因此我们可以用一个神经网络来表示动作价值函数Q(s,a)。
一般而言Q网络的输入输出有三种常见建模方式:
①输入:(s,a),输出:标量Q
②输入:s,输出:所有动作空间的Q
③输入:s,输出:max_Q
经验回放
在一般的有监督学习中,假设训练数据是独立同分布的,我们每次训练神经网络的时候从训练数据中随机采样一个或若干个数据来进行梯度下降,随着学习的不断进行,每一个训练数据会被使用多次。
在原来的 Q-learning 算法中,每一个数据只会用来更新一次q值。
DQN 算法采用了经验回放(experience replay)方法,具体做法为维护一个回放缓冲区(replay buffer),将每次从环境中采样得到的四元组数据(状态、动作、奖励、下一状态)存储到回放缓冲区中,训练 Q 网络的时候再从回放缓冲区中随机采样若干数据来进行训练。
- 经验回放(experience replay)优势
①使样本满足独立假设。在 MDP 中交互采样得到的数据本身不满足独立假设,因为这一时刻的状态和上一时刻的状态有关。非独立同分布的数据对训练神经网络有很大的影响,会使神经网络拟合到最近训练的数据上。采用经验回放可以打破样本之间的相关性,让其满足独立假设。
②提高样本效率。每一个样本可以被使用多次,十分适合深度神经网络的梯度学习。
双Q网络更新
回顾基于时序差分的更新Q(s,a)的过程
Q
(
s
,
a
)
←
Q
(
s
,
a
)
+
α
[
r
+
γ
max
a
′
∈
A
Q
(
s
′
,
a
′
)
−
Q
(
s
,
a
)
]
Q(s,a)\leftarrow Q(s,a)+\alpha\left[r+\gamma\max_{a^{\prime}\in\mathcal{A}}Q(s^{\prime},a^{\prime})-Q(s,a)\right]
Q(s,a)←Q(s,a)+α[r+γa′∈AmaxQ(s′,a′)−Q(s,a)]
于是,对于采样得到的N个数据{(s,a,r,s’)} ,我们可以很自然地将 Q 网络的损失函数构造为均方误差的形式:
ω
∗
=
arg
min
ω
1
2
N
∑
i
=
1
N
[
Q
ω
(
s
i
,
a
i
)
−
(
r
i
+
γ
max
a
′
Q
ω
(
s
i
′
,
a
′
)
)
]
2
\omega^*=\arg\min_\omega\frac1{2N}\sum_{i=1}^N\left[Q_\omega\left(s_i,a_i\right)-\left(r_i+\gamma\max_{a^{\prime}}Q_\omega\left(s_i^{\prime},a^{\prime}\right)\right)\right]^2
ω∗=argωmin2N1i=1∑N[Qω(si,ai)−(ri+γa′maxQω(si′,a′))]2
由于DQN 算法最终更新的目标是让 Q ω ( s , a ) Q_\omega(s,a) Qω(s,a)逼近TD target( r + γ max a ′ Q ω ( s ′ , a ′ ) r+\gamma\operatorname*{max}_{a^{\prime}}Q_{\omega}\left(s^{\prime},a^{\prime}\right) r+γmaxa′Qω(s′,a′)),但由于TD error内本身就包含神经网络的输出,因此在更新网络参数的同时目标也在不断地改变,这非常容易造成神经网络训练的不稳定性。
- 解决方法
使用双Q网络,即main network与target network。
用main network去估计 Q ω ( s , a ) Q_\omega(s,a) Qω(s,a)
用target network去估计 m a x a ′ Q ω ( s ′ , a ′ ) {max}_{a^{\prime}}Q_{\omega}\left(s^{\prime},a^{\prime}\right) maxa′Qω(s′,a′)
2.2伪代码
- 算法流程简述:
①初始化Q网络模型:设置main_network,target_network网络层数、神经元个数、激活函数等;
②初始化“经验回放池”:设置经验回放池大小,即样本(s,a,r,s’,done)的个数,done为标志位,表示是否达到terminal state;
③采样填“池”:根据main_network不断step()获取样本存放至经验回放池;
④训练main_network:根据main_network估计(s,a)的Q值,根据target_network估计s’的最优Q值(即TD_target),将main_network的损失函数设置为TD_error的均方误差,并训练更新main_network;
⑤更新target_network:当main_network更新次数达到设置的阈值(例如:count=10)后,将main_network的网络参数复制给target_network。
2.3算法代码
import random
import gym
import numpy as np
import collections
from tqdm import tqdm
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt
import rl_utils
class ReplayBuffer:
''' 经验回放池 '''
def __init__(self, capacity):
self.buffer = collections.deque(maxlen=capacity) # 队列,先进先出
def add(self, state, action, reward, next_state, done): # 将数据加入buffer
self.buffer.append((state, action, reward, next_state, done))
def sample(self, batch_size): # 从buffer中采样数据,数量为batch_size
transitions = random.sample(self.buffer, batch_size)
state, action, reward, next_state, done = zip(*transitions)
return np.array(state), action, reward, np.array(next_state), done
def size(self): # 目前buffer中数据的数量
return len(self.buffer)
class Qnet(torch.nn.Module):
''' 只有一层隐藏层的Q网络 '''
def __init__(self, state_dim, hidden_dim, action_dim):
super(Qnet, self).__init__()
self.fc1 = torch.nn.Linear(state_dim, hidden_dim)
self.fc2 = torch.nn.Linear(hidden_dim, action_dim)
def forward(self, x):
x = F.relu(self.fc1(x)) # 隐藏层使用ReLU激活函数
return self.fc2(x)
class DQN:
''' DQN算法 '''
def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,
epsilon, target_update, device):
self.action_dim = action_dim
self.q_net = Qnet(state_dim, hidden_dim,
self.action_dim).to(device) # Q网络
# 目标网络
self.target_q_net = Qnet(state_dim, hidden_dim,
self.action_dim).to(device)
# 使用Adam优化器
self.optimizer = torch.optim.Adam(self.q_net.parameters(),
lr=learning_rate)
self.gamma = gamma # 折扣因子
self.epsilon = epsilon # epsilon-贪婪策略
self.target_update = target_update # 目标网络更新频率
self.count = 0 # 计数器,记录更新次数
self.device = device
def take_action(self, state): # epsilon-贪婪策略采取动作
if np.random.random() < self.epsilon:
action = np.random.randint(self.action_dim)
else:
state = torch.tensor([state], dtype=torch.float).to(
self.device) # 🌟将输入的状态数据转换为一个浮点类型的张量
# 🌟之前是基于Q_table去选取s下Q值最大的action,而这里是基于Q_net去选取
action = self.q_net(state).argmax().item()
return action
def update(self, transition_dict):
states = torch.tensor(transition_dict['states'],
dtype=torch.float).to(self.device)
actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(
self.device)
rewards = torch.tensor(transition_dict['rewards'],
dtype=torch.float).view(-1, 1).to(self.device)
next_states = torch.tensor(transition_dict['next_states'],
dtype=torch.float).to(self.device)
dones = torch.tensor(transition_dict['dones'],
dtype=torch.float).view(-1, 1).to(self.device)
q_values = self.q_net(states).gather(1, actions) # Q值
# 下个状态的最大Q值
max_next_q_values = self.target_q_net(next_states).max(
1)[0].view(-1, 1) # 🌟计算Q_target采用的是target_q_net
q_targets = rewards + self.gamma * max_next_q_values * (1 - dones
) # TD误差目标
dqn_loss = torch.mean(F.mse_loss(q_values, q_targets)) # 均方误差损失函数
self.optimizer.zero_grad() # PyTorch中默认梯度会累积,这里需要显式将梯度置为0
dqn_loss.backward() # 反向传播更新参数
self.optimizer.step()
if self.count % self.target_update == 0:
self.target_q_net.load_state_dict(
self.q_net.state_dict()) # 更新目标网络
self.count += 1
lr = 2e-3
num_episodes = 500
hidden_dim = 128 # 🌟隐藏层层数
gamma = 0.98
epsilon = 0.01
target_update = 10 # 🌟训练10次网络更新目标网络1次
buffer_size = 10000
minimal_size = 500
batch_size = 64
device = torch.device("cuda") if torch.cuda.is_available() else torch.device(
"cpu")
env_name = 'CartPole-v0'
env = gym.make(env_name)
random.seed(0)
np.random.seed(0)
env.seed(0)
torch.manual_seed(0)
replay_buffer = ReplayBuffer(buffer_size)
state_dim = env.observation_space.shape[0] # 🌟state=4
action_dim = env.action_space.n # 🌟action=2
agent = DQN(state_dim, hidden_dim, action_dim, lr, gamma, epsilon,
target_update, device)
return_list = []
for i in range(10):
with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar:
for i_episode in range(int(num_episodes / 10)):
episode_return = 0
state = env.reset()
done = False
while not done:
action = agent.take_action(state)
next_state, reward, done, _ = env.step(action)
replay_buffer.add(state, action, reward, next_state, done)
state = next_state
episode_return += reward
# 当buffer数据的数量超过一定值后,才进行Q网络训练
if replay_buffer.size() > minimal_size:
b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size)
transition_dict = {
'states': b_s,
'actions': b_a,
'next_states': b_ns,
'rewards': b_r,
'dones': b_d
}
agent.update(transition_dict)
return_list.append(episode_return)
if (i_episode + 1) % 10 == 0:
pbar.set_postfix({
'episode':
'%d' % (num_episodes / 10 * i + i_episode + 1),
'return':
'%.3f' % np.mean(return_list[-10:])
})
pbar.update(1)
episodes_list = list(range(len(return_list)))
plt.plot(episodes_list, return_list)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('DQN on {}'.format(env_name))
plt.show()
mv_return = rl_utils.moving_average(return_list, 9)
plt.plot(episodes_list, mv_return)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('DQN on {}'.format(env_name))
plt.show()
2.4运行结果
Iteration 0: 100%|██████████| 50/50 [00:01<00:00, 25.44it/s, episode=50, return=9.000]
Iteration 1: 100%|██████████| 50/50 [00:01<00:00, 39.72it/s, episode=100, return=17.900]
Iteration 2: 100%|██████████| 50/50 [00:10<00:00, 4.66it/s, episode=150, return=199.600]
Iteration 3: 100%|██████████| 50/50 [00:16<00:00, 3.12it/s, episode=200, return=191.900]
Iteration 4: 100%|██████████| 50/50 [00:15<00:00, 3.14it/s, episode=250, return=195.400]
Iteration 5: 100%|██████████| 50/50 [00:15<00:00, 3.24it/s, episode=300, return=195.500]
Iteration 6: 100%|██████████| 50/50 [00:17<00:00, 2.84it/s, episode=350, return=197.700]
Iteration 7: 100%|██████████| 50/50 [00:16<00:00, 3.02it/s, episode=400, return=192.800]
Iteration 8: 100%|██████████| 50/50 [00:17<00:00, 2.86it/s, episode=450, return=199.700]
Iteration 9: 100%|██████████| 50/50 [00:16<00:00, 3.06it/s, episode=500, return=169.000]
2.5 算法流程说明
初始化参数
lr = 2e-3
num_episodes = 500
hidden_dim = 128 # 🌟隐藏层层数,❓为什么要这么大?
gamma = 0.98
epsilon = 0.01
target_update = 10 # 🌟训练10次网络更新目标网络1次
buffer_size = 10000
minimal_size = 500
batch_size = 64
device = torch.device("cuda") if torch.cuda.is_available() else torch.device(
"cpu")
设置学习率、周期数、Q网络隐藏层层数、折扣率、ε-greedy参数、target_network更新频率、repaly_buffer大小、Q网络训练使用的最小样本数、Q网络训练优化器(这里是Adam)的采样数、训练采用设备(gpu or cpu)
状态、动作空间、奖励设置
env_name = 'CartPole-v0'
env = gym.make(env_name)
random.seed(0)
np.random.seed(0)
env.seed(0)
torch.manual_seed(0)
-
环境初始化
环境的初始化采用的是gym里自带的“CartPole-v0”环境,又称“车杆”环境
-
状态空间
智能体的状态是一个维数为 4 的向量,每一维都是连续的
-
动作空间
动作是离散的,动作空间大小为 2
-
奖励与目标
在游戏中每坚持一帧,智能体能获得分数为 1 的奖励,坚持时间越长,则最后的分数越高,坚持 200 帧即可获得最高的分数。
初始化“经验回放池”与双Q网络模型
replay_buffer = ReplayBuffer(buffer_size)
...
def __init__(self, capacity):
self.buffer = collections.deque(maxlen=capacity) # 队列,先进先出
...
state_dim = env.observation_space.shape[0] # 🌟state=4
action_dim = env.action_space.n # 🌟action=2
agent = DQN(state_dim, hidden_dim, action_dim, lr, gamma, epsilon,
target_update, device)
...
def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,
epsilon, target_update, device):
self.action_dim = action_dim
self.q_net = Qnet(state_dim, hidden_dim,
self.action_dim).to(device) # Q网络
# 目标网络
self.target_q_net = Qnet(state_dim, hidden_dim,
self.action_dim).to(device)
# 使用Adam优化器
self.optimizer = torch.optim.Adam(self.q_net.parameters(),
lr=learning_rate)
self.gamma = gamma # 折扣因子
self.epsilon = epsilon # epsilon-贪婪策略
self.target_update = target_update # 目标网络更新频率
self.count = 0 # 计数器,记录更新次数
self.device = device
...
def __init__(self, state_dim, hidden_dim, action_dim):
super(Qnet, self).__init__()
self.fc1 = torch.nn.Linear(state_dim, hidden_dim)
self.fc2 = torch.nn.Linear(hidden_dim, action_dim)
def forward(self, x):
x = F.relu(self.fc1(x)) # 隐藏层使用ReLU激活函数
return self.fc2(x)
设置replay_buffer大小,并将其设置为“队列”
设置main_network、target_network为4-128-2的全连接网络(线性层),输入为state_dim =4,隐藏层输出hidden_dim=128,输出为action_dim =2,激活函数为relu。
在调用self.q_net(state)时,会调用forward函数,因此此神经网络的输出为:
y
=
f
c
2
(
r
e
l
u
(
f
c
1
(
x
)
)
)
,
x
=
s
t
a
t
e
s
y=fc_2(relu(fc_1(x))),x=states
y=fc2(relu(fc1(x))),x=states
采样填"池"
for i_episode in range(int(num_episodes / 10)):
episode_return = 0
state = env.reset()
done = False
while not done:
action = agent.take_action(state) #episode采样过程中,q_net有更新,并且take_action是基于q_net输出的Q值决定的
next_state, reward, done, _ = env.step(action)
replay_buffer.add(state, action, reward, next_state, done)
state = next_state
episode_return += reward
...
def take_action(self, state): # epsilon-贪婪策略采取动作
if np.random.random() < self.epsilon:
action = np.random.randint(self.action_dim)
else:
state = torch.tensor([state], dtype=torch.float).to(
self.device) # 🌟将输入的状态数据转换为一个浮点类型的张量
# 🌟之前是基于Q_table去选取s下Q值最大的action,而这里是基于Q_net去选取
action = self.q_net(state).argmax().item() # 这里action的选择还是依据Q值决定的
...
def add(self, state, action, reward, next_state, done): # 将数据加入buffer
self.buffer.append((state, action, reward, next_state, done))
①根据当前state:s采用main_network估计的Q值,采用ε-greedy策略进行aciton获取:agent.take_action(state)
②再通过与环境交互env.step(action),获取样本(s,a,r,s’,done)
③将样本(s,a,r,s’,done)加入至replay_buffer中:replay_buffer.add
④统计周期episode的即时奖励累加值:* episode_return += reward*
训练main_network
# 当buffer数据的数量超过一定值后,才进行Q网络训练
if replay_buffer.size() > minimal_size:
b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size)
transition_dict = {
'states': b_s,
'actions': b_a,
'next_states': b_ns,
'rewards': b_r,
'dones': b_d
}
agent.update(transition_dict)
...
def sample(self, batch_size): # 从buffer中采样数据,数量为batch_size
transitions = random.sample(self.buffer, batch_size)
state, action, reward, next_state, done = zip(*transitions)
return np.array(state), action, reward, np.array(next_state), done
...
def update(self, transition_dict):
states = torch.tensor(transition_dict['states'],
dtype=torch.float).to(self.device)
actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(
self.device)
rewards = torch.tensor(transition_dict['rewards'],
dtype=torch.float).view(-1, 1).to(self.device)
next_states = torch.tensor(transition_dict['next_states'],
dtype=torch.float).to(self.device)
dones = torch.tensor(transition_dict['dones'],
dtype=torch.float).view(-1, 1).to(self.device)
q_values = self.q_net(states).gather(1, actions) # Q值
# 下个状态的最大Q值
max_next_q_values = self.target_q_net(next_states).max(
1)[0].view(-1, 1) # 🌟计算Q_target采用的是target_q_net
q_targets = rewards + self.gamma * max_next_q_values * (1 - dones
) # TD误差目标
dqn_loss = torch.mean(F.mse_loss(q_values, q_targets)) # 均方误差损失函数
self.optimizer.zero_grad() # PyTorch中默认梯度会累积,这里需要显式将梯度置为0
dqn_loss.backward() # 反向传播更新参数
self.optimizer.step() #仅更新self.q_net网络参数, self.optimizer仅仅根据q_net构建的
①replay_buffer样本大小超过minimal_size=500后,通过replay_buffer.sample(batch_size) 中随机采样random.sample(self.buffer, batch_size) 用于训练的batch_size=64个样本数,并以“字典transition_dict ”形式保存;
②将字典内的数据转换为tensor张量;
③通过main_network估计batch_size个样本的(s,a)的Q值: q_values
④通过target_network估计batch_size个样本的s’的最优Q值: max_next_q_values ,即TD_target
max
a
′
∈
A
Q
t
a
r
g
e
t
(
s
′
,
a
′
)
\max_{a^\prime\in\mathcal{A}}Q_{target}(s^\prime,a^\prime)
a′∈AmaxQtarget(s′,a′)
⑤计算TD_error
r
+
γ
max
a
′
∈
A
Q
t
a
r
g
e
t
(
s
′
,
a
′
)
−
Q
m
a
i
n
(
s
,
a
)
r+\gamma\max_{a^{\prime}\in\mathcal{A}}Q_{target}(s^{\prime},a^{\prime})-Q_{main}(s,a)
r+γa′∈AmaxQtarget(s′,a′)−Qmain(s,a)
⑥计算损失函数:*dqn_loss *,设置为TD_error的均方误差:dqn_loss = torch.mean(F.mse_loss(q_values, q_targets))
1
2
N
∑
i
=
1
N
[
Q
m
a
i
n
(
s
i
,
a
i
)
−
(
r
i
+
γ
max
a
′
Q
t
a
r
g
e
t
(
s
i
′
,
a
′
)
)
]
2
\frac{1}{2N}\sum_{i=1}^{N}\left[Q_{main}\left(s_{i},a_{i}\right)-\left(r_{i}+\gamma\max_{a^{\prime}}Q_{target}\left(s_{i}^{\prime},a^{\prime}\right)\right)\right]^{2}
2N1i=1∑N[Qmain(si,ai)−(ri+γa′maxQtarget(si′,a′))]2
⑦训练main_network:梯度清零+反向传播+参数更新(这里不懂的可以补充以下深度学习的基础知识,参考“动手学深度学习”)
更新target_network
if self.count % self.target_update == 0:
self.target_q_net.load_state_dict(
self.q_net.state_dict()) # 更新target_q_net目标网络
self.count += 1
当main_network更新次数*count *累计满10次后,将main_network的网络参数复制给target_network。
三、疑问
3.1为什么Q网络的隐藏层层数要设置为128?
答:大概率是根据人为经验设置,可以设置为其它数量,以查看其训练效果。
将其设置为“256”后,运行结果如下所示,episode_return的统计结果近似,但训练时间成本有所增加,收敛速度更快;设置为64后,反之。因此存在一个“博弈”问题,本身可以通过调参解决。
- hidden_dim=64
Iteration 0: 100%|██████████| 50/50 [00:00<00:00, 401.24it/s, episode=50, return=9.000]
Iteration 1: 100%|██████████| 50/50 [00:00<00:00, 61.46it/s, episode=100, return=12.200]
Iteration 2: 100%|██████████| 50/50 [00:02<00:00, 23.53it/s, episode=150, return=39.400]
Iteration 3: 100%|██████████| 50/50 [00:09<00:00, 5.47it/s, episode=200, return=135.100]
Iteration 4: 100%|██████████| 50/50 [00:13<00:00, 3.62it/s, episode=250, return=146.300]
Iteration 5: 100%|██████████| 50/50 [00:14<00:00, 3.45it/s, episode=300, return=165.700]
Iteration 6: 100%|██████████| 50/50 [00:15<00:00, 3.23it/s, episode=350, return=185.400]
Iteration 7: 100%|██████████| 50/50 [00:16<00:00, 2.99it/s, episode=400, return=178.800]
Iteration 8: 100%|██████████| 50/50 [00:18<00:00, 2.78it/s, episode=450, return=185.000]
Iteration 9: 100%|██████████| 50/50 [00:20<00:00, 2.43it/s, episode=500, return=194.400]
- hidden_dim=256
Iteration 0: 100%|██████████| 50/50 [00:01<00:00, 37.50it/s, episode=50, return=9.000]
Iteration 1: 100%|██████████| 50/50 [00:03<00:00, 13.39it/s, episode=100, return=133.000]
Iteration 2: 100%|██████████| 50/50 [00:15<00:00, 3.19it/s, episode=150, return=197.000]
Iteration 3: 100%|██████████| 50/50 [00:16<00:00, 3.10it/s, episode=200, return=195.500]
Iteration 4: 100%|██████████| 50/50 [00:19<00:00, 2.55it/s, episode=250, return=184.600]
Iteration 5: 100%|██████████| 50/50 [00:16<00:00, 2.96it/s, episode=300, return=200.000]
Iteration 6: 100%|██████████| 50/50 [00:18<00:00, 2.74it/s, episode=350, return=200.000]
Iteration 7: 100%|██████████| 50/50 [00:17<00:00, 2.89it/s, episode=400, return=167.900]
Iteration 8: 100%|██████████| 50/50 [00:17<00:00, 2.85it/s, episode=450, return=200.000]
Iteration 9: 100%|██████████| 50/50 [00:16<00:00, 3.12it/s, episode=500, return=198.500]
3.2 main_network的损失函数为什么设置为TD_error的均方误差?
答:回顾“时序差分:TD”算法的update q-value的过程,就是不断将q(s,a)逼近TD_target的过程。
Q
(
s
t
,
a
t
)
←
Q
(
s
t
,
a
t
)
+
α
[
r
t
+
γ
Q
(
s
t
+
1
,
a
t
+
1
)
−
Q
(
s
t
,
a
t
)
]
Q(s_t,a_t)\leftarrow Q(s_t,a_t)+\alpha[r_t+\gamma Q(s_{t+1},a_{t+1})-Q(s_t,a_t)]
Q(st,at)←Q(st,at)+α[rt+γQ(st+1,at+1)−Q(st,at)]
因此在这里训练main_network过程中,将损失函数定义为TD_error,即最小化TD_error,使得
Q
m
a
i
n
(
s
,
a
)
Q_{main}(s,a)
Qmain(s,a)与
r
+
γ
max
a
′
∈
A
Q
t
a
r
g
e
t
(
s
′
,
a
′
)
r+\gamma\max_{a^{\prime}\in\mathcal{A}}Q_{target}(s^{\prime},a^{\prime})
r+γmaxa′∈AQtarget(s′,a′)足够接近,与TD算法的思路是一样的。
3.3 DQN算法是on-policy还是off-policy?
DQN算法中采样填“池”步骤是基于main_network的Q值与ε-greedy策略进行take action的,但最终优化的是target_network,因此采样和优化的policy是不一致的,因此可以判定为off-policy。
同样,从replay_buffer的定义出发,每次更新main_network的参数时都是随机从replay_buffer中采样,因此采样过程中可能使用到之前policy产生的样本,因此从这个角度也可以判定为是off-policy。
简单判定标准:“🌟采用了replay_buffer,肯定就是off-policy”
总结
- 值函数近似方法中最经典的方法就是DQN,其本质是通过“函数拟合”的方法去估计Q值,从而避免采用“表格式”Q-table的方法记录更新Q值的不足,即状态、动作空间巨大时导致的“存储压力”问题,能够有效解决“连续状态下离散动作”的序贯决策问题。