蒙特卡洛算法需要使用完整的片段进行计算,这在有些问题中是不现实的,尤其是对于没有终止状态的问题。时序差分算法对此进行了改进
蒙特卡洛控制和时序差分学习有什么区别?
四、时序差分算法(Temporal Difference Learning, TD 学习)
4.1 时序差分(0)
4.2 Sarsa算法
4.3 Q学习(Q-learning)
4.4 Sarsa和Q-learning有什么区别?
4.5 示例代码
公共类:discrete.py plotting.py
离散环境的类 discrete.py,它继承自 gym 库的 Env 类,用于创建和管理强化学习的环境。它的主要功能是:
定义了环境的基本属性,如状态的数量,动作的数量,状态转移的概率,初始状态的分布,动作的空间,状态的空间等。
定义了环境的基本方法,如设置随机数种子,重置环境,执行一个动作,返回下一个状态,奖励,是否结束和附加信息等。
使用了 numpy 库,gym 库和 categorical_sample 函数来进行数值计算,环境管理和概率采样等操作。
# 导入 numpy 库,用于进行数值计算
import numpy as np
# 导入 gym 库,用于创建和管理强化学习的环境
from gym import Env, spaces
# 导入 gym 库的 seeding 模块,用于设置随机数种子
from gym.utils import seeding
# 导入 gym 库的 toy_text 模块的 categorical_sample 函数,用于从一个概率分布中采样一个类别
from gym.envs.toy_text.utils import categorical_sample
# 定义一个离散环境的类,继承自 gym 库的 Env 类
class DiscreteEnv(Env):
"""
Has the following members
- nS: number of states # 状态的数量
- nA: number of actions # 动作的数量
- P: transitions (*) # 状态转移的概率
- isd: initial state distribution (**) # 初始状态的分布
(*) dictionary of lists, where
P[s][a] == [(probability, nextstate, reward, done), ...] # P[s][a] 是一个列表,表示在状态 s 下采取动作 a 后,可能的下一个状态,奖励和是否结束的概率
(**) list or array of length nS # isd 是一个长度为 nS 的列表或数组,表示每个状态作为初始状态的概率
"""
# 定义初始化方法,接受四个参数:状态的数量,动作的数量,状态转移的概率,初始状态的分布
def __init__(self, nS, nA, P, isd):
self.P = P # 将状态转移的概率赋值给 self.P
self.isd = isd # 将初始状态的分布赋值给 self.isd
self.lastaction = None # for rendering # 定义一个属性,用于记录上一次的动作,用于渲染
self.nS = nS # 将状态的数量赋值给 self.nS
self.nA = nA # 将动作的数量赋值给 self.nA
# 定义一个属性,表示动作的空间,是一个离散的空间,取值范围是 [0, nA-1]
self.action_space = spaces.Discrete(self.nA)
# 定义一个属性,表示状态的空间,是一个离散的空间,取值范围是 [0, nS-1]
self.observation_space = spaces.Discrete(self.nS)
self.seed() # 调用 seed 方法,设置随机数种子
# 从初始状态的分布中采样一个状态,赋值给 self.s
self.s = categorical_sample(self.isd, self.np_random)
# 定义一个方法,用于设置随机数种子,接受一个参数:种子
def seed(self, seed=None):
# 调用 seeding 模块的 np_random 函数,根据种子生成一个随机数生成器,赋值给 self.np_random,并返回种子
self.np_random, seed = seeding.np_random(seed)
return [seed]
# 定义一个方法,用于重置环境,返回初始状态
def reset(self):
# 从初始状态的分布中采样一个状态,赋值给 self.s
self.s = categorical_sample(self.isd, self.np_random)
self.lastaction = None # 将上一次的动作设为 None
return int(self.s) # 返回初始状态,转换为整数类型
# 定义一个方法,用于执行一个动作,返回下一个状态,奖励,是否结束和附加信息
def step(self, a):
# 根据当前状态和动作,从状态转移的概率中获取可能的转移列表,赋值给 transitions
transitions = self.P[self.s][a]
# 从转移列表中,根据转移的概率,采样一个转移的索引,赋值给 i
i = categorical_sample([t[0] for t in transitions], self.np_random)
# 根据转移的索引,获取转移的概率,下一个状态,奖励和是否结束,赋值给 p, s, r, d
p, s, r, d = transitions[i]
self.s = s # 将下一个状态赋值给 self.s
self.lastaction = a # 将当前动作赋值给 self.lastaction
# 返回下一个状态,奖励,是否结束和附加信息,其中附加信息是一个字典,包含转移的概率,下一个状态转换为整数类型
return (int(s), r, d, {"prob": p})
用于绘制一些问题中的价值函数的图形的函数 plotting.py 。价值函数表示在不同的状态下,采取最优策略能够获得的期望回报。这些代码使用了matplotlib库,numpy库,pandas库和namedtuple来进行数据处理和图形绘制。代码中定义了三个函数,分别是:
plot_cost_to_go_mountain_car
:这个函数用于绘制山地车问题的价值函数,山地车问题是一个连续状态空间的强化学习问题,目标是让一辆车在两座山之间来回移动,最终到达右边的山顶。这个函数接受一个环境对象,一个估计器对象和一个网格数作为参数,然后生成一个三维的曲面图,显示在不同的位置和速度下,采取最优动作的成本(负的价值)。plot_value_function
:这个函数用于绘制二十一点游戏的价值函数,二十一点游戏是一个离散状态空间的强化学习问题,目标是让玩家的牌的总和尽可能接近21,但不超过21,同时要比庄家的牌的总和大。这个函数接受一个价值函数字典和一个标题作为参数,然后分别绘制两个三维的曲面图,显示在不同的玩家总和和庄家显示牌下,有可用的Ace和没有可用的Ace的情况下的价值。plot_episode_stats
:这个函数用于绘制每个回合的统计信息,包括回合的长度,回合的奖励,回合的时间步数和回合的编号。这个函数接受一个命名元组,一个平滑窗口和一个是否显示图形的标志作为参数,然后分别绘制三个二维的折线图,显示回合的长度,回合的奖励和回合的时间步数随回合的编号的变化。这个函数返回三个图形对象。
# 导入matplotlib库,用于绘制图形
import matplotlib
# 导入numpy库,用于进行数值计算
import numpy as np
# 导入pandas库,用于进行数据分析
import pandas as pd
# 导入namedtuple,用于创建命名元组
from collections import namedtuple
# 导入pyplot模块,用于绘制二维图形
from matplotlib import pyplot as plt
# 导入Axes3D模块,用于绘制三维图形
from mpl_toolkits.mplot3d import Axes3D
# 创建一个命名元组,用于存储每个回合的长度和奖励
EpisodeStats = namedtuple("Stats",["episode_lengths", "episode_rewards"])
# 定义一个函数,用于绘制山地车问题的价值函数
def plot_cost_to_go_mountain_car(env, estimator, num_tiles=20):
# 生成一个等差数列,表示状态空间中的位置范围
x = np.linspace(env.observation_space.low[0], env.observation_space.high[0], num=num_tiles)
# 生成一个等差数列,表示状态空间中的速度范围
y = np.linspace(env.observation_space.low[1], env.observation_space.high[1], num=num_tiles)
# 生成一个网格,表示状态空间中的所有可能组合
X, Y = np.meshgrid(x, y)
# 对每个状态,计算估计器预测的最大动作价值,并取负数,表示成本
Z = np.apply_along_axis(lambda _: -np.max(estimator.predict(_)), 2, np.dstack([X, Y]))
# 创建一个图形对象,设置大小为10*5
fig = plt.figure(figsize=(10, 5))
# 在图形对象上添加一个子图,设置为三维投影
ax = fig.add_subplot(111, projection='3d')
# 在子图上绘制一个曲面,表示价值函数
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1,
cmap=matplotlib.cm.coolwarm, vmin=-1.0, vmax=1.0)
# 设置子图的x轴标签为位置
ax.set_xlabel('Position')
# 设置子图的y轴标签为速度
ax.set_ylabel('Velocity')
# 设置子图的z轴标签为价值
ax.set_zlabel('Value')
# 设置子图的标题为山地车问题的成本函数
ax.set_title("Mountain \"Cost To Go\" Function")
# 在图形对象上添加一个颜色条,表示价值的范围
fig.colorbar(surf)
# 显示图形
plt.show()
# 定义一个函数,用于绘制价值函数的曲面图
def plot_value_function(V, title="Value Function"):
"""
Plots the value function as a surface plot.
"""
# 找到价值函数中的最