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

米饭3个月前行业资讯334

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

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

二、SAC 背景与核心思想

1. 强化学习的挑战

探索与利用的平衡:传统算法在初期探索新策略与后期利用已有最优策略之间难以达到平衡。

不稳定性:在连续动作空间中,训练通常会出现发散或收敛缓慢的问题。

样本效率:强化学习中,数据采集成本高,如何有效利用经验池中的数据是关键。

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


最大熵强化学习:在最大化累计奖励的同时,最大化策略的随机性(熵),以鼓励探索。

双 Q 网络:缓解 Q 值过估计的问题。

目标网络:使用目标网络稳定 Q 值计算。

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

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




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




其中:


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

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

效果:


更好的探索:熵的最大化使策略更加多样化。

更稳定的学习:避免陷入次优策略。

三、SAC 算法流程

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


初始化:

初始化两组 Q 网络,用于计算 Q 值。

初始化策略网络和值函数网络。

创建目标值函数网络,并设置其参数为  的初始值。

每一回合循环:

采样动作:


根据策略网络 采样动作 。

执行动作,记录 到经验池中。

更新 Q 网络:


使用 TD 目标更新 Q 值: 

最小化以下损失函数: 

更新值函数网络:


值函数的目标是逼近以下值:


最小化值函数损失:

更新策略网络:


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


更新目标值函数网络:


使用软更新规则: 

四、公式推导

1. Q 值更新

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


        损失函数为: 


2. 值函数更新

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




        损失函数为:


3. 策略网络更新

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




4. 目标值函数更新

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


        其中  控制更新步长。

[Python]  Soft Actor-Critic算法实现

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

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)算法是一种支持连续动作空间的强化学习算法。核心功能包括:


策略网络(Policy Network):生成随机策略,用于采样动作。

价值网络(Q 网络):评估当前动作-状态对的价值。

经验回放缓冲区:存储并采样过往经验,提升训练稳定性。

训练循环:在环境中反复交互,学习最优策略。

# 环境配置
Python                  3.11.5
torch                   2.1.0
torchvision             0.16.0
gym                     0.26.2

 由于博文主要为了介绍相关算法的原理和应用的方法,缺乏对于实际效果的关注,算法可能在上述环境中的效果不佳或者无法运行,一是算法不适配上述环境,二是算法未调参和优化,三是没有呈现完整的代码,四是等等。上述代码用于了解和学习算法足够了,但若是想直接将上面代码应用于实际项目中,还需要进行修改。

五、 SAC 优势

  1. 样本效率高:使用离线经验池,充分利用历史数据。

  2. 探索能力强:通过最大化熵,鼓励更广泛的探索。

  3. 稳定性好:结合双 Q 网络和目标网络,降低训练波动。

  4. 适用于连续动作空间:支持复杂控制任务,如机器人控制。


本文系转载,版权归原作者所有,如若侵权请联系我们进行删除!  

云掣基于多年在运维领域的丰富时间经验,编写了《云运维服务白皮书》,欢迎大家互相交流学习:

《云运维服务白皮书》下载地址:https://fs80.cn/v2kbbq

想了解更多大数据运维托管服务、数据库运维托管服务、应用系统运维托管服务的的客户,欢迎点击云掣官网沟通咨询:https://yunche.pro/?t=shequ


相关文章

docker composer 安装sentry的运维总结--未完待续

docker composer 安装sentry的运维总结--未完待续

一、sentry安装1、下载最新的github工程地址是https://github.com/getsentry/self-hosted。我们安装的时候最新版本还是Sentry 21.6.1, 截止目...

如何理解运维

如何理解运维

运维工程师(运营),负责维护并确保整个服务的高可用性,同时不断优化系统架构提升部署效率,优化资源利用率提高整体的投资回报率。运维工程师面对的最大挑战是大规模集群的管理问题,如何管理好几十万台服务器上的...

Docker:容器化和虚拟化

Docker:容器化和虚拟化

虚拟化虚拟化是一种资源管理技术,它将计算机的各种实体资源(如CPU、内存、磁盘空间、网络适配器等)予以抽象、转换后呈现出来,并可供分割、组合为一个或多个电脑配置环境。这些资源的新虚拟部分是不受现有资源...

深度学习之经典网络-AlexNet详解

深度学习之经典网络-AlexNet详解

 AlexNet 是一种经典的卷积神经网络(CNN)架构,在 2012 年的 ImageNet 大规模视觉识别挑战赛(ILSVRC)中表现优异,将 CNN 引入深度学习的新时代。AlexNe...

【Docker】1.Docker的前身LXC

【Docker】1.Docker的前身LXC

LXC简介:Linux Containers ,一种操作系统层虚拟化技术,为Linux内核容器功能的一个用户空间接口。将应用软件系统打包成一个软件容器内涵应用软件本身的代码,操作系统核心和库...

Docker:LXC容器操作实战

Docker:LXC容器操作实战

前言通过LXC来完成容器的创建、体会什么是容器。利用LXC容器技术来隔离特定的应用,提供虚拟执行环境,从而优化资源管理和部署效率。什么是LXC?LXC为Linux Container的简写,是一种可以...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。