DQN:Deep Q-Network,即在Q-learning算法基础上加入了深度神经网络来近似动作价值函数Q(s,a),从而能够处理高维的状态空间。
1 深度网络
1.1 定义:
深度神经网络就是一个函数,将输入向量x映射到输出向量y,并且拥有可以学习的参数,这些参数使用梯度下降的方法来优化。Q表可以近似动作价值函数Q(s,a),将状态向量s作为输入,并输出所有动作a=(a1,a2,...,an)对应的价值。
Q表是一个二维表格,只能处理离散的状态和动作空间,神经网络则可以处理连续的输入,并且可以处理高维的状态空间。Q表中我们描述状态空间一般用的是状态个数,神经网络则是状态维度。无论是Q表还是神经网络,它们输出的都是预测,而不是直接输出动作。
1.2 更新:
Q-learning算法的更新公式:
α代表学习率,没有额外参数,只需要一步步迭代更新Q值即可。
DQN算法的更新公式:引入了额外的网络参数θ。
yt与Q值的绝对差值记为TD误差,可以写成损失函数的形式使用梯度下降的方式来求解参数θ:
1.3 区别与联系
由此可以看出,强度学习与深度学习训练方式一样,都是将样本输入网络中,然后通过梯度下降来更新网络参数,使得损失函数最小,逼近最真实的Q值。不同的地方在于,强化学习训练的样本是通过与环境实时交互得到的,而深度学习的样本是事先准备好的。强化学习用于解决序列决策问题,深度学习用于解决静态问题如回归、分类、识别等。
2 经验回放
神经网络的训练集是事先准备好的,每次迭代时的样本都是从训练集中随机抽取的,其样本都是独立同分布的。而强化学习的样本都是从环境中实时交互得到的,这样的样本都是有关联的。但是由于使用梯度下降的一个假设前提就是样本是独立同分布的,以及使用的都是小批量梯度下降,不能每次都使用单个样本直接去迭代网络。
所以DQN模型要使用梯度下降需要做一些处理,这就是经验回放的实现初衷。在DQN中,我们每次都将与环境交互得到的样本存储在一个经验回放中,然后从经验池中随机抽取一批样本来训练网络,以此来实现迭代样本独立同分布,满足梯度下降法的前提假设,避免训练的不稳定性。
经验回放的容量需要有一定的容量限制,这是因为在深度学习中得到的样本都是事先准备好的很好的样本,但是在强化学习中得到的样本都是智能体生成的实时样本,在训练初期可能可以得到很好的效果去朝着很好的方向去收敛,但是到后期这些产生的样本就相对来说质量不是很好,投入深度学习网络就会影响其稳定。所以其容量太小会导致收集的样本具有一定的局限性,太大则会失去经验本身的意义。
3 目标网络
DQN算法中还有一个重要的技巧,即使用了一个每隔若干步才更新的目标网络。目标网络和当前网络结构相同,都用于近似Q值,在实践中每隔若干步才将每步更新的当前网络复制给目标网络,这样可以保证训练的稳定,避免Q值的估计发散。
4 实战:DQN算法(以下均来自学习资料)
4.1 伪代码
DQN的训练过程包括交互采样以及模型更新两个步骤,其中交互采样的目的是与环境交互产生样本,模型更新则是利用得到的样本来更新相关的网络参数。
4.2 定义模型
首先是定义两个神经网络,即当前网络以及目标网络,两个网络的结构相同,使用一个Python类来定义。
class MLP(nn.Module): # 所有网络必须继承 nn.Module 类,这是 PyTorch 的特性
def __init__(self, input_dim,output_dim,hidden_dim=128):
super(MLP, self).__init__()
# 定义网络的层,这里都是线性层
self.fc1 = nn.Linear(input_dim, hidden_dim) # 输入层
self.fc2 = nn.Linear(hidden_dim,hidden_dim) # 隐藏层
self.fc3 = nn.Linear(hidden_dim, output_dim) # 输出层
def forward(self, x):
# 各层对应的激活函数
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return self.fc3(x) # 输出层不需要激活函数
这里定义了一个三层的全连接网络,输入维度就是状态数,输出维度就是动作数,中间的隐藏层使用ReLU激活函数。使用pytorch的module类来定义网络,所以网络都必须继承这个类。在pytorch中只需要定义网络的前向传播,即forward函数,反向传播过程pytorch会自动完成,这也是pytorch的特性。
4.3 经验回放
经验回放主要实现缓存样本以及取出样本两个功能。
class ReplayBuffer:
def __init__(self, capacity):
self.capacity = capacity # 经验回放的容量
self.buffer = [] # 用列表存放样本
self.position = 0 # 样本下标,便于覆盖旧样本
def push(self, state, action, reward, next_state, done):
''' 缓存样本
'''
if len(self.buffer) < self.capacity: # 如果样本数小于容量
self.buffer.append(None)
self.buffer[self.position] = (state, action, reward, next_state, done)
self.position = (self.position + 1) % self.capacity
def sample(self, batch_size):
''' 取出样本,即采样
'''
batch = random.sample(self.buffer, batch_size) # 随机采出小批量转移
state, action, reward, next_state, done = zip(*batch) # 解压成状态,动作等
return state, action, reward, next_state, done
def __len__(self):
''' 返回当前样本数
'''
return len(self.buffer)
4.4 定义智能体
智能体即策略的载体,主要功能是根据当前的状态输出动作和更新策略,分别于伪代码中的交互采样以及模型更新过程相对应。
class Agent:
def __init__(self):
# 定义当前网络
self.policy_net = MLP(state_dim,action_dim).to(device)
# 定义目标网络
self.target_net = MLP(state_dim,action_dim).to(device)
# 将当前网络参数复制到目标网络中
self.target_net.load_state_dict(self.policy_net.state_dict())
# 定义优化器
self.optimizer = optim.Adam(self.policy_net.parameters(), lr=learning_rate)
# 经验回放
self.memory = ReplayBuffer(buffer_size)
self.sample_count = 0 # 记录采样步数
def sample_action(self,state):
''' 采样动作,主要用于训练
'''
self.sample_count += 1
# epsilon 随着采样步数衰减
self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * math.exp(-1. * self.sample_count / self.epsilon_decay)
if random.random() > self.epsilon:
with torch.no_grad(): # 不使用梯度
state = torch.tensor(np.array(state), device=self.device, dtype=torch.float32).unsqueeze(dim=0)
q_values = self.policy_net(state)
action = q_values.max(1)[1].item() # choose action corresponding to the maximum q value
else:
action = random.randrange(self.action_dim)
def predict_action(self,state):
''' 预测动作,主要用于测试
'''
with torch.no_grad():
state = torch.tensor(np.array(state), device=self.device, dtype=torch.float32).unsqueeze(dim=0)
q_values = self.policy_net(state)
action = q_values.max(1)[1].item() # choose action corresponding to the maximum q value
return action
def update(self):
pass
DQN算法的更新方式:
def update(self, share_agent=None):
# 当经验回放中样本数小于更新的批大小时,不更新算法
if len(self.memory) < self.batch_size: # when transitions in memory donot meet a batch, not update
return
# 从经验回放中采样
state_batch, action_batch, reward_batch, next_state_batch, done_batch = self.memory.sample(
self.batch_size)
# 转换成张量(便于GPU计算)
state_batch = torch.tensor(np.array(state_batch), device=self.device, dtype=torch.float)
action_batch = torch.tensor(action_batch, device=self.device).unsqueeze(1)
reward_batch = torch.tensor(reward_batch, device=self.device, dtype=torch.float).unsqueeze(1)
next_state_batch = torch.tensor(np.array(next_state_batch), device=self.device, dtype=torch.float)
done_batch = torch.tensor(np.float32(done_batch), device=self.device).unsqueeze(1)
# 计算 Q 的实际值
q_value_batch = self.policy_net(state_batch).gather(dim=1, index=action_batch) # shape(batchsize,1),requires_grad=True
# 计算 Q 的估计值,即 r+\gamma Q_max
next_max_q_value_batch = self.target_net(next_state_batch).max(1)[0].detach().unsqueeze(1)
expected_q_value_batch = reward_batch + self.gamma * next_max_q_value_batch* (1-done_batch)
# 计算损失
loss = nn.MSELoss()(q_value_batch, expected_q_value_batch)
# 梯度清零,避免在下一次反向传播时重复累加梯度而出现错误。
self.optimizer.zero_grad()
# 反向传播
loss.backward()
# clip避免梯度爆炸
for param in self.policy_net.parameters():
param.grad.data.clamp_(-1, 1)
# 更新优化器
self.optimizer.step()
# 每C(target_update)步更新目标网络
if self.sample_count % self.target_update == 0:
self.target_net.load_state_dict(self.policy_net.state_dict())
4.5 定义环境
环境名称叫做Cart Pole,中文译作推车杆游戏,属于OpenAI Gym平台。
官网环境介绍:Cart Pole - Gymnasium Documentation
环境的奖励设置时每个时步下能维持杆不倒就给一个+1的奖励,因此没有终止状态。前面基于TD的算法都需要一个终止状态,所以我们可以设置一个环境的最大步数,比如我们可以设置如果在200个时步内坚持杆子不倒就近似说明学到了一个不错的策略。
4.6 设置参数
定义好智能体与环境之后就可以设置参数了:
self.epsilon_start = 0.95 # epsilon 起始值
self.epsilon_end = 0.01 # epsilon 终止值
self.epsilon_decay = 500 # epsilon 衰减率
self.gamma = 0.95 # 折扣因子
self.lr = 0.0001 # 学习率
self.buffer_size = 100000 # 经验回放容量
self.batch_size = 64 # 批大小
self.target_update = 4 # 目标网络更新频率
5 本章小结
本章讲了深度强化学习最为基础的 DQN 算法,相比于 Q-learning 算法,除了用神经网络来替代 Q 表这项改进之外,还提出了目标网络、经验回放等技巧,主要优化引入神经网络带来的局部最小值问题。最后,我们利用 PyTorch 框架实现了 DQN 算法并取得了不错的效果。
6 练习题
1.相比于Q-learning 算法,DQN 算法做了哪些改进?
改进主要包括:1、经验回放;2、目标网络;3、深度神经网络;4、TD误差。
2.为什么要在 DQN 算法中引入 greedy 策略?
引入该策略是为了在探索和利用中取得平衡,greedy策略意味着在选择动作时,算法会选择当前估计Q值最高的动作,这是一种利用已有知识来最大化累积奖励的方法。
3.DQN 算法为什么要多加一个目标网络?
为了解决Q-learning算法使用深度神经网络时的一些问题,可以提高训练的稳定性以及收敛性,可以防止过度估计以及减轻相关性。
4.经验回放的作用是什么?
经验回放通过存储和重新利用经验数据,使得深度强化学习算法更加稳定、高效地学习,并且能够更好地应对实际环境中的复杂性。可以解耦时间关系、数据重用、避免非静态分布以及提高样本效率。