【DL】强化学习

学习资源

动手学强化学习
https://hrl.boyuai.com/
https://github.com/boyu-ai/Hands-on-RL

b站资源
【王树森】深度强化学习(DRL)

简介

强化学习是机器学习的一个领域,它主要关注如何使智能体(Agent)在环境(Environment)中学会采取行动(Action)以最大化某种累积奖励(Reward)。强化学习与其他类型的机器学习(如监督学习和无监督学习)的主要区别在于,它不依赖于预先标记的输入/输出对,而是通过智能体与环境的交互来学习。以下是这些概念的详细介绍:

  1. 智能体(Agent)

    • 智能体是强化学习中的决策者,它通过观察环境来学习如何采取行动。智能体的目标是通过这些行动来最大化其长期奖励。它可以是任何能够感知其环境并根据这些观察做出决策的实体,如机器人、软件程序等。
  2. 环境(Environment)

    • 环境是智能体所处并与之互动的系统或问题域。环境接收智能体的行动并根据这些行动提供状态的反馈和奖励。环境的反馈可以是非常简单的,也可以是极其复杂且动态变化的。
  3. 状态(State)

    • 状态是对环境在某一时刻的描述。它可以是环境的完整描述,也可以只是环境的一部分。状态为智能体提供了决策的上下文,在给定状态下采取行动可以导致环境状态的变化。
  4. 动作(Action)

    • 动作是智能体在给定状态下可以采取的决策或步骤。智能体的动作空间可以是离散的(如左转、右转)或连续的(如加速的量)。智能体的行动会影响环境,并导致状态的变化和奖励的给予。
  5. 奖励(Reward)

    • 奖励是环境对智能体采取特定行动的即时评价。奖励通常是一个标量值,指示智能体的行动对于达成其目标的有用程度。智能体的目标是最大化在整个学习过程中获得的累积奖励。
  6. 策略(Policy)

    • 策略是从状态到动作的映射,它定义了智能体在给定状态下应该采取什么行动。策略可以是简单的静态规则,也可以是复杂的动态函数,取决于智能体的学习算法和环境的性质。智能体的目标是学习一个最优策略,这个策略能在长期内最大化累积奖励。

强化学习的过程过程可以通过多种算法来通常涉及智能体不断地通过与环境互动来尝试不同的策略,评估这些策略带来的奖励,并根据这些奖励来调整其策略,以学习如何最优地行动。这个实现,如Q学习、深度Q网络(DQN)、策略梯度方法等。

学习笔记

Q 为什么说sarsa算法只关注了下一步状态的价值估计,而不是真实的价值?
A
SARSA(State-Action-Reward-State-Action)算法是一种在强化学习中常用的策略评估和策略改进方法,它属于时序差分(TD)学习的一种。SARSA算法之所以被认为只关注了下一步状态的价值估计,而不是真实的价值,主要有以下几个原因:

1. 单步更新机制

  • SARSA算法采用的是单步时序差分(TD(0))更新机制。这意味着在每个时间步,SARSA只根据当前状态、动作、奖励、下一个状态和下一个动作来更新当前状态-动作对的价值估计。具体来说,SARSA的更新公式为:
    Q(s,a)Q(s,a)+α[r+γQ(s,a)Q(s,a)] Q(s, a) \leftarrow Q(s, a) + \alpha \left[ r + \gamma Q(s', a') - Q(s, a) \right]
    其中,Q(s,a)Q(s, a) 是状态 ss 下采取动作 aa 的价值估计,rr 是即时奖励,γ\gamma 是折扣因子,ss' 是下一个状态,aa' 是下一个动作。

  • 这种单步更新机制意味着SARSA只考虑了从当前状态 ss 到下一个状态 ss' 的单步转移,而没有考虑更远未来的状态和动作。因此,SARSA的价值估计是基于对下一步状态-动作对的价值估计,而不是对整个未来轨迹的真实价值。

2. 在线策略(On-Policy)特性

  • SARSA是一种在线策略算法,它直接使用当前策略(即在状态 ss' 下选择的动作 aa')来更新价值函数。这意味着SARSA的价值估计是基于当前策略的行为,而不是基于某种最优策略的行为。
  • 由于SARSA的价值更新依赖于当前策略,它只能估计当前策略下的价值,而不能直接估计最优策略下的真实价值。因此,SARSA的价值估计是策略相关的,而不是策略无关的。

3. 缺乏对长期回报的考虑

  • SARSA的更新公式中,只考虑了当前的即时奖励 rr 和下一个状态-动作对的价值 Q(s,a)Q(s', a'),而没有考虑更远的未来奖励。这意味着SARSA的价值估计是短视的,它没有考虑到长期回报的影响。
  • 相比之下,Q-learning(另一种TD学习算法)在更新时会考虑下一个状态的最大价值动作(即 maxaQ(s,a)\max_{a'} Q(s', a')),从而间接考虑了更远的未来回报。因此,Q-learning的价值估计更接近于最优策略下的真实价值。

4. 探索与利用的平衡

  • 由于SARSA是基于当前策略的,它在探索与利用之间需要保持平衡。如果策略过于保守(即倾向于选择已知的高价值动作),SARSA的价值估计可能会低估某些潜在的高价值状态-动作对。
  • 这种探索与利用的平衡问题进一步限制了SARSA对真实价值的估计能力,因为它可能会错过某些潜在的高价值路径。

总结

SARSA算法之所以被认为只关注了下一步状态的价值估计,而不是真实的价值,主要是因为它采用了单步时序差分更新机制,依赖于当前策略,缺乏对长期回报的考虑,并且在探索与利用之间需要平衡。这些特性使得SARSA的价值估计更偏向于当前策略下的短期价值,而不是最优策略下的长期真实价值。

Q可否这样理解:下一步的状态价值Q(s',a')正常来说应该用蒙特卡洛方法来估计得到一个近似真实的值,但是sarsa算法只根据当前步来获得下一步状态的价值,所以只能是一个估计的价值
A
是的,您的理解是正确的。SARSA算法确实只根据当前步来获得下一步状态的价值,因此它只能是一个估计的价值,而不是一个近似真实的值。下面我将进一步解释这一点。

蒙特卡洛方法与时序差分方法的对比

蒙特卡洛方法

  • 蒙特卡洛方法通过在整个轨迹(episode)结束后,根据实际获得的累积奖励来估计状态-动作对的价值。具体来说,蒙特卡洛方法使用以下公式更新价值函数:
    Q(s,a)Q(s,a)+α[GtQ(s,a)] Q(s, a) \leftarrow Q(s, a) + \alpha \left[ G_t - Q(s, a) \right]
    其中,GtG_t 是从时间 tt 开始到轨迹结束时的累积折扣奖励。

  • 蒙特卡洛方法的优势在于它能够提供一个无偏估计(即真实的累积奖励),因为它考虑了整个轨迹的奖励。

时序差分方法(如SARSA)

  • 时序差分方法(如SARSA)则是在每个时间步进行更新,而不是等到整个轨迹结束。SARSA的更新公式为:
    Q(s,a)Q(s,a)+α[r+γQ(s,a)Q(s,a)] Q(s, a) \leftarrow Q(s, a) + \alpha \left[ r + \gamma Q(s', a') - Q(s, a) \right]
    其中,rr 是即时奖励,γ\gamma 是折扣因子,Q(s,a)Q(s', a') 是下一个状态-动作对的价值估计。

  • 由于SARSA只考虑了当前步的奖励和下一步状态-动作对的价值估计,它提供的是一个有偏估计。这是因为 Q(s,a)Q(s', a') 本身也是一个估计值,而不是真实的累积奖励。

SARSA的局限性

  • 单步更新:SARSA的单步更新机制意味着它只依赖于当前步的奖励和下一步状态-动作对的价值估计。这种更新方式使得SARSA的价值估计是短视的,因为它没有考虑到更远的未来奖励。

  • 依赖当前策略:SARSA的价值更新依赖于当前策略选择的动作 aa',这意味着它的价值估计是策略相关的。如果当前策略不是最优的,SARSA的价值估计也会偏离最优策略下的真实价值。

  • 探索与利用的平衡:SARSA需要在探索与利用之间保持平衡。如果策略过于保守,SARSA可能会低估某些潜在的高价值状态-动作对;如果策略过于冒险,SARSA可能会高估某些低价值状态-动作对。

总结

SARSA算法只根据当前步来获得下一步状态的价值,因此它只能是一个估计的价值,而不是一个近似真实的值。这种估计是基于当前策略和单步更新的,因此它具有一定的局限性,特别是在考虑长期回报和最优策略时。相比之下,蒙特卡洛方法通过整个轨迹的累积奖励来估计价值,能够提供一个更接近真实值的估计。

常见算法

动态规划

动态规划(Dynamic Programming, DP)在强化学习中扮演着基础而重要的角色,尤其是在处理具有完全已知的环境模型(即状态转移概率和奖励函数已知)的问题时。DP方法依赖于贝尔曼方程,这是一组递归方程,用于描述状态值函数或动作值函数(即Q函数)之间的关系。在强化学习中,动态规划法主要用于两个方面:策略评估(Policy Evaluation)和策略提升(Policy Improvement),这两个步骤循环交替执行以找到最优策略。

策略评估(Policy Evaluation)

策略评估的目标是计算某策略下的状态值函数,即在遵循特定策略的条件下,从某状态开始所能获得的预期回报。通过迭代地应用贝尔曼期望方程,我们可以评估当前策略的效果:

Vπ(s)=aAπ(as)s,rP(s,rs,a)[r+γVπ(s)]V^{\pi}(s) = \sum_{a \in A} \pi(a|s) \sum_{s', r} P(s', r | s, a)[r + \gamma V^{\pi}(s')]

其中,$$ V^{\pi}(s) $$ 是在状态 ss 下遵循策略 π\pi 所得到的价值,π(as)\pi(a|s) 是在状态 ss 下采取动作 aa 的概率,P(s,rs,a)P(s', r | s, a) 是从状态 ss 采取动作 aa 转移到状态 ss' 并得到奖励 rr 的概率,γ\gamma 是折扣因子,用于计算未来奖励的现值。

策略提升(Policy Improvement)

策略提升的目标是生成一个新策略,该策略在每个状态下都可以选择使动作价值最大化的动作。通过这个过程,我们可以从当前策略产生一个更好的策略。策略提升通常使用贝尔曼最优方程:

Qπ(s,a)=s,rP(s,rs,a)[r+γVπ(s)]Q^{\pi}(s, a) = \sum_{s', r} P(s', r | s, a)[r + \gamma V^{\pi}(s')]

然后,新的策略 π\pi' 在每个状态下选择最大化 Qπ(s,a)Q^{\pi}(s, a) 的动作:

π(s)=argmaxaQπ(s,a)\pi'(s) = \arg\max_a Q^{\pi}(s, a)

策略迭代(Policy Iteration)

策略迭代结合了策略评估和策略提升,通过迭代这两个步骤直到策略收敛到最优策略。每次迭代包括完全评估当前策略(直到值函数收敛),然后进行策略提升。

值迭代(Value Iteration)

值迭代是策略迭代的一种简化形式,它结合了策略评估的一步操作和策略提升。值迭代直接迭代更新每个状态的最大动作价值,直到价值函数收敛:

V(s)=maxas,rP(s,rs,a)[r+γV(s)]V(s) = \max_a \sum_{s', r} P(s', r | s, a)[r + \gamma V(s')]

动态规划方法在理论上可以得到最优策略,但其应用受限于环境模型的已知性以及状态和动作空间的规模。当状态和动作空间非常大或环境模型未知时,直接应用动态规划变得不可行,这时通常会

马尔可夫

蒙特卡洛

蒙特卡洛方法在强化学习中的应用提供了一种在不完全知道环境模型(即转移概率和奖励函数未知)的情况下学习最优策略的方法。与动态规划不同,蒙特卡洛(MC)方法不需要知道环境的具体动态,而是通过从环境中采样完成的序列(或称为“情节”)来学习。这些方法特别适用于具有高度不确定性或模型未知的环境。MC方法的关键特点是它们依赖于经验平均来估计值函数,这些平均值是从一系列完整情节中获得的。

MC策略评估

在策略评估过程中,MC方法通过对从当前策略生成的一系列完整情节的回报进行平均来估计状态的值。每个情节包含一系列的状态、动作和奖励,以及情节的最终结果。通过对同一状态多次访问得到的回报进行平均,MC方法可以估计该状态的值,即该状态的长期回报期望。

MC控制

为了找到最优策略,MC方法采用了一种称为MC控制的方法。MC控制通常涉及两个主要步骤:探索和利用。一个常见的策略是使用ε-贪婪策略,其中智能体大部分时间选择当前最佳动作(利用),但有时也随机选择其他动作(探索),以确保长期学习。

MC控制方法通过不断交替执行策略评估和策略提升来工作。在策略评估阶段,智能体使用其当前策略在环境中执行动作,并记录下来每个情节的结果。接着,使用这些情节的结果来更新值函数。在策略提升阶段,智能体根据估计的值函数更新其策略,通常是通过选择使得估计值函数最大化的动作。

优点与局限

MC方法的一个主要优点是它们不依赖于环境的先验知识,使其适用于模型未知的情况。此外,MC方法直接从最终回报中学习,而不依赖于其他状态的值估计,这有助于减少累积的估计误差。

然而,MC方法也有其局限性。首先,它们只适用于情节性任务,即那些有明确开始和结束的任务。其次,MC方法可能需要很多情节才能获得可靠的值估计,尤其是在回报信号稀疏或噪声很大的情境中。此外,MC方法只在情节结束时更新值函数和策略,这可能导致学习速度较慢。

总的来说,蒙特卡洛方法在强化学习中提供了一种强大的工具,尤其是在那些模型未知或难以精确建模的情况下。通过适当的策略和技巧(如重要性采样)的改进,可以进一步增强MC方法的应用范围和效率。

时序差分法

Q-Learning

什么是 Q Learning (Reinforcement Learning 强化学习
强化学习算法系列教程及代码实现-Q-Learning
Q学习(Q-learning)简单理解

DQN(Deep Q Network)

什么是 DQN (Reinforcement Learning 强化学习)
【深度强化学习】(1) DQN 模型解析,附Pytorch完整代码

Deep Q-Network (DQN) 的结构与原理

Deep Q-Network (DQN) 是深度强化学习中的一种方法,通过结合 Q-Learning 和深度神经网络,实现从高维状态空间中进行学习。以下将从结构、原理及其每个组件的实现过程进行详细讲解。


一、DQN 的核心思想

DQN 主要用于解决强化学习中的 Q-Learning 在高维状态空间中表现不佳的问题。其基本思想是使用深度神经网络(DNN)来近似 Q值函数,从而在面对复杂的状态时,依然能高效地学习最优策略。

Q-Learning 的核心更新公式如下:

Q(s,a)Q(s,a)+α(r+γmaxaQ(s,a)Q(s,a)) Q(s, a) \leftarrow Q(s, a) + \alpha \left( r + \gamma \max_{a'} Q(s', a') - Q(s, a) \right)

在 DQN 中,这个公式变为基于神经网络的近似形式:

Qθ(s,a)Q(s,a) Q_{\theta}(s, a) \approx Q^*(s, a)

其中,θ\theta 是神经网络的参数。


二、DQN 的结构组件

1. 神经网络架构

DQN 使用一个神经网络来近似 Q 值函数。输入是状态 ss,输出是每个动作 aa 对应的 Q 值 Q(s,a)Q(s, a)

  • 输入层:接收状态 ss(通常是一个多维向量或图像)。
  • 隐藏层:一系列全连接层(Dense Layer)或卷积层(用于图像输入),用于提取特征。
  • 输出层:输出每个动作 aa 对应的 Q 值。

实现:

import torch
import torch.nn as nn
import torch.optim as optim

class DQNetwork(nn.Module):
    def __init__(self, state_size, action_size):
        super(DQNetwork, self).__init__()
        self.fc1 = nn.Linear(state_size, 128)
        self.fc2 = nn.Linear(128, 64)
        self.fc3 = nn.Linear(64, action_size)
    
    def forward(self, state):
        x = torch.relu(self.fc1(state))
        x = torch.relu(self.fc2(x))
        return self.fc3(x)

2. 经验回放(Experience Replay)

为了提高样本效率并减少训练过程中的相关性,DQN 引入了 经验回放机制。它通过存储过去的经验(s,a,r,ss, a, r, s')在一个缓冲区中,并从中随机抽取小批量数据进行训练,打破了数据的时间相关性。

实现:

import random
from collections import deque

class ReplayBuffer:
    def __init__(self, buffer_size):
        self.buffer = deque(maxlen=buffer_size)
    
    def add(self, experience):
        self.buffer.append(experience)
    
    def sample(self, batch_size):
        return random.sample(self.buffer, batch_size)
    
    def __len__(self):
        return len(self.buffer)

3. 目标网络(Target Network)

为了稳定训练,DQN 使用了一个 目标网络 QθQ_{\theta^-},这个网络与主网络具有相同的结构,但参数在一定步数后才同步更新。

目标网络的作用是计算目标 Q 值:

y=r+γmaxaQθ(s,a) y = r + \gamma \max_{a'} Q_{\theta^-}(s', a')

实现:

# 初始化两个网络
main_network = DQNetwork(state_size, action_size)
target_network = DQNetwork(state_size, action_size)
target_network.load_state_dict(main_network.state_dict())

在训练过程中,每隔 CC 步将主网络的参数复制到目标网络:

if step % update_target_every == 0:
    target_network.load_state_dict(main_network.state_dict())

4. ε-贪心策略(Epsilon-Greedy Policy)

DQN 使用 ε-贪心策略 来平衡探索(exploration)和利用(exploitation)。在每一步中,智能体以概率 ϵ\epsilon 随机选择动作,以 1ϵ1 - \epsilon 的概率选择当前 Q 值最大的动作。

a={随机动作以概率 ϵargmaxaQ(s,a)以概率 1ϵ a = \begin{cases} \text{随机动作} & \text{以概率 } \epsilon \\ \arg\max_a Q(s, a) & \text{以概率 } 1 - \epsilon \end{cases}

实现:

import numpy as np

def epsilon_greedy_action(state, epsilon):
    if np.random.rand() < epsilon:
        return np.random.randint(action_size)  # 随机动作
    else:
        with torch.no_grad():
            return torch.argmax(main_network(torch.tensor(state, dtype=torch.float32))).item()

三、DQN 的训练过程

  1. 初始化:

    • 初始化主网络和目标网络。
    • 初始化经验回放缓冲区。
  2. 交互和存储:

    • 根据 ε-贪心策略选择动作。
    • 执行动作并存储 (s,a,r,s)(s, a, r, s') 到经验回放缓冲区。
  3. 经验回放:

    • 从经验缓冲区中随机抽取一个小批量数据。
  4. 计算目标 Q 值:
    y=r+γmaxaQθ(s,a) y = r + \gamma \max_{a'} Q_{\theta^-}(s', a')

  5. 更新主网络参数:
    通过梯度下降法最小化以下损失函数:
    L(θ)=E[(yQθ(s,a))2] L(\theta) = \mathbb{E} \left[ (y - Q_{\theta}(s, a))^2 \right]

  6. 更新目标网络:
    每隔 CC 步,将主网络的参数复制到目标网络。

深度强化学习基础 (4/5):Actor-Critic Methods(4/5)

PPO

PPO
用一个PPO代码进行解释:

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
from torch.distributions import Categorical
import gym

# 定义神经网络架构
class ActorCritic(nn.Module):
    def __init__(self, state_dim, action_dim):
        super(ActorCritic, self).__init__()
        self.fc = nn.Linear(state_dim, 128)
        self.actor = nn.Linear(128, action_dim)  # actor部分输出动作概率
        self.critic = nn.Linear(128, 1)  # critic部分输出状态值

    def forward(self, state):
        x = torch.relu(self.fc(state))
        action_prob = torch.softmax(self.actor(x), dim=-1)
        state_value = self.critic(x)
        return action_prob, state_value

# PPO算法的实现
class PPO:
    def __init__(self, env, actor_critic, clip_ratio=0.2, gamma=0.99, lamda=0.95, lr=3e-4, update_timestep=4000, batch_size=64):
        self.env = env
        self.actor_critic = actor_critic
        self.clip_ratio = clip_ratio  # 剪切比率
        self.gamma = gamma  # 折扣因子
        self.lamda = lamda  # GAE-Lambda参数
        self.lr = lr  # 学习率
        self.update_timestep = update_timestep  # 每更新一次所需的时间步数
        self.batch_size = batch_size  # 批量更新的大小
        
        # 优化器
        self.optimizer = optim.Adam(self.actor_critic.parameters(), lr=self.lr)

    def compute_gae(self, rewards, values, next_values, dones):
        # 计算广义优势估计(GAE)
        '''
        compute_gae 输出的是每个时间步的 优势(advantage),并且优势列表中的每个元素对应于 states 中每个状态的优势值。
        '''
        advantages = []
        gae = 0
        for t in reversed(range(len(rewards))):  # 计算每个时间步t的优势
            delta = rewards[t] + self.gamma * next_values[t] * (1 - dones[t]) - values[t]
            gae = delta + self.gamma * self.lamda * (1 - dones[t]) * gae
            advantages.insert(0, gae)
        return advantages  # 
 
    def update(self, states, actions, rewards, next_states, dones):
        # 获取当前状态值和下一个状态值
        '''
        actor_critic输入单一状态则输出单一value值
        状态列表输入,actor_critic 输出一个与输入状态数量相同的状态值列表,每个状态对应一个状态值
        '''
        _, old_values = self.actor_critic(states)  # 输入状态列表,返回old_values列表,对应states中每个状态
        _, next_values = self.actor_critic(next_states)

        # 计算GAE
        advantages = self.compute_gae(rewards, old_values.detach().numpy(), next_values.detach().numpy(), dones)

        # 计算目标值
        targets = advantages + old_values.detach().numpy()

        # 转换为PyTorch的张量
        states = torch.tensor(states, dtype=torch.float32)
        actions = torch.tensor(actions, dtype=torch.long)
        advantages = torch.tensor(advantages, dtype=torch.float32)
        targets = torch.tensor(targets, dtype=torch.float32)

        # PPO的优化
        for _ in range(self.update_timestep):
            action_prob, state_value = self.actor_critic(states)
            dist = Categorical(action_prob)
            log_probs = dist.log_prob(actions)

            # 计算重要性比率
            ratio = torch.exp(log_probs - log_probs.detach())

            # 计算截断后的损失
            surrogate_loss = ratio * advantages
            clipped_surrogate_loss = torch.clamp(ratio, 1 - self.clip_ratio, 1 + self.clip_ratio) * advantages
            loss = -torch.min(surrogate_loss, clipped_surrogate_loss).mean()

            # 计算价值函数损失
            value_loss = (state_value - targets).pow(2).mean()

            # 总损失
            total_loss = loss + 0.5 * value_loss
            self.optimizer.zero_grad()
            total_loss.backward()
            self.optimizer.step()

    def select_action(self, state):
        state = torch.tensor(state, dtype=torch.float32)
        action_prob, _ = self.actor_critic(state)
        dist = Categorical(action_prob)
        action = dist.sample()
        return action.item()

    def train(self, num_epochs=1000):
        for epoch in range(num_epochs):
            state = self.env.reset()
            done = False
            episode_reward = 0
            states, actions, rewards, next_states, dones = [], [], [], [], []

            # 收集数据,从初始到终止状态
            while not done:
                action = self.select_action(state)
                next_state, reward, done, _ = self.env.step(action)

                states.append(state)
                actions.append(action)
                rewards.append(reward)
                next_states.append(next_state)
                dones.append(done)

                state = next_state
                episode_reward += reward

            # 更新策略
            self.update(states, actions, rewards, next_states, dones)  # 将数据列表输入PPO进行更新
            print(f"Epoch {epoch + 1}, Episode reward: {episode_reward}")

# 创建环境和模型
env = gym.make("CartPole-v1")
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
actor_critic = ActorCritic(state_dim, action_dim)

# 创建PPO实例并训练
ppo = PPO(env, actor_critic)
ppo.train()

PPO每个epoch也就是每一轮训练中,PPO都会收集一个完整的任务过程,即从初始状态到终止状态的一个状态-动作链。然后把收集到的一组states, actions, rewards, next_states, dones送入PPO进行更新。

PPO设置batch:
Batch 的作用
Batch Size:指每次梯度更新时使用的样本数量(即一个 mini-batch 的大小)。
例如,若经验池中有 10,000 条 transitions,设置 batch size=64,则每次从中随机采样 64 条数据计算梯度,共进行约 156 次更新(10,000 / 64)。
Episode 与 Batch 的关系:
一个 episode 包含多个 transitions(状态-动作-奖励序列)。
一个 batch 可能包含来自不同 episode 的 transitions,确保数据的多样性。

赞赏