文章目录
- 第一部分 强化学习基础
- 第1章 强化学习概述
- 1.1 强化学习概念
- 1.2 强化学习的环境
- 1.3 强化学习的目标
- 1.4 强化学习的数据
- 第2章 多臂老虎机问题(MAB问题)
- 2.1 问题描述
- 2.1.1 问题定义
- 2.1.2 形式化描述
- 2.1.3 累积懊悔
- 2.1.4 估计期望奖励
- 2.2 解决方法
- 2.2.1 ϵ-贪婪算法
- 2.2.2 上置信界算法
- 2.2.3 汤普森采样算法
- 2.2.4 小结
- 第3章 马尔可夫决策过程
- 3.1 马尔可夫过程
- 3.2 马尔可夫奖励过程
- 3.2.1 定义
- 3.2.2 回报
- 3.2.3 价值函数
- 3.3 马尔可夫决策过程
- 3.4 蒙特卡洛方法
- 3.5 占用度量
- 3.6 最优策略
- 第4章 动态规划算法
- 第5章 时序差分算法
- 第6章 Dyna-Q 算法
- 第二部分 强化学习进阶
- 第7章 DQN 算法
- 第8章 DQN 改进算法
- 第9章 策略梯度算法
- 第10章 Actor-Critic 算法
- 第11章 TRPO 算法
- 第12章 PPO 算法
- 第13章 DDPG 算法
- 第14章 SAC 算法
- 第三部分 强化学习前沿
- 第15章 模仿学习
- 第16章 模型预测控制
- 第17章 基于模型的策略优化
- 第18章 离线强化学习
- 第19章 目标导向的强化学习
- 第20章 多智能体强化学习入门
- 第21章 多智能体强化学习进阶
第一部分 强化学习基础
第1章 强化学习概述
1.1 强化学习概念
- 定义:强化学习是机器通过与环境交互来实现目标的一种计算方法。
机器与环境的一轮交互:机器在环境的一个状态下做一个动作决策,动作执行后,环境发生改变并把相应的奖励反馈和下一轮状态传回机器。 - 交互:
- 感知:智能体在某种程度上感知环境的状态
- 决策:智能体根据当前状态计算出到达目标需要采取的动作的过程;
策略是智能体最终体现的智能形式,是不同智能体之间的核心区别。 - 奖励:环境根据状态和智能体采取的动作,产生一个标量信号作为奖励反馈;
最大化累计奖励期望是智能体提升策略的目标,也是衡量智能体策略好坏的关键指标。
1.2 强化学习的环境
环境的下一刻状态的概率分布由当前状态和智能体的动作共同决定,公式如下:
环境的下一刻状态 ∼ P ( ⋅ ∣ 当前状态,智能体的动作 ) 环境的下一刻状态 \sim P(\cdot | 当前状态,智能体的动作) 环境的下一刻状态∼P(⋅∣当前状态,智能体的动作)
1.3 强化学习的目标
- 整体回报:整个交互过程的每一轮获得的奖励信号的累加,类似一盘游戏最后的得分。
- 价值:回报的期望,也是智能体学习的优化目标。
计算方式:需要对交互过程中每一轮智能体采取动作的概率分布和环境相应的状态转移的概率分布做积分运算。
1.4 强化学习的数据
在强化学习中,数据是智能体与环境交互的过程中得到的。如果智能体不采取某个决策动作,那么该动作对于的数据就无法被观测到,所以当前智能体的训练数据来自之前智能体的决策结果。因此,策略不同,得到的数据分布就不同。
强化学习中有一个关于数据分布的概念,叫作占用度量。归一化的占用度量用于衡量在交互过程中,采样到一个具体的状态动作对的概率分布。
占用度量的一个重要性质:占用度量相同当且仅当策略相同。因此,寻找最优策略对应寻找最优占用度量。
第2章 多臂老虎机问题(MAB问题)
2.1 问题描述
2.1.1 问题定义
有一个用于 K 根拉杆的老虎机,每一根拉杆都对应一个关于奖励的概率分布 R 。每拉动一个拉杆,就可以从该拉杆的奖励概率分布中获得一个奖励 r 。在各拉杆的奖励概率分布未知的情况下,从头开始尝试,目标是在操作 T 次拉杆后获得尽可能高的累积奖励。
由于奖励的概率分布是未知的,因此我们需要在“探索拉杆的获奖概率”和“根据经验选择获奖多的拉杆”中进行权衡。
【通俗易懂:有 K 个机器,你不知道每个机器的奖励概率分布,你只有 T 次机会选择机会,探索机器的奖励概率分布也算在 T 次内,然后尽可能获得最多的奖励。】
【示例:有 1 个 10 臂老虎机,你有 20 次选择机会,你可以花 10 次机会探索前 5 臂,根据获得的奖励,选择奖励期望最大的一个,剩下 10 次都选择最大的那 1 臂。(有可能奖励期望最大在没有探索的 5 臂中,也有可能在前 5 臂中,但是不是你选择的那个)】
2.1.2 形式化描述
多臂老虎机问题可以表示为一个元组 < A , R > <A,\;R> <A,R>,其中:
- A 为动作集合,其中一个动作表示拉动一根拉杆,若多臂老虎机有 K 个拉杆,则动作空间就是集合 { a 1 , a 2 , . . . , a K } \{a_1,a_2,...,a_K\} {a1,a2,...,aK},用 a t ∈ A a_t \in A at∈A 表示任意一个动作;
- R 为概率分布,拉动每一根拉杆的动作 a 都对应一个奖励概率分布 R ( r ∣ a ) R(r|a) R(r∣a),拉动不同拉杆的奖励分布通常是不同。
假设每个时间步只能拉动 1 根拉杆,多臂老虎机的目标为最大化一段时间步 T 内累积的奖励: m a x ∑ t = 1 T r t , r t ∼ R ( ⋅ ∣ a t ) max\sum^T_{t=1}r_t,\;r_t\sim R(\cdot|a_t) max∑t=1Trt,rt∼R(⋅∣at)。其中 a t a_t at 表示在第 t 时间步拉动某一拉杆的动作, r t r_t rt 表示动作 a t a_t at 获得的奖励。
2.1.3 累积懊悔
对于每一个动作 a ,我们定义其期望奖励为 Q ( a ) = E r ∼ R ( ⋅ ∣ a ) [ r ] Q(a)=E_{r\sim R(\cdot | a)}[r] Q(a)=Er∼R(⋅∣a)[r] 。于是,至少存在一根拉杆,它的期望奖励不小于任意一根拉杆,将该最优期望奖励表示为 Q ∗ = max a ∈ A Q ( a ) Q^*=\max_{a\in A}Q(a) Q∗=maxa∈AQ(a) 。
- 懊悔:当前动作 a 与最优拉杆期望奖励的差距,即 R ( a ) = Q ∗ − Q ( a ) R(a)=Q^*-Q(a) R(a)=Q∗−Q(a)
- 累积懊悔:操作 T 次拉杆后累积的懊悔总量,对于一次完整的 T 步决策 { a 1 , a 2 , . . . , a T } \{a_1,a_2,...,a_T\} {a1,a2,...,aT} ,累积懊悔为: σ R = ∑ t = 1 T R ( a t ) \sigma_R=\sum^T_{t=1}R(a_t) σR=∑t=1TR(at) 。
所以求解 MAB 问题等价于最小化累积懊悔。
2.1.4 估计期望奖励
为了知道拉动哪一根拉杆可以获得更高的奖励,所以我们需要估计这根拉杆的期望奖励。
算法流程:
对于 ∀ a ∈ A \forall a\in A ∀a∈A ,初始化计算器 N ( a ) = 0 N(a)=0 N(a)=0 和 期望奖励估值 Q ^ ( a ) = 0 \hat{Q}(a)=0 Q^(a)=0
for t = 1 → T t=1 \rightarrow T t=1→T do
选取某根拉杆,该动作记为 a t a_t at
得到奖励 r t r_t rt
更新计数器: N ( a t ) + = 1 N(a_t)+=1 N(at)+=1
更新期望奖励估值: Q ^ ( a t ) + = 1 N ( a t ) [ r t − Q ^ ( a t ) ] \hat{Q}(a_t)+=\frac{1}{N(a_t)}[r_t-\hat{Q}(a_t)] Q^(at)+=N(at)1[rt−Q^(at)]
示例:
编写一个拉杆为 10 的多臂老虎机。其中每个拉杆的奖励服从伯努利分布,即每次有 p 的概率获得奖励 1 ,有 1-p 的概率获得奖励 0 。【0 表示没有获奖,1 表示获奖。】
在 MAB 目录下新建 BernoulliBandit.py 文件
代码如下:
import numpy as np
import matplotlib.pyplot as plt
class BernoulliBandit:
"""伯努利多臂老虎机,输入 K 表示拉杆个数"""
def __init__(self, K):
self.probs = np.random.uniform(size=K) # 随机生成 K 个 0-1 的数,表示每个拉杆的获奖概率
self.best_idx = np.argmax(self.probs) # 获奖概率最大的拉杆
self.best_prob = self.probs[self.best_idx] # 最大的获奖概率
self.K=K
def step(self, k):
if np.random.rand() < self.probs[k]:
return 1
else:
return 0
np.random.seed(1)
K = 10
bandit = BernoulliBandit(K)
print("随机生成了一个 %d 臂的伯努利多臂老虎机" % K)
print("获奖概率最大的拉杆为 %d 号,其获奖概率为 %.4f" % (bandit.best_idx, bandit.best_prob))
运行结果如下:
D:\RL\MAB\.venv\Scripts\python.exe D:\RL\MAB\BernoulliBandit.py
随机生成了一个10臂的伯努利多臂老虎机
获奖概率最大的拉杆为1 号,其获奖概率为0.7203
进程已结束,退出代码为 0
接下来编写 Solver 基础类来解决 MAB 问题。具体的解决策略由每个继承 Solver 的类来实现。
新建 Solver.py 文件,文件代码如下:
import numpy as np
class Solver:
"""多臂老虎机解决方案抽象类"""
def __init__(self, bandit):
self.bandit = bandit
self.counts = np.zeros(self.bandit.K) # 每根拉杆的尝试次数
self.regret = 0 # 当前步的累积懊悔
self.actions = [] # 记录每一步的动作
self.regrets = [] # 记录每一步的累积懊悔
def update_regret(self, k):
# 记录累积懊悔并保存,k为本次动作选择的拉杆的编号
self.regret += self.bandit.best_prob - self.bandit.probs[k]
self.regrets.append(self.regret)
def run_one_step(self):
# 返回当前动作选择的拉杆,由具体的策略实现
raise NotImplementedError
def run(self, num_steps):
# 运行一定次数,num_steps为总运行次数
for _ in range(num_steps):
k = self.run_one_step()
self.counts[k] += 1
self.actions.append(k)
self.update_regret(k)
可能遇到的问题与解决方案:
- 没有配置 numpy 的模块
解决方案:
点击设置
在项目:xxxx 的 Python 解释器中,点击 + 号
输入 numpy ,选择第一个,点击安装软件包
安装成功后关闭该界面
此时发现项目软件包中多了 numpy,点击确定即可。
- 没有配置 matplotlib 的模块
解决方案:
同上,只是搜索 matplotlib 即可
2.2 解决方法
2.2.1 ϵ-贪婪算法
完全贪婪算法就是在每一刻都采取期望奖励估值最大的动作,这就是纯粹的利用,没有探索。而 ε-贪婪算法则是在其基础上添加了噪声,每次以 1-ε 的概率选择以往经验中期望奖励估值最大的那根拉杆【利用】,以 ε 的概率随机选择一根拉杆【探索】,公式如下:
a t = { a r g max a ∈ A Q ^ ,采样概率: 1 − ϵ 从 A 中随机选择,采样概率: ϵ a_t= \left\{ \begin{array}{ll} arg\; \max\limits_{a\in A} \hat{Q},采样概率:1-\epsilon \\ 从A中随机选择,采样概率:\epsilon \end{array} \right. at={arga∈AmaxQ^,采样概率:1−ϵ从A中随机选择,采样概率:ϵ
随着探索次数不断的增多,对各个动作的奖励估计越来越准确,所以没必要继续花费大力气进行探索。所以我们可以让 ε 随着时间衰减,但是不会到 0 。因为基于有限步观测的完全贪婪算法仍然是一个局部信息的贪婪算法,永远离最优解有一个固定的差距。
项目结构:
新建 EpsilonGreedy.py 文件,文件代码如下:
import numpy as np
from Solver import Solver
class EpsilonGreedy(Solver):
def __init__(self,bandit,epsilon=0.01,init_prob=1.0):
super(EpsilonGreedy,self).__init__(bandit)
self.epsilon = epsilon
self.estimates=np.array([init_prob]*self.bandit.K)
def run_one_step(self):
if np.random.random()<self.epsilon:
k=np.random.randint(0,self.bandit.K)
else:
k=np.argmax(self.estimates)
r=self.bandit.step(k)
self.estimates[k]+=1./(self.counts[k]+1)*(r-self.estimates[k])
return k
在新建 Main.py 文件,文件代码如下:
import numpy as np
from matplotlib import pyplot as plt
from BernoulliBandit import bandit
from EpsilonGreedy import EpsilonGreedy
def plot_results(solvers, solver_names):
"""输出解决方法的累积懊悔变化图"""
for idx, solver in enumerate(solvers):
time_list = range(len(solver.regrets))
plt.plot(time_list, solver.regrets, label=solver_names[idx])
plt.xlabel('Time Step')
plt.ylabel('Cumulative regrets')
plt.title('%d-arm bandit' % solvers[0].bandit.K)
plt.legend()
plt.show()
np.random.seed(1)
epsilon_greedy_solver = EpsilonGreedy(bandit, epsilon=0.01)
epsilon_greedy_solver.run(5000)
print('epsilon-贪婪算法的累积懊悔为:', epsilon_greedy_solver.regret)
plot_results([epsilon_greedy_solver], ["EpsilonGreedy"])
运行 Main.py 文件,结果如下:
随机生成了一个 10 臂的伯努利多臂老虎机
获奖概率最大的拉杆为 1 号,其获奖概率为 0.7203
epsilon-贪婪算法的累积懊悔为: 25.526630933945313
接下来尝试不同 ε 取值的结果:
修改 Main.py 代码如下:
import numpy as np
from matplotlib import pyplot as plt
from BernoulliBandit import bandit
from EpsilonGreedy import EpsilonGreedy
def plot_results(solvers, solver_names):
"""输出解决方法的累积懊悔变化图"""
for idx, solver in enumerate(solvers):
time_list = range(len(solver.regrets))
plt.plot(time_list, solver.regrets, label=solver_names[idx])
plt.xlabel('Time Step')
plt.ylabel('Cumulative regrets')
plt.title('%d-arm bandit' % solvers[0].bandit.K)
plt.legend()
plt.show()
# np.random.seed(1)
# epsilon_greedy_solver = EpsilonGreedy(bandit, epsilon=0.01)
# epsilon_greedy_solver.run(5000)
# print('epsilon-贪婪算法的累积懊悔为:', epsilon_greedy_solver.regret)
# plot_results([epsilon_greedy_solver], ["EpsilonGreedy"])
np.random.seed(0)
epsilons = [1e-4, 0.01, 0.1, 0.25, 0.5]
epsilon_greedy_solver_list = [EpsilonGreedy(bandit, epsilon=e) for e in epsilons]
epsilon_greedy_solver_names = ['epsilon={}'.format(e) for e in epsilons]
for epsilon_greedy_solver in epsilon_greedy_solver_list:
epsilon_greedy_solver.run(5000)
plot_results(epsilon_greedy_solver_list, epsilon_greedy_solver_names)
运行 Main.py 文件,结果如下:
随机种子为 0 的结果很完美,但是选择随机种子为 1 的话,这是实验结果:
但是将时间步扩大为 50000,实验结果又变回来了
接下来尝试 ε 随着时间反比例衰减,公式为: ϵ t = 1 t \epsilon_t=\frac1t ϵt=t1
修改 EpsilonGreedy.py 文件,文件代码如下:
import numpy as np
from Solver import Solver
class EpsilonGreedy(Solver):
def __init__(self, bandit, epsilon=0.01, init_prob=1.0):
super(EpsilonGreedy, self).__init__(bandit)
self.epsilon = epsilon
self.estimates = np.array([init_prob] * self.bandit.K)
def run_one_step(self):
if np.random.random() < self.epsilon:
k = np.random.randint(0, self.bandit.K)
else:
k = np.argmax(self.estimates)
r = self.bandit.step(k)
self.estimates[k] += 1. / (self.counts[k] + 1) * (r - self.estimates[k])
return k
class DecayingEpsilonGreedy(Solver):
def __init__(self, bandit, init_prob=1.0):
super(DecayingEpsilonGreedy, self).__init__(bandit)
self.estimates = np.array([init_prob] * self.bandit.K)
self.total_count = 0
def run_one_step(self):
self.total_count += 1
if np.random.random() < 1 / self.total_count:
k = np.random.randint(0, self.bandit.K)
else:
k = np.argmax(self.estimates)
r = self.bandit.step(k)
self.estimates[k] = 1. / (self.counts[k] + 1) * (r - self.estimates[k])
return k
修改 Main.py 文件,文件代码如下:
import numpy as np
from matplotlib import pyplot as plt
from BernoulliBandit import bandit
from EpsilonGreedy import EpsilonGreedy, DecayingEpsilonGreedy
def plot_results(solvers, solver_names):
"""输出解决方法的累积懊悔变化图"""
for idx, solver in enumerate(solvers):
time_list = range(len(solver.regrets))
plt.plot(time_list, solver.regrets, label=solver_names[idx])
plt.xlabel('Time Step')
plt.ylabel('Cumulative regrets')
plt.title('%d-arm bandit' % solvers[0].bandit.K)
plt.legend()
plt.show()
# np.random.seed(1)
# epsilon_greedy_solver = EpsilonGreedy(bandit, epsilon=0.01)
# epsilon_greedy_solver.run(5000)
# print('epsilon-贪婪算法的累积懊悔为:', epsilon_greedy_solver.regret)
# plot_results([epsilon_greedy_solver], ["EpsilonGreedy"])
# np.random.seed(1)
# epsilons = [1e-4, 0.01, 0.1, 0.25, 0.5]
# epsilon_greedy_solver_list = [EpsilonGreedy(bandit, epsilon=e) for e in epsilons]
# epsilon_greedy_solver_names = ['epsilon={}'.format(e) for e in epsilons]
# for epsilon_greedy_solver in epsilon_greedy_solver_list:
# epsilon_greedy_solver.run(50000)
# plot_results(epsilon_greedy_solver_list, epsilon_greedy_solver_names)
np.random.seed(1)
decaying_epsilon_greedy_solver = DecayingEpsilonGreedy(bandit)
decaying_epsilon_greedy_solver.run(5000)
print('epsilon 反比衰减的贪婪算法的累积懊悔为:', decaying_epsilon_greedy_solver.regret)
plot_results([decaying_epsilon_greedy_solver], ["DecayingEpsilonGreedy"])
运行 Main.py 文件,结果如下:
D:\RL\MAB\.venv\Scripts\python.exe D:\RL\MAB\Main.py
随机生成了一个 10 臂的伯努利多臂老虎机
获奖概率最大的拉杆为 1 号,其获奖概率为 0.7203
epsilon 反比衰减的贪婪算法的累积懊悔为: 10.114334931260183
进程已结束,退出代码为 0
这是扩大时间步至 50000 的结果:
从实验结果可以发现,反比例衰减的 ε-贪婪算法可以使得累积懊悔与时间步的关系变为次线性,这明显优于固定 ε 值的 ε-贪婪算法。
2.2.2 上置信界算法
对于多臂老虎机来说,一根拉杆的探索次数较少,它的不确定性就很高。不确定越高,它具有的探索价值就越大。为此,引入不确定性度量 U(a) ,它随着一个动作被尝试次数的增加而减少。【说白了就是新鲜感】
上置信界(UCB)算法是一种经典的基于不确定性的策略算法,其思想是基于霍夫丁不等式。在霍夫丁不等式中,令 X 1 , X 2 , . . . , X n X_1,X_2,...,X_n X1,X2,...,Xn 为 n 个独立同分布的随机变量,取值范围为 [0,1] ,其经验期望为 x ˉ = 1 n ∑ j = 1 n X j \bar{x}=\frac1n\sum ^n_{j=1}X_j xˉ=n1∑j=1nXj ,则有 P ( E [ X ] ≥ x ˉ + u ) ≤ e − 2 n u 2 P(E[X]\ge\bar{x}+u)\le e^{-2nu^2} P(E[X]≥xˉ+u)≤e−2nu2
将霍夫丁不等式运用到多臂老虎机问题中。 Q ^ \hat{Q} Q^ 代入 x ˉ \bar{x} xˉ,不等式中 u = U ^ ( a t ) u=\hat{U}(a_t) u=U^(at) 代表不确定性度量。给定一个概率 p = e − 2 N ( a t ) U ( a t ) 2 p=e^{-2N(a_t)U(a_t)^2} p=e−2N(at)U(at)2 ,根据上述不等式, Q ( a t ) < Q ^ ( a t ) + U ^ ( a t ) Q(a_t)<\hat{Q}(a_t)+\hat{U}(a_t) Q(at)<Q^(at)+U^(at) 至少以概率 1 − p 1 - p 1−p 成立,当 p 很小时, Q ( a t ) < Q ^ ( a t ) + U ^ ( a t ) Q(a_t)<\hat{Q}(a_t)+\hat{U}(a_t) Q(at)<Q^(at)+U^(at) 就以很大概率成立,所以 Q ^ ( a t ) + U ^ ( a t ) \hat{Q}(a_t)+\hat{U}(a_t) Q^(at)+U^(at) 就是期望奖励上界。
根据 p = e − 2 N ( a t ) U ( a t ) 2 p=e^{-2N(a_t)U(a_t)^2} p=e−2N(at)U(at)2 得知 U ^ ( a t ) = − log p 2 N ( a t ) \hat{U}(a_t)=\sqrt{\frac{-\log p}{2N(a_t)}} U^(at)=2N(at)−logp ,其中 N ( a t ) N(a_t) N(at) 是该拉杆的已经拉动的次数。确定概率 p 就可以计算 U ^ ( a t ) \hat{U}(a_t) U^(at) 。
在实际编程中,令 p = 1 t p=\frac1t p=t1 ;令 U ^ ( a t ) = − log p 2 ( N ( a t ) + 1 ) \hat{U}(a_t)=\sqrt{\frac{-\log p}{2(N(a_t)+1)}} U^(at)=2(N(at)+1)−logp ,以免出现分母为 0 的情况;令 a t = a r g m a x a ∈ A [ Q ^ ( a ) + c ⋅ U ^ ( a ) ] a_t=arg\;max_{a\in A}[\hat{Q}(a)+c\cdot\hat{U}(a)] at=argmaxa∈A[Q^(a)+c⋅U^(a)] ,用系数 c 来控制不确定性比重。
新建 UCB.py 文件,文件代码如下:
import numpy as np
from Solver import Solver
class UCB(Solver):
def __init__(self, bandit, c, init_prob=1.0):
super(UCB, self).__init__(bandit)
self.total_count = 0
self.estimates = np.array([init_prob] * self.bandit.K)
self.c = c
def run_one_step(self):
self.total_count += 1
ucb = self.estimates + self.c * np.sqrt(np.log(self.total_count) / (2 * (self.counts + 1))) # 计算上置信界
k = np.argmax(ucb)
r = self.bandit.step(k)
self.estimates[k] += 1. / (self.counts[k] + 1) * (r - self.estimates[k])
return k
修改 Main.py 文件,文件代码如下:
import numpy as np
from matplotlib import pyplot as plt
from BernoulliBandit import bandit
from EpsilonGreedy import EpsilonGreedy, DecayingEpsilonGreedy
from UCB import UCB
def plot_results(solvers, solver_names):
"""输出解决方法的累积懊悔变化图"""
for idx, solver in enumerate(solvers):
time_list = range(len(solver.regrets))
plt.plot(time_list, solver.regrets, label=solver_names[idx])
plt.xlabel('Time Step')
plt.ylabel('Cumulative regrets')
plt.title('%d-arm bandit' % solvers[0].bandit.K)
plt.legend()
plt.show()
def apply_epsilon_greedy_1():
np.random.seed(1)
epsilon_greedy_solver = EpsilonGreedy(bandit, epsilon=0.01)
epsilon_greedy_solver.run(5000)
print('epsilon-贪婪算法的累积懊悔为:', epsilon_greedy_solver.regret)
plot_results([epsilon_greedy_solver], ["EpsilonGreedy"])
def apply_epsilon_greedy_2():
np.random.seed(1)
epsilons = [1e-4, 0.01, 0.1, 0.25, 0.5]
epsilon_greedy_solver_list = [EpsilonGreedy(bandit, epsilon=e) for e in epsilons]
epsilon_greedy_solver_names = ['epsilon={}'.format(e) for e in epsilons]
for epsilon_greedy_solver in epsilon_greedy_solver_list:
epsilon_greedy_solver.run(50000)
plot_results(epsilon_greedy_solver_list, epsilon_greedy_solver_names)
def apply_decaying_epsilon_greedy():
np.random.seed(1)
decaying_epsilon_greedy_solver = DecayingEpsilonGreedy(bandit)
decaying_epsilon_greedy_solver.run(50000)
print('epsilon 反比衰减的贪婪算法的累积懊悔为:', decaying_epsilon_greedy_solver.regret)
plot_results([decaying_epsilon_greedy_solver], ["DecayingEpsilonGreedy"])
def apply_UCB():
np.random.seed(1)
c = 1 # 不确定性比重
UCB_solver = UCB(bandit, c)
UCB_solver.run(5000)
print('上置信界算法累积懊悔为:', UCB_solver.regret)
plot_results([UCB_solver], ["UCB"])
apply_UCB()
运行 Main.py 文件,结果如下:
D:\RL\MAB\.venv\Scripts\python.exe D:\RL\MAB\Main.py
随机生成了一个 10 臂的伯努利多臂老虎机
获奖概率最大的拉杆为 1 号,其获奖概率为 0.7203
上置信界算法累积懊悔为: 70.45281214197854
进程已结束,退出代码为 0
2.2.3 汤普森采样算法
MAB问题还有一种经典算法——汤普森采样,先假设每个拉杆的奖励服从特定的概率分布,然后根据每个拉杆的期望奖励来进行选择。但是计算所有拉杆的期望奖励的代价比较高,所以该算法使用采样的方式,即根据当前每个动作 a 的奖励概率分布进行一轮采样,得到一组拉杆的奖励样本,再选择样本中奖励最大的动作。【汤普森采样是一种计算所有拉杆的最高奖励概率的蒙特卡洛采样方法】
在实际中,通常用 Beta 分布对当前每个动作的奖励概率分布进行建模。具体来说,若某拉杆被选择了 k 次,其中 m 1 m_1 m1 次奖励为 1, m 2 m_2 m2 次奖励为 0,则该拉杆服从参数为 ( m 1 + 1 , m 2 + 1 ) (m_1+1,m_2+1) (m1+1,m2+1) Beta 分布。
新建 ThompsonSampling.py 文件,文件代码如下:
import numpy as np
from Solver import Solver
class ThompsonSampling(Solver):
def __init__(self, bandit):
super(ThompsonSampling, self).__init__(bandit)
self._a = np.ones(self.bandit.K)
self._b = np.ones(self.bandit.K)
def run_one_step(self):
samples = np.random.beta(self._a, self._b)
k = np.argmax(samples)
r = self.bandit.step(k)
self._a[k] += r
self._b[k] += (1 - r)
return k
修改 Main.py 文件,文件代码如下:
import numpy as np
from matplotlib import pyplot as plt
from BernoulliBandit import bandit
from EpsilonGreedy import EpsilonGreedy, DecayingEpsilonGreedy
from ThompsonSampling import ThompsonSampling
from UCB import UCB
def plot_results(solvers, solver_names):
"""输出解决方法的累积懊悔变化图"""
for idx, solver in enumerate(solvers):
time_list = range(len(solver.regrets))
plt.plot(time_list, solver.regrets, label=solver_names[idx])
plt.xlabel('Time Step')
plt.ylabel('Cumulative regrets')
plt.title('%d-arm bandit' % solvers[0].bandit.K)
plt.legend()
plt.show()
def apply_epsilon_greedy_1():
np.random.seed(1)
epsilon_greedy_solver = EpsilonGreedy(bandit, epsilon=0.01)
epsilon_greedy_solver.run(5000)
print('epsilon-贪婪算法的累积懊悔为:', epsilon_greedy_solver.regret)
plot_results([epsilon_greedy_solver], ["EpsilonGreedy"])
def apply_epsilon_greedy_2():
np.random.seed(1)
epsilons = [1e-4, 0.01, 0.1, 0.25, 0.5]
epsilon_greedy_solver_list = [EpsilonGreedy(bandit, epsilon=e) for e in epsilons]
epsilon_greedy_solver_names = ['epsilon={}'.format(e) for e in epsilons]
for epsilon_greedy_solver in epsilon_greedy_solver_list:
epsilon_greedy_solver.run(50000)
plot_results(epsilon_greedy_solver_list, epsilon_greedy_solver_names)
def apply_decaying_epsilon_greedy():
np.random.seed(1)
decaying_epsilon_greedy_solver = DecayingEpsilonGreedy(bandit)
decaying_epsilon_greedy_solver.run(50000)
print('epsilon 反比衰减的贪婪算法的累积懊悔为:', decaying_epsilon_greedy_solver.regret)
plot_results([decaying_epsilon_greedy_solver], ["DecayingEpsilonGreedy"])
def apply_UCB():
np.random.seed(1)
c = 1 # 不确定性比重
UCB_solver = UCB(bandit, c)
UCB_solver.run(5000)
print('上置信界算法累积懊悔为:', UCB_solver.regret)
plot_results([UCB_solver], ["UCB"])
def apply_thompson_sampling():
np.random.seed(1)
thompson_sampling_solver = ThompsonSampling(bandit)
thompson_sampling_solver.run(5000)
print('汤普森采样算法累积懊悔为:', thompson_sampling_solver.regret)
plot_results([thompson_sampling_solver], ["ThompsonSampling"])
apply_thompson_sampling()
运行 Main.py 文件,结果如下:
D:\RL\MAB\.venv\Scripts\python.exe D:\RL\MAB\Main.py
随机生成了一个 10 臂的伯努利多臂老虎机
获奖概率最大的拉杆为 1 号,其获奖概率为 0.7203
汤普森采样算法累积懊悔为: 57.19161964443925
进程已结束,退出代码为 0
2.2.4 小结
算法 | 累积懊悔与时间步的关系 |
---|---|
ε-贪婪算法 | 线性 |
ε-衰减贪婪算法 | 次线性(对数) |
上置信界算法 | 次线性(对数) |
汤普森采样算法 | 次线性(对数) |
第3章 马尔可夫决策过程
3.1 马尔可夫过程
过程 | 介绍 |
---|---|
随机过程 | 在某时刻 t 的状态 S t S_t St 通常取决于 t 时刻之前的状态。状态 S t + 1 S_{t+1} St+1 的概率表示为: P ( S t + 1 ∣ S 1 , . . . , S t ) P(S_{t+1}|S_1,...,S_t) P(St+1∣S1,...,St) |
马尔可夫过程 | 某时刻 t 的状态只取决于上一个时刻 t-1 的状态。状态 S t + 1 S_{t+1} St+1 的概率表示为: P ( S t + 1 ∣ S t ) = P ( S t + 1 ∣ S 1 , . . . , S t ) P(S_{t+1}|S_t)=P(S_{t+1}|S_1,...,S_t) P(St+1∣St)=P(St+1∣S1,...,St) |
马尔可夫过程也被称为马尔可夫链,通常用元组
<
S
,
P
>
<S,P>
<S,P> 来描述,其中 S 是有限数量的状态集合,P 是状态转移矩阵。假设有 n 个状态,则
S
=
{
s
1
,
s
2
,
.
.
.
,
s
n
}
S=\{s_1,s_2,...,s_n\}
S={s1,s2,...,sn} ,
P
=
[
P
(
s
1
∣
s
1
)
⋯
P
(
s
n
∣
s
1
)
⋮
⋱
⋮
P
(
s
1
∣
s
n
)
⋯
P
(
s
n
∣
s
n
)
]
P=\begin{bmatrix} P(s_1|s_1) & \cdots & P(s_n|s_1) \\ \vdots & \ddots & \vdots\\ P(s_1|s_n) & \cdots & P(s_n|s_n) \end{bmatrix}
P=
P(s1∣s1)⋮P(s1∣sn)⋯⋱⋯P(sn∣s1)⋮P(sn∣sn)
矩阵 P 中第 i 行第 j 列元素
P
(
s
j
∣
s
i
)
=
P
(
S
t
+
1
=
s
j
∣
S
t
=
s
i
)
P(s_j|s_i)=P(S_{t+1}=s_j|S_t=s_i)
P(sj∣si)=P(St+1=sj∣St=si) 表示从状态
s
i
s_i
si 转移到状态
s
j
s_j
sj 的概率,称
P
(
s
′
∣
s
)
P(s'|s)
P(s′∣s) 为状态转移函数。从某个状态出发,到达其他状态的概率和必须为 1 。即状态转移矩阵 P 的每一行和为 1 。
示例:
S = { S i , 1 ≤ i ≤ 6 } 状态转移矩阵 P = [ 0.9 0.1 0 0 0 0 0.5 0 0.5 0 0 0 0 0 0 0.6 0 0.4 0 0 0 0 0.3 0.7 0 0.2 0.3 0.5 0 0 0 0 0 0 0 1 ] S=\{S_i,1\le i \le 6\} \\ \; \\ 状态转移矩阵\;P= \begin{bmatrix} 0.9 & 0.1 & 0 & 0 & 0 & 0 \\ 0.5 & 0 & 0.5 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0.6 & 0 & 0.4 \\ 0 & 0 & 0 & 0 & 0.3 & 0.7 \\ 0 & 0.2 & 0.3 & 0.5 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 \\ \end{bmatrix} S={Si,1≤i≤6}状态转移矩阵P= 0.90.500000.10000.2000.5000.30000.600.500000.300000.40.701
3.2 马尔可夫奖励过程
3.2.1 定义
马尔可夫奖励过程由 < S , P , r , γ > <S,P,r,\gamma> <S,P,r,γ> 构成,其中:
- S 是有限状态的集合
- P 是状态转移矩阵
- r 是奖励函数, r ( s ) r(s) r(s) 是指转移到该状态时可以获得的奖励期望
- γ \gamma γ 是折扣因子,取值范围为: [ 0 , 1 ] [0,1] [0,1] 。引入折扣因子是因为远期利益具有一定的不确定性,有时希望能尽快获得有些奖励,所以需要对远期利益打一些折扣。接近 1 则更关注长期的累积奖励,接近 0 则更关注短期奖励。
3.2.2 回报
回报是指从状态
S
t
S_t
St 开始,一直到终止状态,所有奖励的衰减之和。具体公式如下:
G
t
=
∑
k
=
0
∞
γ
k
R
t
+
k
G_t=\sum^{\infty}_{k=0}\gamma^kR_{t+k}
Gt=k=0∑∞γkRt+k
其中
R
t
R_t
Rt 是指在时刻 t 获得的奖励。
示例:
【状态旁的数字表示进入该状态获得的奖励】
新建项目 MDP,项目结构如下:
在 MDP 目录下新建文件 MRP.py ,文件代码如下:
import numpy as np
np.random.seed(0)
P = [
[0.9, 0.1, 0.0, 0.0, 0.0, 0.0],
[0.5, 0.0, 0.5, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.6, 0.0, 0.4],
[0.0, 0.0, 0.0, 0.0, 0.3, 0.7],
[0.0, 0.2, 0.3, 0.5, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 1.0]
]
P = np.array(P)
rewards = [-1, -2, -2, 10, 1, 0]
gamma = 0.5
def compute_return(start, chain, gamma):
G = 0
for i in reversed(range(start, len(chain))):
G = gamma * G + rewards[chain[i] - 1]
return G
chain = [1, 2, 3, 6]
start = 0
G = compute_return(start, chain, gamma)
print('计算回报为:%s' % G)
运行 MRP.py 文件,运行结果如下:
D:\RL\MDP\.venv\Scripts\python.exe D:\RL\MDP\MRP.py
计算回报为:-2.5
进程已结束,退出代码为 0