在深度强化学习(Deep Reinforcement Learning,DRL)领域,Actor-Critic算法是一种常见且有效的策略梯度方法。它将策略(Actor)和值函数(Critic)组合在一起,以帮助智能体更好地学习和优化其行为策略。本文中,将介绍一个简单的Actor-Critic实现,用于连续动作空间的问题。这个实现基于PyTorch,一个流行的深度学习框架,以及Python中的unittest
库进行单元测试。
一、了解Actor-Critic算法
Actor-Critic(演员-评论家)是一种强化学习算法,通常用于解决连续动作空间中的问题。"Actor"是一个策略网络,负责生成智能体的行动策略,通常使用神经网络来表示。"Critic"是一个值函数网络,用于评估当前状态的价值,帮助Actor更好地指导行为策略。两者协同工作,以帮助智能体学习如何执行动作以最大化累积奖励。
-
演员(Actor)
- 演员负责学习并执行动作策略。它输出在给定状态下采取的动作,通常表示为策略函数。
- 演员的目标是最大化期望奖励。为了实现这一目标,演员会通过策略梯度方法或其他优化算法来调整动作策略。
- 通常,演员的策略是参数化的,可以是神经网络或其他函数逼近器。
-
评论家(Critic)
- 评论家负责估计每个状态的价值函数,即在给定状态下采取特定动作的长期奖励预期。
- 评论家的目标是为演员提供反馈,以指导演员学习更好的策略。通过比较实际奖励和评论家的估计奖励,可以计算出演员策略的优化方向。
- 评论家通常使用值函数来估计奖励,可以是基于动作值(动作-值函数,Action-Value Function)的Q-值或基于策略的状态值(状态-值函数,State-Value Function)。
-
工作流程
- 演员基于当前状态选择一个动作,通常使用策略函数。
- 执行动作后,观察新的状态和奖励。 评论家使用这些信息来更新价值函数的估计。
- 评论家的反馈传递给演员,用于改进动作策略。
- 这个循环不断迭代,直到演员的策略逐渐收敛到最优策略。
-
公式推导
-
策略函数:演员的策略通常由一个参数化函数表示,通常使用神经网络。策略函数的目标是最大化期望奖励。
π ( a ∣ s , θ π ) \pi(a|s, \theta_{\pi}) π(a∣s,θπ)
其中, a a a是动作, s s s是状态, θ π \theta_{\pi} θπ是策略函数的参数。 -
策略梯度:为了最大化期望奖励,我们可以采用策略梯度方法,其中梯度由下式给出:
∇ θ π J ( θ π ) = E [ ∇ θ π log ( π ( a ∣ s , θ π ) ) ⋅ Q ( s , a ) ] \nabla_{\theta_{\pi}} J(\theta_{\pi}) = \mathbb{E}[\nabla_{\theta_{\pi}} \log(\pi(a|s, \theta_{\pi})) \cdot Q(s, a)] ∇θπJ(θπ)=E[∇θπlog(π(a∣s,θπ))⋅Q(s,a)]
其中, J ( θ π ) J(\theta_{\pi}) J(θπ)是目标, Q ( s , a ) Q(s, a) Q(s,a)是状态-动作对的长期奖励(通常是基于评论家的估计)。
-
状态值函数(V-Value):
V ( s ) V(s) V(s)
它估计在状态 s s s下的长期奖励。 -
动作值函数(Q-Value): Q ( s , a ) Q(s, a) Q(s,a)
它估计在状态 s s s下采取动作 a a a后的长期奖励。 -
评论家的任务是估计状态值函数或动作值函数。评论家的目标通常是通过Bellman方程来更新值函数的估计:
Q ( s , a ) = E [ r + γ V ( s ′ ) ] Q(s, a) = \mathbb{E}[r + \gamma V(s')] Q(s,a)=E[r+γV(s′)]
其中, r r r是在状态 s s s采取动作 a a a后获得的即时奖励, s ′ s' s′是下一个状态, γ \gamma γ是折扣因子。
-
-
Actor-Critic算法的更新步骤:
-
演员(Actor)使用策略梯度法更新策略参数:
θ π ← θ π + α ∇ θ π log ( π ( a ∣ s , θ π ) ) ⋅ Q ( s , a ) \theta_{\pi} \leftarrow \theta_{\pi} + \alpha \nabla_{\theta_{\pi}} \log(\pi(a|s, \theta_{\pi})) \cdot Q(s, a) θπ←θπ+α∇θπlog(π(a∣s,θπ))⋅Q(s,a)
-
评论家(Critic)使用Bellman方程来更新值函数估计:
Q ( s , a ) ← Q ( s , a ) + β ( r + γ V ( s ′ ) − Q ( s , a ) ) Q(s, a) \leftarrow Q(s, a) + \beta \left(r + \gamma V(s') - Q(s, a)\right) Q(s,a)←Q(s,a)+β(r+γV(s′)−Q(s,a))
其中, α \alpha α和 β \beta β是学习速率。
-
Actor-Critic算法有多个变种,包括基于策略梯度方法的A2C(Advantage Actor-Critic)、TRPO(Trust Region Policy Optimization)、PPO(Proximal Policy Optimization)等。这些变种在如何执行策略梯度更新和价值函数估计方面有不同的方法,但都遵循演员-评论家架构的基本思想。Actor-Critic算法在各种强化学习任务中表现出色,特别适用于连续动作空间和高维状态空间的问题。它的优势在于能够结合策略学习和价值学习,从而更有效地训练智能体。
二、ActorCritic神经网络
我们首先定义了一个ActorCritic
类,它继承自PyTorch的nn.Module
。这个类包括两个子模块:actor
和critic
,分别用于生成策略和估算状态值。以下是代码的详细解释:
class ActorCritic(nn.Module):
def __init__(self, num_inputs, num_outputs, hidden_size, std):
super(ActorCritic, self).__init__()
self.actor = nn.Sequential(
nn.Linear(num_inputs, hidden_size),
nn.ReLU(),
nn.Linear(hidden_size, hidden_size),
nn.ReLU(),
nn.Linear(hidden_size, num_outputs),
nn.Tanh()
)
self.critic = nn.Sequential(
nn.Linear(num_inputs, hidden_size),
nn.ReLU(),
nn.Linear(hidden_size, hidden_size),
nn.ReLU(),
nn.Linear(hidden_size, 1)
)
self.std = std
num_inputs
是输入特征的数量,通常对应于环境状态的维度。num_outputs
是输出的数量,代表可选的动作的维度。hidden_size
是隐藏层的神经元数量。std
表示策略中的标准差。
Actor
和Critic
都是神经网络,通过多个线性层和ReLU激活函数堆叠在一起,以建模智能体的策略和值函数。Actor
网络的输出使用tanh函数,以确保输出在-1到1之间。这是因为Actor通常输出的是连续动作的均值,而tanh函数可以帮助控制动作幅度。
接下来,我们定义了forward
方法,用于执行前向传播:
def forward(self, x):
value = self.critic(x)
mu = self.actor(x)
std = torch.ones_like(mu) * self.std
return value, mu, std
forward
方法接受状态x
作为输入,然后分别计算值函数(value
)、策略(mu
),以及策略中的标准差(std
)。值函数直接由Critic
网络计算,而策略则由Actor
网络计算,并且标准差被设定为一个常数值self.std
。
三、单元测试ActorCritic类
在代码中,我们还包括了一个用于测试ActorCritic
类的单元测试类TestActorCritic
。单元测试是一种验证代码是否按照预期工作的方法,它可以确保类的各个部分都表现正常。以下是测试的一部分:
class TestActorCritic(unittest.TestCase):
def setUp(self):
self.num_inputs = 10
self.num_outputs = 3
self.hidden_size = 20
self.std = 0.1
self.model = ActorCritic(self.num_inputs, self.num_outputs, self.hidden_size, self.std)
def test_forward(self):
x = Variable(torch.randn(1, self.num_inputs))
value, mu, std = self.model.forward(x)
self.assertEqual(value.shape, torch.Size([1, 1]))
self.assertEqual(mu.shape, torch.Size([1, self.num_outputs]))
self.assertEqual(std.shape, torch.Size([1, self.num_outputs]))
self.assertTrue(torch.all(mu >= -1) and torch.all(mu <= 1))
self.assertTrue(torch.all(std >= 0))
在setUp
方法中,我们初始化了一个ActorCritic
实例,然后在test_forward
方法中,我们传入一个随机生成的状态x
,并验证值函数、策略和标准差的形状是否符合预期,以及策略的取值范围是否正确。
四、强化学习环境的引入
要将强化学习模型投入实际应用,我们需要引入一个强化学习环境。这个环境模拟了智能体在不同状态下采取行动并获得奖励的情景。以下是如何引入一个环境的代码示例:
import gym
# 创建强化学习环境
env = gym.make('CartPole-v1') # 以CartPole环境为例
在这个示例中,我们使用了CartPole环境,它是一个简单的强化学习任务,智能体需要平衡一个杆子在移动的小车上。您可以根据您的需求选择不同的环境。
五、环境学习
与环境互动通常包括以下步骤:
- 重置环境:在每个episode(一个完整的任务执行)开始时,需要重置环境并获得
初始状态。
state = env.reset()
- 执行动作:根据策略网络生成的动作,与环境互动并观察下一个状态和奖励。
action = model.select_action(state) # 从策略中选择动作
next_state, reward, done, _ = env.step(action)
在上述代码中,select_action
方法是一个用于选择动作的函数,需要根据当前状态和策略网络生成一个动作。next_state
是下一个状态,reward
是智能体在执行动作后获得的奖励,done
表示是否达到了任务结束的条件。
- 学习与训练:根据观察到的状态、奖励等信息,智能体需要进行学习和训练。
model.update(state, action, reward, next_state, done)
这里,update
方法是用于更新Actor和Critic网络的函数,以使智能体逐渐改进其策略和值函数。
- 循环迭代:重复上述步骤,直到episode结束或达到训练的停止条件。
六、实例
- 构建环境
选择OpenAI Gym库中的CartPole-v1环境作为示例。CartPole任务旨在平衡一个杆子在移动的小车上。这是一个简单的任务,适用于初学者了解强化学习的基本概念。
import gym
# 创建强化学习环境
env = gym.make('CartPole-v1')
- 与环境交互
与环境交互,包括重置环境、执行动作和观察环境状态、奖励等。
在与环境互动时,我们按照以下步骤进行:
- 重置环境:在每个episode开始时,我们需要重置环境以获得初始状态。
state = env.reset()
- 执行动作:根据策略网络生成的动作,与环境互动并观察下一个状态和奖励。
action = model.select_action(state) # 从策略中选择动作
next_state, reward, done, _ = env.step(action) # 执行动作并观察环境
- 学习与训练:根据观察到的状态、奖励等信息,智能体需要进行学习和训练。
model.update(state, action, reward, next_state, done)
- 循环迭代:重复上述步骤,直到episode结束或达到训练的停止条件。
这个循环迭代是强化学习中的关键部分,它允许智能体与环境互动并逐渐改进其策略和值函数。
3.训练
# 定义训练参数
num_episodes = 1000 # 训练的总episode数量
for episode in range(num_episodes):
state = env.reset() # 重置环境,获得初始状态
episode_reward = 0 # 用于跟踪每个episode的累积奖励
while True:
action = model.select_action(state) # 从策略中选择动作
next_state, reward, done, _ = env.step(action) # 执行动作并观察环境
# 更新智能体的策略和值函数
model.update(state, action, reward, next_state, done)
state = next_state
episode_reward += reward
if done:
break
# 打印本episode的奖励
print(f"Episode {episode + 1}, Reward: {episode_reward}")
在这个训练中,我们重复执行多个episode,每个episode都包括重置环境、执行动作、观察环境并更新智能体策略和值函数的步骤。最终,我们会打印每个episode的累积奖励,以跟踪模型的性能。