深度强化学习的基础,要好好看
CartPole环境中,动作只有两个,向左移动和向右移动,但是状态是无限的,因为是连续的。这种情况下使用Q learning中表格的方式来记录状态就是不太现实的。所以可以使用神经网络来做拟合。
如果动作是无限的,神经网络的输入可以是(s, a),而如果动作是离散的,则可以把当前状态输入进去,得到每一个动作的Q值。
DQN就是使用了神经网络的Q learning。
需要注意的是因为Q learning有在动作中取max的操作,所以没办法处理非离散的动作。
核心是使用神经网络去学习Q函数。在这里有两个用来优化的模块:
* 经验回放是把采样的数据保存下来用于后续训练,有点类似之前提到的Q planning。同时也是为了让样本满足独立假设。
* 目标网络是为了防止不够稳定,先冻结参数针对目标网络做训练,然后再去更新目标网络。
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)
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_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
这里的一个关键是DQN的输入是state,输出的是所有action的价值。
有了这个能够处理连续状态空间的DQN之后,也可以用来处理一些视频游戏上的强化学习,输入图像,让DQN学习以图像为输入的强化学习。
DQN改进
DQN有两个简单的改进算法:double DQN和dueling DQN
* double DQN的目的是为了缓解DQN对Q值的过高估计问题。具体原因可以看中间那段解释,当神经网络出现误差的时候,会导致更新目标出现过高估计,而新的Q拟合之后,会进一步用来更新上一的状态的Q值。导致Q值的累积
所以double DQN是用两个独立的神经网络,一个用来选择最优动作,一个用来计算价值。对应到普通的DQN中,就是用训练网络来选择最优动作,而用目标网络计算价值。
Dueling DQN的核心思想则是把动作价值拆解成状态价值+动作优势,分别建模。
分别建模的好处在于,有的时候状态更加关键,而不在于动作。
有的读者可能会问:“为什么 Dueling DQN 会比 DQN 好?”部分原因在于 Dueling DQN 能更高效学习状态价值函数。每一次更新时,函数都会被更新,这也会影响到其他动作的值。而传统的 DQN 只会更新某个动作的值,其他动作的值就不会更新。因此,Dueling DQN 能够更加频繁、准确地学习状态价值函数。
在实现上还有一个特点需要注意:
因为拆解了动作价值函数后,无法保证V/A的唯一性,可能导致网络抖动。所以这里对A做了限制。
用了均值的这个式子之后,如果所有A值都减去C,就会影响Q值。
文章评论