【强化学习】Soft Actor-Critic (SAC) 算法

【强化学习】Soft Actor-Critic (SAC) 算法
        📢本篇文章是博主强化学习(RL)领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅解。文章分类在👉强化学习专栏:

       【强化学习】- 【单智能体强化学习】(13)---《Soft Actor-Critic (SAC) 算法》

Soft Actor-Critic (SAC) 算法

目录​​​​​​​

一、Soft Actor-Critic (SAC) 算法详解

二、SAC 背景与核心思想

1. 强化学习的挑战

2. 最大熵强化学习的目标

三、SAC 算法流程

初始化:

每一回合循环:

四、公式推导

1. Q 值更新

2. 值函数更新

3. 策略网络更新

4. 目标值函数更新

[Python]  Soft Actor-Critic算法实现

[Notice] 代码核心

五、 SAC 优势


一、Soft Actor-Critic (SAC) 算法详解

        Soft Actor-Critic(SAC) 是一种最先进的强化学习算法,属于 Actor-Critic 方法的变体。它特别适合处理 连续动作空间,并通过引入最大熵(Maximum Entropy)强化学习的思想,解决了许多传统算法中的稳定性和探索问题。


二、SAC 背景与核心思想

1. 强化学习的挑战

  • 探索与利用的平衡:传统算法在初期探索新策略与后期利用已有最优策略之间难以达到平衡。
  • 不稳定性:在连续动作空间中,训练通常会出现发散或收敛缓慢的问题。
  • 样本效率:强化学习中,数据采集成本高,如何有效利用经验池中的数据是关键。

SAC 引入了以下核心思想来应对这些问题:

  1. 最大熵强化学习:在最大化累计奖励的同时,最大化策略的随机性(熵),以鼓励探索。
  2. 双 Q 网络:缓解 Q 值过估计的问题。
  3. 目标网络:使用目标网络稳定 Q 值计算。

2. 最大熵强化学习的目标

传统强化学习的目标是最大化期望累计奖励:

J(\pi) = \mathbb{E}{\pi} \left[ \sum{t=0}^T \gamma^t r(s_t, a_t) \right]

而 SAC 则通过添加一个 熵项,在奖励中加入策略随机性的权重,目标变为:

J(\pi) = \mathbb{E}{\pi} \left[ \sum{t=0}^T \gamma^t \left( r(s_t, a_t) + \alpha \mathcal{H}(\pi(\cdot|s_t)) \right) \right]

其中:

\alpha

:熵系数,控制熵和奖励之间的平衡。

\mathcal{H}(\pi(\cdot|s_t)) = -\mathbb{E}_{a \sim \pi} [\log \pi(a|s_t)]

,表示策略的熵,鼓励策略更随机化;

效果

  • 更好的探索:熵的最大化使策略更加多样化。
  • 更稳定的学习:避免陷入次优策略。

三、SAC 算法流程

        SAC 使用了 Actor-Critic 框架,结合策略梯度和 Q 函数更新。以下是算法的关键步骤:

初始化

创建目标值函数网络

V_{\psi'}

,并设置其参数为

V_{\psi'}

的初始值。

初始化策略网络

\pi_\phi

和值函数网络

V_\psi

初始化两组 Q 网络

Q_{\theta_1}, Q_{\theta_2}

,用于计算 Q 值。

每一回合循环

  1. 采样动作
  2. 更新 Q 网络
  3. 更新值函数网络
  4. 更新策略网络
  5. 更新目标值函数网络

使用软更新规则:

\psi' \gets \tau \psi + (1 - \tau) \psi'

策略网络的目标是最大化奖励和熵,最小化以下损失:

J_\pi = \mathbb{E} \left[ \alpha \log \pi_\phi(a|s) - \min_{i=1,2} Q_{\theta_i}(s, a) \right]

最小化值函数损失:

J_V = \mathbb{E} \left[ \left( V_\psi(s) - y_V \right)^2 \right]

值函数

V_\psi

的目标是逼近以下值:

y_V = \mathbb{E}{a \sim \pi} \left[ \min{i=1,2} Q_{\theta_i}(s, a) - \alpha \log \pi_\phi(a|s) \right]

最小化以下损失函数:

J_Q = \mathbb{E} \left[ \left( Q_{\theta_i}(s, a) - y \right)^2 \right] \quad (i = 1, 2)

使用 TD 目标更新 Q 值:

y = r + \gamma (1 - \text{done}) \cdot V_{\psi'}(s')

执行动作,记录

(s, a, r, s', \text{done})

到经验池中。

根据策略网络

\pi_\phi

采样动作

a \sim \pi(a|s)


四、公式推导

1. Q 值更新

        Q 值通过 Bellman 方程更新,目标是最小化 TD 误差:

y = r + \gamma (1 - \text{done}) \cdot V_{\psi'}(s')

        损失函数为:

J_Q = \mathbb{E}{(s, a, r, s') \sim D} \left[ \left( Q{\theta_i}(s, a) - y \right)^2 \right]

2. 值函数更新

        值函数估计策略的长期价值,目标值为:

y_V = \mathbb{E}{a \sim \pi} \left[ \min{i=1,2} Q_{\theta_i}(s, a) - \alpha \log \pi_\phi(a|s) \right]

        损失函数为:

J_V = \mathbb{E}{s \sim D} \left[ \left( V\psi(s) - y_V \right)^2 \right]

3. 策略网络更新

        策略网络的目标是最大化奖励和熵,等价于最小化:

J_\pi = \mathbb{E}{s \sim D, a \sim \pi} \left[ \alpha \log \pi\phi(a|s) - \min_{i=1,2} Q_{\theta_i}(s, a) \right]

4. 目标值函数更新

        目标值函数使用软更新规则:

\psi' \gets \tau \psi + (1 - \tau) \psi'

        其中

\tau \in (0, 1]

控制更新步长。


[Python]  Soft Actor-Critic算法实现

        以下是PyTorch中Soft Actor-Critic (SAC)算法的完整实现:

 项目代码我已经放入GitCode里面,可以通过下面链接跳转:🔥

【强化学习】--- Soft Actor-Critic算法 

后续相关单智能体强化学习算法也会不断在【强化学习】项目里更新,如果该项目对你有所帮助,请帮我点一个星星✨✨✨✨✨,鼓励分享,十分感谢!!!

若是下面代码复现困难或者有问题,也欢迎评论区留言

 1.参数设置

"""《SAC, Soft Actor-Critic算法》 时间:2024.12 作者:不去幼儿园 """ import torch # 引入 PyTorch 库,用于构建和训练深度学习模型 import torch.nn as nn # PyTorch 的神经网络模块 import torch.optim as optim # PyTorch 的优化模块,用于更新模型参数 import numpy as np # NumPy 库,用于高效的数值计算 import gym # OpenAI Gym 库,用于创建和交互强化学习环境 import random # Python 的随机模块,用于随机抽样 from collections import deque # Python 的双端队列模块,用于构建经验回放缓冲区 # 超参数设置 GAMMA = 0.99 # 折扣因子,用于计算未来奖励 TAU = 0.005 # 软更新目标网络的参数 ALPHA = 0.2 # 熵正则化系数,鼓励探索 LR = 0.001 # 学习率,用于优化器 BATCH_SIZE = 256 # 每次训练的样本数量 MEMORY_CAPACITY = 100000 # 经验回放缓冲区的最大容量 

2.策略网络

# 策略网络(用于生成随机的策略动作) class PolicyNetwork(nn.Module): def __init__(self, state_dim, action_dim, max_action): super(PolicyNetwork, self).__init__() self.fc1 = nn.Linear(state_dim, 256) # 第一层全连接层,输入状态维度 self.fc2 = nn.Linear(256, 256) # 第二层全连接层 self.mean = nn.Linear(256, action_dim) # 输出动作均值 self.log_std = nn.Linear(256, action_dim) # 输出动作的对数标准差 self.max_action = max_action # 动作的最大值,用于缩放 def forward(self, state): x = torch.relu(self.fc1(state)) # 激活第一层 x = torch.relu(self.fc2(x)) # 激活第二层 mean = self.mean(x) # 计算动作均值 log_std = self.log_std(x).clamp(-20, 2) # 将对数标准差限制在合理范围内 std = torch.exp(log_std) # 通过对数标准差计算标准差 return mean, std # 返回均值和标准差 def sample(self, state): mean, std = self.forward(state) # 获取动作分布的均值和标准差 normal = torch.distributions.Normal(mean, std) # 正态分布 x_t = normal.rsample() # 使用重参数化技巧采样 y_t = torch.tanh(x_t) # 使用 Tanh 将动作限制在 [-1, 1] action = y_t * self.max_action # 缩放动作到最大值范围 log_prob = normal.log_prob(x_t) # 计算动作的对数概率 log_prob -= torch.log(1 - y_t.pow(2) + 1e-6) # Tanh 的修正项 log_prob = log_prob.sum(dim=-1, keepdim=True) # 对每个维度求和 return action, log_prob # 返回动作和对数概率 

 3.Q 网络

# Q 网络(价值函数,用于评估状态-动作对的价值) class QNetwork(nn.Module): def __init__(self, state_dim, action_dim): super(QNetwork, self).__init__() self.fc1 = nn.Linear(state_dim + action_dim, 256) # 输入包括状态和动作 self.fc2 = nn.Linear(256, 256) # 第二层全连接层 self.fc3 = nn.Linear(256, 1) # 输出单一 Q 值 def forward(self, state, action): x = torch.cat([state, action], dim=-1) # 将状态和动作连接起来作为输入 x = torch.relu(self.fc1(x)) # 激活第一层 x = torch.relu(self.fc2(x)) # 激活第二层 x = self.fc3(x) # 输出 Q 值 return x # 返回 Q 值

 4.经验回放缓冲区

# 经验回放缓冲区 class ReplayBuffer: def __init__(self, capacity): self.buffer = deque(maxlen=capacity) # 初始化一个具有固定最大容量的双端队列 def push(self, state, action, reward, next_state, done): # 存储经验 self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size): # 随机采样 batch = random.sample(self.buffer, batch_size) # 随机选取 batch_size 个经验 states, actions, rewards, next_states, dones = zip(*batch) # 解压 return (np.array(states), np.array(actions), np.array(rewards), np.array(next_states), np.array(dones)) def __len__(self): # 返回缓冲区当前大小 return len(self.buffer)

 5.SAC 算法

 # SAC 算法智能体 class SACAgent: def __init__(self, state_dim, action_dim, max_action): self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检查是否有 GPU # 初始化网络 self.actor = PolicyNetwork(state_dim, action_dim, max_action).to(self.device) # 策略网络 self.q1 = QNetwork(state_dim, action_dim).to(self.device) # 第一个 Q 网络 self.q2 = QNetwork(state_dim, action_dim).to(self.device) # 第二个 Q 网络 # 初始化优化器 self.actor_optimizer = optim.Adam(self.actor.parameters(), lr=LR) # 策略网络优化器 self.q1_optimizer = optim.Adam(self.q1.parameters(), lr=LR) # Q1 优化器 self.q2_optimizer = optim.Adam(self.q2.parameters(), lr=LR) # Q2 优化器 # 初始化经验回放缓冲区 self.replay_buffer = ReplayBuffer(MEMORY_CAPACITY) self.max_action = max_action # 最大动作值 def select_action(self, state): # 根据策略选择动作 state = torch.FloatTensor(state).to(self.device).unsqueeze(0) # 转换状态为张量 action, _ = self.actor.sample(state) # 从策略中采样动作 return action.cpu().detach().numpy()[0] # 转换为 NumPy 格式返回 def train(self): # 训练智能体 if len(self.replay_buffer) < BATCH_SIZE: # 如果经验回放缓冲区不足,跳过训练 return # 从回放缓冲区采样 states, actions, rewards, next_states, dones = self.replay_buffer.sample(BATCH_SIZE) states = torch.FloatTensor(states).to(self.device) actions = torch.FloatTensor(actions).to(self.device) rewards = torch.FloatTensor(rewards).unsqueeze(1).to(self.device) next_states = torch.FloatTensor(next_states).to(self.device) dones = torch.FloatTensor(dones).unsqueeze(1).to(self.device) # 更新 Q 网络 with torch.no_grad(): next_actions, log_probs = self.actor.sample(next_states) # 计算下一步的动作及其概率 target_q1 = self.q1(next_states, next_actions) # Q1 值 target_q2 = self.q2(next_states, next_actions) # Q2 值 target_q = torch.min(target_q1, target_q2) - ALPHA * log_probs # 使用最小值更新 q_target = rewards + GAMMA * (1 - dones) * target_q # 计算目标 Q 值 q1_loss = ((self.q1(states, actions) - q_target) ** 2).mean() # Q1 损失 q2_loss = ((self.q2(states, actions) - q_target) ** 2).mean() # Q2 损失 self.q1_optimizer.zero_grad() # 清空梯度 q1_loss.backward() # 反向传播 self.q1_optimizer.step() # 更新 Q1 self.q2_optimizer.zero_grad() q2_loss.backward() self.q2_optimizer.step() # 更新策略网络 new_actions, log_probs = self.actor.sample(states) q1_new = self.q1(states, new_actions) q2_new = self.q2(states, new_actions) q_new = torch.min(q1_new, q2_new) actor_loss = (ALPHA * log_probs - q_new).mean() # 策略损失 self.actor_optimizer.zero_grad() actor_loss.backward() self.actor_optimizer.step() def update_replay_buffer(self, state, action, reward, next_state, done): self.replay_buffer.push(state, action, reward, next_state, done)

6.主函数循环

# 训练循环 env = gym.make("Pendulum-v1") # 创建环境 state_dim = env.observation_space.shape[0] # 状态空间维度 action_dim = env.action_space.shape[0] # 动作空间维度 max_action = float(env.action_space.high[0]) # 最大动作值 agent = SACAgent(state_dim, action_dim, max_action) # 初始化智能体 num_episodes = 500 # 训练的总回合数 for episode in range(num_episodes): state = env.reset() # 重置环境 episode_reward = 0 # 初始化总奖励 done = False while not done: action = agent.select_action(state) # 根据策略选择动作 next_state, reward, done, _ = env.step(action) # 执行动作 agent.update_replay_buffer(state, action, reward, next_state, done) # 更新经验 agent.train() # 训练智能体 state = next_state # 更新状态 episode_reward += reward # 累积奖励 print(f"Episode {episode}, Reward: {episode_reward}") # 打印回合奖励


[Notice] 代码核心

(SAC, Soft Actor-Critic)算法是一种支持连续动作空间的强化学习算法。核心功能包括:

  1. 策略网络(Policy Network):生成随机策略,用于采样动作。
  2. 价值网络(Q 网络):评估当前动作-状态对的价值。
  3. 经验回放缓冲区:存储并采样过往经验,提升训练稳定性。
  4. 训练循环:在环境中反复交互,学习最优策略。
​# 环境配置 Python 3.11.5 torch 2.1.0 torchvision 0.16.0 gym 0.26.2
        由于博文主要为了介绍相关算法的原理和应用的方法,缺乏对于实际效果的关注,算法可能在上述环境中的效果不佳或者无法运行,一是算法不适配上述环境,二是算法未调参和优化,三是没有呈现完整的代码,四是等等。上述代码用于了解和学习算法足够了,但若是想直接将上面代码应用于实际项目中,还需要进行修改。

五、 SAC 优势

  1. 样本效率高:使用离线经验池,充分利用历史数据。
  2. 探索能力强:通过最大化熵,鼓励更广泛的探索。
  3. 稳定性好:结合双 Q 网络和目标网络,降低训练波动。
  4. 适用于连续动作空间:支持复杂控制任务,如机器人控制。

参考文献:

[Haarnoja, Tuomas, et al. "Soft actor-critic: Off-policy maximum entropy deep reinforcement learning with a stochastic actor."‌发表于2018年,详细介绍了SAC算法的基本原理和应用‌1。

Haarnoja, Tuomas, et al. "Soft actor-critic algorithms and applications."‌ 同进一步探讨了SAC算法的损失函数和最大熵框架‌

 更多强化学习文章,请前往:【强化学习(RL)】专栏

        博客都是给自己看的笔记,如有误导深表抱歉。文章若有不当和不正确之处,还望理解与指出。由于部分文字、图片等来源于互联网,无法核实真实出处,如涉及相关争议,请联系博主删除。如有错误、疑问和侵权,欢迎评论留言联系作者,或者添加VX:Rainbook_2,联系作者。✨

Read more

使用 VS Code 将项目代码上传到 Gitee 的完整指南

使用 VS Code 将项目代码上传到 Gitee 的完整指南

在现代软件开发流程中,版本控制是不可或缺的一环。 Gitee(码云)作为国内领先的代码托管平台,为开发者提供了稳定、快速的 Git 服务。 本文将详细介绍如何使用 Visual Studio Code(VS Code)将本地项目代码上传至 Gitee 仓库,涵盖从环境配置、初始化仓库到推送代码的完整流程。 一、准备工作 1. 安装必要工具 * Git:确保你的系统已安装 Git。 可通过终端运行 git --version  或 git -v 验证是否安装成功。 * VS Code:下载并安装 Visual Studio Code。 * Gitee 账号:前往 Gitee 官网 注册账号(如尚未注册)。 2. 安装 VS

By Ne0inhk
使用Git将代码从远程仓库拉取到本地(详细图解、简单易懂)

使用Git将代码从远程仓库拉取到本地(详细图解、简单易懂)

目录 一、前言 二、全流程 一、前言 本博客主要记录一下使用Git将代码从远程仓库拉取到本地的全流程,使用Git拉取代码在学校内多同学合作开发项目或者是实习拉取公司代码等场景都很常见,单纯记录希望对你有帮助 二、全流程 首先在你想要存放代码的位置新建一个文件夹并改名 进入刚刚创建的空文件中,右键然后点击显示更多选项 然后点击Git Bash Here 然后就会出现如图所示的命令行窗口 此时先不用管命令行窗口,找到你要远程仓库所在的平台(我这里以Gitee演示),如图点击克隆/下载按钮 HTTPS下方就是远程仓库的url地址,只要有远程仓库的url地址,只需要在刚刚的命令行窗口打上git clone在将url地址复制在后面再回车即可(Gitee下面的提示也给了,直接复制带git clone的命令就行,没有的话就自己敲git clone) 复制到命令行窗口之后,等待片刻即可 然后点开刚刚创建的文件夹就可以看到拉取下来的代码了,后续用IDEA打开该文件就可以在本地进行开发了

By Ne0inhk

Zvec 架构深度解析:阿里巴巴开源的轻量级进程内向量数据库

Zvec 架构深度解析:阿里巴巴开源的轻量级进程内向量数据库 Zvec 是阿里巴巴开源的一个轻量级、闪电般快速的进程内向量数据库。本文将深入分析 Zvec 的代码架构,揭示其核心设计理念和技术实现细节。 一、项目概览 1.1 核心特性 Zvec 基于 Alibaba 久经考验的 Proxima 向量搜索引擎构建,提供生产级的低延迟、可扩展的相似度搜索能力: * 极致性能:毫秒级搜索数十亿级向量 * 简单易用:无需服务器配置,零依赖安装 * 混合向量支持:同时支持稠密向量(Dense)和稀疏向量(Sparse) * 混合搜索:语义相似度 + 结构化过滤 * 随处运行:嵌入到应用进程内运行 1.2 技术栈 组件技术语言C++17构建系统CMakePython绑定Pybind11存储引擎RocksDB向量索引Proxima (IVF, HNSW, Flat)序列化Protobuf压缩LZ4位图CRoaring距离计算SIMD 加速 1.3

By Ne0inhk

GitHub机器人故障处理:从403错误到权限重构

GitHub机器人故障处理:从403错误到权限重构 【免费下载链接】LightGBMmicrosoft/LightGBM: LightGBM 是微软开发的一款梯度提升机(Gradient Boosting Machine, GBM)框架,具有高效、分布式和并行化等特点,常用于机器学习领域的分类和回归任务,在数据科学竞赛和工业界有广泛应用。 项目地址: https://gitcode.com/GitHub_Trending/li/LightGBM 在开源项目协作中,自动化工具是提升管理效率的关键。LightGBM项目近期遭遇了"no-response"机器人功能异常,导致issue标签管理失效。本文将系统分析这一故障从发现到解决的全过程,揭示GitHub工作流权限管理的核心要点,为同类项目提供可复用的故障处理方案。 故障表现:标签管理失控的真实场景 用户反馈聚焦三大异常现象 项目维护者@guolinke首先注意到异常:在issue #4589中,用户@数据分析菜鸟已提供详细的日志信息,但"awaiting response"标签仍然存在。

By Ne0inhk