文章目录
前言
在强化学习(Reinforcement Learning, RL)的广阔天地中,智能体(Agent)通过与环境(Environment)的交互来学习如何做出最优决策。根据智能体是否学习环境的模型,RL算法可以分为两大类:无模型的强化学习(Model-Free RL)和基于模型的强化学习(Model-Based RL)。
- 无模型RL:不尝试理解环境的动态变化,而是直接从与环境交互采样到的数据中学习策略或价值函数。我们熟悉的Q-learning、Sarsa、DQN等都属于这一类。它们通常更通用,但学习效率(即样本复杂度)较低。
- 基于模型RL:尝试构建一个环境模型,这个模型可以预测状态转移的概率和奖励。一旦有了模型,智能体就可以在“脑海中”进行推演和规划,而无需与真实环境进行昂贵的交互。
Dyna-Q算法正是基于模型RL领域一个经典且基础的算法。它巧妙地将无模型的Q-learning与基于模型的规划(Planning)结合起来,旨在大幅提升学习效率。它的核心思想非常直观:智能体不仅从与真实世界的交互中学习,还会利用这些经验来构建一个内部世界模型。然后,它可以在这个模型中进行“想象”或“排练”,生成大量模拟经验,从而加速学习过程。
本文将从理论和代码两个层面,深入剖明Dyna-Q算法。我们将首先理解其背后的工作原理,然后通过一个经典的“悬崖漫步”环境,一步步用Python(基于NumPy)实现整个算法,并最终通过实验结果来直观感受其学习效率的提升。
注:本文作为系列博客的一部分,虽然标题中含有“PyTorch”,但Dyna-Q作为一种表格型方法,使用NumPy实现更为直接清晰。在后续更复杂的算法中,我们将看到PyTorch在处理函数近似、神经网络等方面的强大威力。
完整代码:下载链接
Dyna-Q 算法详解
Dyna-Q算法的框架非常优雅,它在标准的Q-learning流程中加入了“模型学习”和“规划”两个环节。
核心思想
我们可以通过下面这张图来理解Dyna-Q的整个交互和学习循环:
- 真实交互 (Real Interaction):智能体在真实环境中执行一个动作,获得一个反馈(下一个状态、奖励)。
- 直接学习 (Direct RL):利用这次真实的经验
(s, a, r, s')
,通过Q-learning更新一次Q值表。这是从真实世界中学习。 - 模型学习 (Model Learning):同样利用这次经验,更新内部的环境模型。对于一个确定的环境,模型学习非常简单,就是记录下“在状态s下执行动作a,会得到奖励r并转移到状态s’”。
- 规划/模拟 (Planning):这是Dyna-Q的核心。在完成一次真实交互后,算法会额外进行 N 次“规划”步骤。在每一步规划中,它会:
- 从已经访问过的状态-动作对中随机抽取一个。
- 利用模型来预测这个状态-动作对会产生的奖励和下一个状态。
- 用这个模拟出的经验,再次通过Q-learning来更新Q值表。
通过这种方式,每一次与真实环境的交互都被“反复利用”,大大提高了数据的利用率和学习效率。
算法伪代码
下面是Dyna-Q算法的具体流程伪代码,它清晰地展示了上述步骤:
- 初始化 Q(s,a) 和模型 M(s,a)
- 循环 对每个序列 e = 1 → E:
- 得到初始状态 s
- 循环 对每一步 t = 1 → T:
- (1) 用 ε-贪婪策略根据 Q 选择当前状态 s 下的动作 a
- (2) 得到环境反馈的 r, s’
- (3) (直接学习)
Q(s, a) ← Q(s, a) + α[r + γ * max_a' Q(s', a') - Q(s, a)]
- (4) (模型学习)
M(s, a) ← r, s'
- (5) (规划) 循环 n 次:
- 随机选择一个曾经访问过的状态 sm
- 采取一个曾经在状态 sm 下执行过的动作 am
- 通过模型得到
rm, s'm ← M(sm, am)
- (间接学习)
Q(sm, am) ← Q(sm, am) + α[rm + γ * max_a' Q(s'm, a') - Q(sm, am)]
- s ← s’
- 循环结束
- 循环结束
值得注意的是,伪代码中 M(s, a) ← r, s'
的模型更新方式仅适用于确定性环境,即在状态s执行动作a,结果是唯一确定的。本文的实验环境“悬崖漫步”就是这样一个例子。
实验环境:悬崖漫步 (The Environment: Cliff Walking)
为了验证算法,我们需要一个实验环境。悬崖漫步(Cliff Walking)是一个非常经典的网格世界问题。
- 目标:智能体从左下角的起点(S)出发,移动到右下角的目标(G)。
- 规则:
- 每走一步,奖励为 -1。
- 中间有一片区域是悬崖(Cliff)。如果智能体进入悬崖区域,会获得 -100 的巨大负奖励,并被传送回起点S,当前回合结束。
- 到达终点G,回合结束。
- 最优路径:沿着悬崖上方边缘行走,总步数为13步,总回报为-13。
环境代码实现
我们使用Python和NumPy来实现这个环境。
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm
import random
import time
class CliffWalkingEnv:
"""
悬崖漫步环境类
这是一个经典的强化学习环境,智能体需要从起点走到终点,
同时避免掉入悬崖。环境是一个矩形网格世界。
"""
def __init__(self, ncol, nrow):
"""
初始化悬崖漫步环境
参数:
ncol (int): 网格的列数,标量
nrow (int): 网格的行数,标量
"""
self.nrow = nrow # 网格行数,标量
self.ncol = ncol # 网格列数,标量
self.x = 0 # 当前智能体位置的横坐标(列索引),标量,取值范围[0, ncol-1]
self.y = self.nrow - 1 # 当前智能体位置的纵坐标(行索引),标量,取值范围[0, nrow-1]
def step(self, action):
"""
执行一个动作,返回下一个状态、奖励和是否结束的标志
参数:
action (int): 动作编号,标量,取值范围[0, 3]
0: 上移, 1: 下移, 2: 左移, 3: 右移
返回:
next_state (int): 下一个状态编号,标量,取值范围[0, nrow*ncol-1]
reward (int): 奖励值,标量,通常为-1或-100
done (bool): 是否结束当前回合,标量布尔值
"""
# 定义4种动作对应的坐标变化
# change是一个4x2的列表,每行代表一个动作的坐标变化[dx, dy]
# change[0]: 上移 [0, -1], change[1]: 下移 [0, 1]
# change[2]: 左移 [-1, 0], change[3]: 右移 [1, 0]
# 坐标系原点(0,0)定义在左上角
change = [[0, -1], [0, 1], [-1, 0], [1, 0]] # 形状: (4, 2)
# 更新智能体位置,确保不超出边界
# min和max函数确保坐标在有效范围内
self.x = min(self.ncol -