10.3 Soft Actor-Critic (SAC)
Soft Actor-Critic(SAC)是一种深度强化学习算法,用于解决连续动作空间和高维状态空间下的强化学习问题。SAC是Actor-Critic(演员-评论家)算法的一种变体,它引入了一些改进和创新,使其在多方面具有出色的性能。
10.3.1 Soft Actor-Critic算法的核心思想
Soft Actor-Critic(SAC)的核心思想是通过最大熵强化学习来实现策略优化,以平衡探索和利用。它通过引入双值函数、目标熵的自动调整以及经验回放等技术来处理连续动作空间的问题,并通过深度神经网络来学习复杂的策略。SAC已经在许多强化学习任务中表现出色,特别适用于需要处理高维状态和连续动作的问题。
- 最大熵强化学习:SAC借鉴了最大熵强化学习的思想。最大熵强化学习不仅关注最大化累积奖励,还最大化策略的熵(或不确定性)。这意味着SAC的策略不仅会试图获得高回报,还会试图保持多样性和探索性,从而更全面地探索状态空间。最大熵正则化的引入有助于在策略优化中平衡探索和利用。
- 连续动作空间:SAC专门设计用于处理连续动作空间的问题。在连续动作空间中,动作可以是实数,因此通常有无限多个可能的动作。SAC使用一个高斯分布来参数化策略,允许演员网络输出连续动作的均值和标准差,从而灵活地适应连续动作空间。
- 双值函数(Q值和V值):SAC引入了两个值函数,即Q值函数和V值函数。Q值函数用于估计状态-动作对的价值,而V值函数用于估计状态的价值。这两个值函数有助于更准确地估计策略的性能。SAC使用Q值函数来计算优势函数,以指导策略学习。
- 目标熵的自动调整:SAC引入了一个目标熵的概念,它表示策略的期望熵。SAC会自动调整目标熵,以平衡探索和利用。当策略的熵低于目标熵时,SAC会鼓励更多的探索,从而保持策略的多样性。
- 经验回放:SAC通常使用经验回放来存储和重用以前的经验样本,以增加数据效率和稳定性。这有助于防止样本相关性,并允许SAC从历史经验中学习。
10.3.2 熵(Entropy)的作用及其在SAC中的应用
熵(Entropy)在信息理论和强化学习中都有重要的作用。在强化学习中,熵被引入到最大熵强化学习算法中,其中策略的熵用于探索和平衡探索与利用的关系。下面将详细介绍熵在强化学习中的作用以及在Soft Actor-Critic(SAC)算法中的应用:
1. 熵的作用
- 不确定性度量:熵是一种度量不确定性或随机性的指标。在强化学习中,策略的熵可以用来衡量策略的不确定性,即在给定状态下选择动作的随机性。高熵策略表示策略在相同状态下会选择多个不同的动作,而低熵策略则表示策略在相同状态下更倾向于选择相同的动作。
- 探索与利用的平衡:熵在强化学习中用于平衡探索和利用。高熵策略更加探索性,因为它使得策略在不同动作之间的选择更加均匀,从而有助于发现新的状态和动作组合。低熵策略更加利用,因为它使得策略更倾向于选择具有高估计价值的动作。
- 策略优化:最大化策略的熵是一种策略优化的正则化方法。在策略优化中,不仅要追求最大化期望回报,还要追求最大化策略的熵。这使得策略不仅会关注回报,还会注重保持多样性和探索。
2. 熵在Soft Actor-Critic中的应用
在Soft Actor-Critic(SAC)算法中,熵的应用是其核心思想之一。以下是熵在SAC中的具体应用:
- 最大化策略的熵:SAC的目标函数不仅包括最大化期望累积奖励,还包括最大化策略的熵。这可以表示为一个带有熵正则化项的优化问题,其中目标是最大化期望奖励和策略熵的加权和。这使得SAC策略更具探索性,因为它会鼓励策略选择多样性的动作。
- 自动调整目标熵:SAC通过自动调整目标熵的方式来平衡探索和利用。它使用一个目标熵的参数,然后通过学习过程中的自动调整来控制策略的探索性。当策略的熵低于目标熵时,SAC会鼓励更多的探索,以保持策略的多样性。
- 探索性能的提升:通过引入熵正则化项,SAC能够在探索性能和利用性能之间找到平衡。这有助于在强化学习任务中更好地处理探索问题,特别是在高维状态和连续动作空间中。
总之,熵在SAC中用于平衡探索和利用,通过最大化策略的熵来提高探索性能。这使得SAC成为一种有效处理连续动作空间问题的算法,并在实际应用中取得了出色的性能。
10.3.3 SAC算法的训练过程
Soft Actor-Critic(SAC)算法的训练过程通常包括以下步骤:
(1)初始化
初始化演员(Actor)、目标演员、评论家(Critic)、目标评论家、策略(Policy)和目标策略网络的神经网络参数,然后设置其他算法参数,如学习率、目标熵、折扣因子等。
(2)数据采集
与环境交互以收集样本数据。这通常涉及使用当前策略(演员网络)与环境互动,并记录状态、采取的动作、即时奖励和下一个状态。为了提高数据利用效率,通常使用经验回放缓冲区存储和重用以前的经验样本。
(3)计算目标策略熵
计算目标策略的熵,以用于自动调整目标熵。目标策略的熵通常是一个固定值,或者它可以通过训练过程自动学习。
(4)更新评论家网络
使用经验回放缓冲区中的样本数据,计算Q值(状态-动作对的价值估计)。使用均方误差或其他适当的损失函数来训练评论家网络,使其近似Q值函数。更新目标评论家网络,通常采用软更新策略(例如,使用滑动平均值来更新目标网络参数)。
(5)更新演员和策略网络
使用评论家网络的Q值估计来计算策略梯度。使用策略梯度算法来更新演员网络的参数,以最大化期望累积奖励和策略熵的加权和。更新目标演员网络和目标策略网络,通常采用软更新策略。
(6)自动调整目标熵
在训练过程中,自动调整目标熵以平衡探索和利用。如果策略的熵低于目标熵,增加目标熵的值,以鼓励更多的探索。
(7)重复:重复步骤2至步骤6,直到达到预定的训练轮数或其他停止条件。
(8)评估策略:在训练结束后,可以使用演员网络的最终参数来评估策略的性能。通常,这涉及在测试环境中运行策略,并计算平均奖励或其他性能指标。
(9)保存模型(可选):保存训练后的演员、评论家和策略网络模型,以备将来使用。
SAC的训练过程是一个迭代的过程,演员和评论家网络相互协作,通过反馈信号来改进策略。通过最大化期望累积奖励和策略熵的加权和,SAC能够有效地处理连续动作空间和高维状态空间的问题,同时保持多样性和探索性。
10.3.4 SAC算法实战
下面是一个使用SAC算法的简单例子,该例子创建了一个简单的虚拟环境,其中一个虚拟机器人尝试在不碰到障碍物的情况下远离原点。 SAC代理通过训练来学习如何选择动作以最大化总奖励。
实例10-3:使用SAC算法训练一个强化学习代理(源码路径:daima\10\sac.py)
实例文件sac.py的主要实现代码如下所示:
# 定义虚拟环境
class Environment:
def __init__(self):
self.state_dim = 2 # 状态维度为2(x坐标和y坐标)
self.action_dim = 1 # 动作维度为1(推进或后退)
self.state = np.array([0.0, 0.0]) # 初始状态为原点坐标
def step(self, action):
# 模拟虚拟机器人的动作
velocity = action[0] # 动作表示速度
self.state[0] += velocity # 更新x坐标
# 计算奖励,目标是使机器人尽量远离原点
reward = -np.abs(self.state[0])
# 检查是否达到终止条件(机器人离原点太远)
done = np.abs(self.state[0]) > 10.0
return self.state, reward, done
# 创建连续动作空间的SAC代理
class SACAgent:
def __init__(self, state_dim, action_dim):
self.state_dim = state_dim
self.action_dim = action_dim
# 创建策略网络和Q网络
self.actor = self.build_actor()
self.q1 = self.build_critic()
self.q2 = self.build_critic()
# 定义优化器
self.actor_optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
self.critic_optimizer = tf.keras.optimizers.Adam(learning_rate=0.002)
def build_actor(self):
input_layer = Input(shape=(self.state_dim,))
x = Dense(64, activation='relu')(input_layer)
x = Dense(64, activation='relu')(x)
mean = Dense(self.action_dim, activation='tanh')(x)
log_stddev = Dense(self.action_dim)(x)
actor = Model(inputs=input_layer, outputs=[mean, log_stddev])
return actor
def build_critic(self):
input_layer = Input(shape=(self.state_dim + self.action_dim,))
x = Dense(64, activation='relu')(input_layer)
x = Dense(64, activation='relu')(x)
q_value = Dense(1)(x)
critic = Model(inputs=input_layer, outputs=q_value)
return critic
def select_action(self, state):
mean, log_stddev = self.actor.predict(np.array([state]))
stddev = tf.math.exp(log_stddev)
action_distribution = tfp.distributions.Normal(loc=mean, scale=stddev)
action = action_distribution.sample()
return action.numpy()[0]
def update(self, state, action, reward, next_state, done):
with tf.GradientTape(persistent=True) as tape:
mean, log_stddev = self.actor(np.array([state]))
stddev = tf.math.exp(log_stddev)
action_distribution = tfp.distributions.Normal(loc=mean, scale=stddev)
log_prob = action_distribution.log_prob(action)
# 将action从一维数组转换为二维数组
action = np.array([action])
# 连接state和action
q1_value = self.q1(np.concatenate([np.array([state]), action], axis=-1))
q2_value = self.q2(np.concatenate([np.array([state]), action], axis=-1))
next_mean, next_log_stddev = self.actor(np.array([next_state]))
next_stddev = tf.math.exp(next_log_stddev)
next_action_distribution = tfp.distributions.Normal(loc=next_mean, scale=next_stddev)
next_action = next_action_distribution.sample()
next_log_prob = next_action_distribution.log_prob(next_action)
target_q_value = tf.minimum(q1_value, q2_value) - log_prob + next_log_prob
target_q_value = tf.stop_gradient(target_q_value)
critic_loss1 = tf.reduce_mean(tf.square(target_q_value - q1_value))
critic_loss2 = tf.reduce_mean(tf.square(target_q_value - q2_value))
actor_loss = tf.reduce_mean(log_prob - tf.minimum(q1_value, q2_value))
actor_gradients = tape.gradient(actor_loss, self.actor.trainable_variables)
critic_gradients1 = tape.gradient(critic_loss1, self.q1.trainable_variables)
critic_gradients2 = tape.gradient(critic_loss2, self.q2.trainable_variables)
self.actor_optimizer.apply_gradients(zip(actor_gradients, self.actor.trainable_variables))
self.critic_optimizer.apply_gradients(zip(critic_gradients1, self.q1.trainable_variables))
self.critic_optimizer.apply_gradients(zip(critic_gradients2, self.q2.trainable_variables))
del tape
# 训练SAC代理
def train_sac_agent():
env = Environment()
agent = SACAgent(env.state_dim, env.action_dim)
max_episodes = 1000
for episode in range(max_episodes):
state = env.state
total_reward = 0
while True:
action = agent.select_action(state)
next_state, reward, done = env.step(action)
agent.update(state, action, reward, next_state, done)
total_reward += reward
state = next_state
if done:
break
print(f"Episode: {episode}, Total Reward: {total_reward}")
if __name__ == "__main__":
train_sac_agent()
上述代码的实现流程如下所示:
- 首先定义了一个虚拟环境Environment,该环境具有状态维度为2(x坐标和y坐标)和动作维度为1(推进或后退)。虚拟机器人的目标是尽量远离原点。
- 创建一个SAC代理SACAgent,该代理包括策略网络(actor)和两个Q网络(critics)。策略网络用于生成动作,Q网络用于估计动作的价值。代理使用Adam优化器进行参数更新。
- 策略网络和Q网络的结构在build_actor和build_critic方法中定义。策略网络生成均值和对数标准差,用于构建动作的概率分布。Q网络用于估计状态-动作对的Q值。
- select_action方法用于根据策略网络选择动作,并返回动作的样本。
- update方法实现了SAC算法的更新步骤,包括计算动作的log概率、Q值的估计、目标Q值等,并使用梯度下降更新策略网络和Q网络的参数。
- 在train_sac_agent函数中,代理与环境互动,执行多个训练周期。在每个周期内,代理根据策略选择动作,与环境互动,然后更新策略和Q网络的参数。最后,打印每个周期的总奖励。
- 主程序检查是否是直接运行脚本,并调用train_sac_agent函数来训练SAC代理。