Reinforcement Learning with Deep EnergyBased Policies:
作者:禅与计算机程序设计艺术
1.简介
近年来深度学习技术逐渐火热,其在图像识别、自然语言处理等领域取得了显著成果。在强化学习中,基于深度神经网络(DNN)的方法由于其优秀的泛化性能、快速收敛速度、容错性以及模型参数数量低等优点而得到广泛应用。因此,本文将会介绍一种新的基于深度能量逆策略(Deep Energy Based Policy, DEBP)方法,它可以有效克服传统基于值函数的方法存在的一些不足。DEBP方法根据智能体的状态、动作及其执行结果计算出相对应的能量值,并对其进行优化,使得智能体能够根据奖励和惩罚两种信号选择最优动作。通过这种方式,DEBP能够获得比值函数更高级的学习能力,并能够对复杂任务进行较好的探索。
2.相关概念术语
首先,让我们回顾一下一般的基于值函数的强化学习方法。在这一类方法中,智能体通过学习环境和奖励系统提供的反馈信息,学习到一个评估函数(即价值函数),该函数根据状态和动作给出一个实数评估值。然后,智能体根据这个评估函数来决定下一步要采取什么样的动作。值函数通常是一个基于马尔科夫决策过程(MDP)框架下的预测模型,它的作用是在未来的某一时间步内,当前状态的情况下,由各个可能的动作产生的回报期望值。值函数的训练目标就是最大化每个状态动作对的回报期望值,从而使智能体在这个状态下选择具有最大期望值的动作。值函数的方法比较简单直观,但往往不能解决实际的复杂问题。 另一方面,还有一种基于策略的强化学习方法,它学习到的是一个执行策略(Policy)。策略是一个确定如何行动的行为,而不是像值函数那样直接给出一个评估值。策略可以看做是值函数的另一种形式,但也不是完全独立的。策略接受输入状态,输出预测的下一步执行的动作。与值函数一样,策略也是依据历史数据来学习的。 然而,对于大多数问题来说,直接学习策略可能会遇到很多困难。比如,为了能够快速收敛,需要找到一个合适的搜索策略;当环境变换时,需要动态地更新策略;当智能体面临新任务时,需要重新学习策略等等。于是,出现了基于深度神经网络的新型强化学习方法——基于深度能量逆策略(DEBP)。 基于深度能量逆策略的关键是计算出每一个状态动作对的能量值,并优化这些能量值,使得智能体能够根据奖励和惩罚两种信号选择最优动作。所谓能量值,就是指对动作执行之后的状态受到的影响程度。例如,如果某个动作能使得智能体从状态s转移至状态s’,并且效果显著,则这个动作的能量值就应该很高。同理,如果某个动作导致智能体进入了一个比初始状态更糟糕的状态,或者导致损失很大的后果,那么它的能量值应该很低。基于能量值的动作选择可以缓解较差的动作导致长期效益降低的问题。
综上,以下将对基于深度能量逆策略的相关概念及术语做进一步阐述: · 能量函数:能量函数用来描述一个动作的好坏。它依赖于当前状态、动作及其执行后的状态,返回一个实数能量值。能量函数的输出越大,说明动作的好坏程度越高。通常,能量函数的设计要结合领域知识,同时考虑不同动作之间的关系。例如,一个机器人的走路动作可能带来较大的能量,但是爬行动作的能量可能小于走路动作。 · 能量逆策略:能量逆策略是一种能量函数逆映射的方法。它接收状态作为输入,输出动作及其对应的能量值。能量值越大,说明该动作越可能被选择。因此,在搜索空间较大时,能量逆策略能够加速搜索进程。 · 折扣因子:折扣因子用来表示智能体对某种动作的偏好程度。它是一个实数值,通常在[0,1]范围内。当折扣因子=1时,表示只倾向于选择具有最大能量值的动作;当折扣因子=0时,表示完全随机选择动作。折扣因子可以帮助智能体更平滑地响应环境变化,避免陷入局部最优或过拟合。 · 装饰器:装饰器是指能够修改智能体行为的组件。它们能够改变状态、动作或能量值的输出,从而影响智能体的行为。例如,有些装饰器能够改变动作的执行顺序、增加随机性、限制动作空间等。 · 数据集:数据集是由一系列的状态、动作及其能量值组成的数据集合。用于训练和测试模型。
3.核心算法原理和具体操作步骤
3.1 DQN算法
DQN算法是深度Q-网络(Deep Q Network)的缩写,是一种基于经验重放(Replay Buffer)的方法,该方法可以用较少的时间间隔收集足够数量的经验数据,然后利用这些经验数据训练模型。DQN的特点是能够在各种各样的游戏环境中进行高效的学习,且不需要大量的人工标记样本。相对于其他强化学习算法,DQN在实现上简单直接,并取得了不错的效果。
(1)能量函数的设计
对于每个动作a,DQN算法都对应有一个能量函数h(s)。能量函数h(s)定义如下:
其中ϵ是超参数,用于控制能量函数的衰减程度,以免它将所有能量都集中在较短的一段时间内。γ是折扣因子,它控制智能体对于某一特定动作的偏好程度。如果γ=1,说明不会偏向任何动作,而如果γ=0,说明完全随机选择动作。t是当前时间步,N是滑动窗口大小。
(2)DQN网络结构
DQN网络结构包括两个部分:图像特征提取网络FE和动作值函数网络VF。FE的输入是图像,输出是固定维度的向量x。VF的输入是x,输出是每个动作的动作值函数q。
(3)DQN训练过程
DQN算法的训练过程分为四个阶段:(1)采样经验池;(2)抽取批量样本;(3)训练网络;(4)更新目标网络。下面分别讨论。
(3.1)采样经验池
DQN算法的主要特点之一是利用经验数据来训练模型,所以第一步是收集经验池。每一次迭代前,智能体都会与环境交互,生成一系列的经验数据。经验数据的形式包括:状态、动作、奖励、下一个状态、终止标识符等。
(3.2)抽取批量样本
DQN算法采用一种称为Experience Replay的技术,通过随机抽取之前收集到的经验数据,构建批量样本。每一条经验数据包括一个状态、动作、奖励、下一个状态、终止标识符等,每一批次抽取的样本数量可以由参数设置。
(3.3)训练网络
训练网络的目标是最大化DQN网络输出的动作值函数q。具体地说,输入状态x和动作a,通过FE网络获取特征向量x',并通过VF网络计算动作值函数q(x', a),其中q(x', a)表示在状态x下执行动作a的Q值。在训练过程中,使用梯度下降法更新VF网络的参数,使得动作值函数 q'(x') 的输出接近真实的Q值r+γmax q'(x'),其中γmax是下一步能量值最大的动作。
(3.4)更新目标网络
DQN算法使用目标网络来提升DQN网络的稳定性。目标网络用来估计在下一时刻到达终止状态时的Q值,并与DQN网络参数的更新频率保持一致。每隔一定的步数更新一次目标网络的参数。目标网络的更新过程采用参数延迟的方法,在DQN网络参数更新时同步更新目标网络的参数。
4.代码示例及模型训练
4.1 模型训练代码
这里给出一个Pytorch版本的DQN算法的训练代码:
import torch
import torch.nn as nn
from collections import deque
import numpy as np
class Net(nn.Module):
def __init__(self, input_shape, outputs_count):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(*input_shape),
nn.ReLU(),
nn.Linear(64, outputs_count))
def forward(self, x):
return self.layers(x)
class Agent():
def __init__(self, env, net, optimizer, batch_size, gamma, eps, replay_buffer_size, tau):
self.env = env
self.net = net
self.target_net = Net(env.observation_space.shape, env.action_space.n).to(device)
self.optimizer = optimizer
self.batch_size = batch_size
self.gamma = gamma
self.eps = eps
self.replay_buffer_size = replay_buffer_size
self.tau = tau
# Initialize target network parameters to match main networks'
for target_param, param in zip(self.target_net.parameters(), self.net.parameters()):
target_param.data.copy_(param.data)
# Experience buffer to store experience tuples (state, action, reward, next state, done flag)
self.replay_buffer = deque(maxlen=replay_buffer_size)
def act(self, state, epsilon):
if np.random.rand() <= epsilon:
return self.env.action_space.sample() # explore actions randomly with probability epsilon
state = torch.FloatTensor([state]).to(device)
with torch.no_grad():
Q_values = self.net(state)
_, action = torch.max(Q_values, dim=1)
return int(action.item())
def compute_loss(self, batch_states, batch_actions, batch_rewards, batch_next_states, batch_dones):
batch_size = len(batch_states)
Q_vals = self.net(batch_states)[np.arange(batch_size), batch_actions].squeeze()
next_Q_vals = self.target_net(batch_next_states).detach().max(dim=1)[0]
expected_Q_vals = batch_rewards + self.gamma * next_Q_vals * (1 - batch_dones)
loss = ((expected_Q_vals - Q_vals)**2).mean()
return loss
def update(self):
# Sample experiences from the buffer
states, actions, rewards, next_states, dones = [], [], [], [], []
for exp in self.replay_buffer:
states.append(exp[0])
actions.append(exp[1])
rewards.append(exp[2])
next_states.append(exp[3])
dones.append(exp[4])
states = torch.tensor(states).float().to(device)
actions = torch.tensor(actions).long().to(device)
rewards = torch.tensor(rewards).float().to(device)
next_states = torch.tensor(next_states).float().to(device)
dones = torch.tensor(dones).float().to(device)
# Compute and apply gradient updates to both networks using mini-batches of size self.batch_size
indices = np.random.choice(len(self.replay_buffer), self.batch_size, replace=False)
batch_states = states[indices]
batch_actions = actions[indices]
batch_rewards = rewards[indices]
batch_next_states = next_states[indices]
batch_dones = dones[indices]
self.optimizer.zero_grad()
loss = self.compute_loss(batch_states, batch_actions, batch_rewards, batch_next_states, batch_dones)
loss.backward()
self.optimizer.step()
# Update target network's parameters using soft updates at rate self.tau
with torch.no_grad():
for target_param, param in zip(self.target_net.parameters(), self.net.parameters()):
target_param.data.copy_(self.tau*param.data + (1.-self.tau)*target_param.data)
def train(self, episodes):
scores_history = []
best_score = float('-inf')
for e in range(episodes):
score = 0
state = self.env.reset()
done = False
while not done:
# Select an action using ε-greedy policy
action = self.act(state, self.eps)
# Take step in environment and get next observation and reward
next_state, reward, done, _ = self.env.step(action)
# Add transition to replay buffer
self.replay_buffer.append((state, action, reward, next_state, done))
# Sample random minibatch from replay buffer and perform SGD on VF and FE networks
self.update()
# Update agent's current state and score
state = next_state
score += reward
print(f"Episode {e}: Score={score:.2f}")
scores_history.append(score)
avg_score = sum(scores_history[-10:]) / len(scores_history[-10:])
if avg_score > best_score:
print("Best average score reached")
best_score = avg_score
torch.save({'model': self.net}, 'best_model.pth')
# Decay exploration rate over time
self.eps *= 0.999
if __name__ == '__main__':
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Environment configuration
env_id = "CartPole-v1"
env = gym.make(env_id)
# Training hyperparameters
lr = 1e-3
episodes = 1000
batch_size = 64
gamma = 0.99
eps = 1.0
replay_buffer_size = 10000
tau = 1e-3
seed = 42
# Set seeds for reproducibility
torch.manual_seed(seed)
np.random.seed(seed)
env.seed(seed)
# Define model architecture and optimization algorithm
net = Net(env.observation_space.shape, env.action_space.n).to(device)
optimizer = optim.Adam(net.parameters(), lr=lr)
# Train agent
agent = Agent(env, net, optimizer, batch_size, gamma, eps, replay_buffer_size, tau)
agent.train(episodes)
代码解读
这个代码首先定义了一个网络结构Net,它由两个全连接层组成,前者由输入大小决定,后者由输出个数决定,输出层使用线性激活函数。
然后定义了一个Agent类,它包含环境、神经网络、优化器、经验回放池、折扣因子、ε-贪婪策略等重要参数。Agent类的构造函数初始化DQN算法的关键变量和属性。在构造函数末尾,它初始化了目标网络,并将主网络的参数复制到目标网络中。
Agent类中的act函数定义了ε-贪婪策略。它根据当前状态和ε值,决定是否随机选择动作。如果ε值大于随机概率,则会随机选择动作。否则,会将状态传入神经网络,获取动作值函数Q的值,然后返回其对应的动作值最大的动作。
Agent类中的compute_loss函数计算目标网络的Q值,并计算DQN网络与目标网络之间存在的损失函数,将损失值反向传播到DQN网络的权重上。
Agent类中的update函数通过随机抽取批量样本,并调用compute_loss函数计算损失值,最后调用优化器进行梯度下降,更新网络参数。在梯度下降之后,还会将网络参数更新到目标网络中,目标网络的参数采用软更新方法进行更新。
Agent类中的train函数循环执行episode次数,在每个episode中,首先重置环境,获取当前状态,执行ε-贪婪策略,获得当前动作,观察环境的反馈,并存储当前状态动作奖励下一个状态的转换信息到经验回放池中。在经验回放池中的经验信息会随着时间积累,越来越多,随着时间推移,智能体会逐渐学习到更多有用的信息,最后智能体可以在给定的环境中胜利。
4.2 执行训练脚本
将上面的代码保存为dqn.py文件,执行命令:
$ python dqn.py
代码解读
模型训练完成后,会打印出训练日志,其中包含每一轮训练得到的得分,这时模型已经有了很好的表现,可以用它来解决强化学习问题。
