强化学习可以来帮助我们进行辅助决策,例如根据当前的游戏场景,自动帮我们"按下"相应按键自个儿玩游戏。例如让AI从头自学开车(基于Deep-Q-Learning强化学习)
个人觉得强化学习的基础稍微需要靠一点数学。
本文章是学习 俞勇《动手学强化学习》这本书的记录。
github代码链接: https://github.com/boyu-ai/Hands-on-RL
课程链接: 伯禹学习平台-动手学强化学习
另外,我觉得还可以看看这个 强化学习第一节(RL基本概念+工具+基本算法 等接下来的一系列教程, 例如后面的DQN算法部分讲得似乎很详细。
看完文末代码实践的例子后,推荐看看这篇我另外一篇博客 强化学习_蒙特卡罗与时序差分(Sarsa/Q-Learning)例子
另外,这本蘑菇书也不错 蘑菇书EasyRL, 想配套的github链接: datawhalechina/easy-rl
可以看看这个, https://github.com/kzl/decision-transformer
1.价值函数是对于 未来累积奖励 的预测,评估给定策略下 状态 的好坏。
2.基于模型 的强化学习和 模型无关 的强化学习的根本区别在于学习过程中有没有环境模型。
1.在策略学习过程中,往往需要进行新策略探索与旧策略的利用, 以实现 尝试不同策略,以进行策略提升/提升对旧策略的评估能力。
2. ϵ-greedy算法 不具有 次线性收敛保证, 衰减ϵ-greedy算法 才具有次线性收敛保证
1.马尔科夫决策过程(Markov decision process, MDP)可以由状态集合、动作集合、状态转移概率、折扣因子、奖励函数构成的五元组表示。
2.马尔科夫决策过程
~~ 数学特性:提供了一套结果部分随机、部分在决策者的控制下的决策过程建模的数学框架
~~ 状态的性质:从历史中捕获所有相关信息 ;
~~ 对于强化学习的意义:形式化地描述了一种强化学习的环境 ;
3.马尔科夫决策过程的当前状态是未来的充分统计量。
4.马尔科夫性质: 当前状态可以完全表征过程。
1.价值迭代 是 贪心 更新法
2.策略迭代中,用 Bellman等式 更新价值函数代价很大。
3.策略迭代 更适合 空间较小 的马尔科夫决策过程, 价值迭代 更适合 空间较大 的马尔科夫决策过程。
4.MDP(马尔科夫决策过程)的目标是选择可以最大化 累积奖励期望 的动作。
5.达成MDP目标的方法是可以对 最优价值函数 和 最优策略 执行迭代更新。
6.价值迭代使用 Bellman等式 对价值函数进行迭代更新
V ( s ) = R ( s ) + m a x α ∈ A γ ∑ s ′ ∈ S P s α ( s ′ ) V ( s ′ ) V(s) = R(s)+max_{\alpha \in A \gamma} \sum_{s'\in S}P_{s\alpha }(s')V(s') V(s)=R(s)+maxα∈Aγs′∈S∑Psα(s′)V(s′)
学习一个MDP模型主要是学习 状态转移概率 和 奖励函数。
2.从经验中直接学习价值函数和策略叫做 模型无关 的强化学习。(注意,这里的模型指的是环境,在强化学习中,负责决策的一般叫智能体agent)
1.蒙特卡洛策略评估使用 经验平均累计 奖励
2.蒙特卡洛是一种 off-line 的方法, 它的更新策略:
V ( S t ) = V ( S t ) + α ( G t ( n ) − V ( S t ) ) V(S_t) = V(S_t)+\alpha (G_t^{(n)}-V(S_t)) V(St)=V(St)+α(Gt(n)−V(St))
1.ϵ贪心策略探索中,以 1-ϵ 的概率选择 贪心策略, ϵ 的概率 随机 选择策略。
2.时序差分学习是 在线策略 算法、可以基于 不完整 的序列进行、具有更低的方差、有偏估计的特点。
3.模型无关的强化学习可以被分为两类,在线策略学习 和 离线策略学习。
1.离线策略蒙特卡洛根据两个策略之间的 重要性比 对累计奖励 G t G_t Gt 加权
1.时序差分 方法直接从经验片段中进行学习、结合了 动态规划 与 蒙特卡洛 方法的思想、能够从不完整的序列中学习、方差小但有偏差。Sarse算法和Q-Learning算法都属于时序差分算法。
2.时序差分的目标是 R t + 1 + γ V ( S t + 1 ) R_{t+1}+\gamma V(S_{t+1}) Rt+1+γV(St+1).
3.蒙特卡洛必须等 片段结束,直到累计奖励已知、只能从完整序列 中学习、只能在 片段化有终止 的环境下工作、具有 高方差 但 无偏差 的特点。
4.时序差分的目标 R t + 1 + γ V ( S t + 1 ) R_{t+1} + \gamma V(S_{t+1}) Rt+1+γV(St+1) 是 V π ( S t ) V^\pi (S_t) Vπ(St) 的有偏估计,而时序差分的真实目标 R t + 1 + γ V π ( S t + 1 ) R_{t+1} + \gamma V^\pi (S_{t+1}) Rt+1+γVπ(St+1) 才是 V π ( S t ) V^\pi (S_t) Vπ(St) 的无偏估计。
5.时序差分算法是一种 on-line方法, 它的更新:
V ( S t ) = V ( S t ) + α ( R t + 1 + γ V ( S t + 1 ) − V ( S t ) ) V(S_t) = V(S_t)+\alpha (R_{t+1}+\gamma V(S_{t+1})-V(S_t)) V(St)=V(St)+α(Rt+1+γV(St+1)−V(St))
1.SARSA是一种 在线策略(on-policy)算法, SARSA算法中的两个“A”都是由当前策略选择的。
2.SARSA算法通常使用 ϵ-贪心 策略进行策略评估和改进。
3. SARSA算法更新:
Q ( S t , A t ) = Q ( S t , A t ) + α [ R t + 1 + γ Q ( S t + 1 , A t + 1 ) − Q ( S t , A t ) ] Q(S_t, A_t) = Q(S_t, A_t) + \alpha[R_{t+1}+\gamma Q(S_{t+1}, A_{t+1})-Q(S_t, A_t)] Q(St,At)=Q(St,At)+α[Rt+1+γQ(St+1,At+1)−Q(St,At)]
1.Q学习是一种 离线策略 学习方法,探索策略与优化策略不是同一个。
2.Q学习 不需要重要性采样 ,采用 多步自助法 时需要。
3.Q学习算法更新:
Q ( S t , A t ) = Q ( S t , A t ) + α [ R t + 1 + γ m a x α Q ( S t + 1 , α ) − Q ( S t , A t ) ] Q(S_t, A_t) = Q(S_t, A_t) + \alpha[R_{t+1}+\gamma max_\alpha Q(S_{t+1}, \alpha)-Q(S_t, A_t)] Q(St,At)=Q(St,At)+α[Rt+1+γmaxαQ(St+1,α)−Q(St,At)]
1.在更新状态值函数近似的参数θ时, θ − > θ + α ( V π ( s ) − V θ ( s ) ) x ( s ) \theta -> \theta+\alpha(V^\pi (s)-V_{\theta}(s))x(s) θ−>θ+α(Vπ(s)−Vθ(s))x(s),其中 α \alpha α表示步长, V π ( s ) − V θ ( s ) V^\pi (s)-V_{\theta}(s) Vπ(s)−Vθ(s)表示预测误差, x ( s ) x(s) x(s)表示特征值。
1.深度强化学习的思想是直接使用 深度神经网络 建立 价值 和 策略 近似函数
2.深度强化学习可以分类为基于 价值 的方法、基于 随机策略 的方法和基于 确定性策略 的方法,其中基于确定性策略的方法包括了 确定性策略梯度(DPG)和 深度确定性策略梯度(DDPG)。
1.利用 神经网络 去预测 当前状态下的Q函数。通过输出层神经元最大的Q函数值选择相应的action。
2.特点:
~~ (1).分为policy network和target network, 让训练更稳定
~~ (2).replay memory, 可以提高数据使用率,减少样本间的相关性,减轻单一序列导致的波动,稳定训练效果。
截图来自: 强化学习第五节(DQN)
代码讲解bilibili
代码来源: desny/snake_and_ladders
引入一些库
import numpy as np
import gym
from gym.spaces import Discrete
from contextlib import contextmanager
import time
@contextmanager
def timer(name):
start = time.time()
yield
end = time.time()
print('{} COST:{}'.format(name, end-start))
Discrete是 gym库中一个类。
Discrete(2) # {0, 1}
Discrete(3, start=-1) # {-1, 0, 1}
class SnakeEnv(gym.Env):
SIZE=100
def __init__(self, ladder_num, dices):
self.ladder_num = ladder_num
self.dices = dices
self.observation_space = Discrete(self.SIZE+1)
self.action_space = Discrete(len(dices))
if ladder_num == 0:
self.ladders = {0:0}
else:
# 处理梯子值,让梯子的数值无重复地反向赋值
ladders = set(np.random.randint(1, self.SIZE, size=self.ladder_num*2))
while len(ladders) < self.ladder_num*2:
ladders.add(np.random.randint(1, self.SIZE))
ladders = list(ladders)
ladders = np.array(ladders)
np.random.shuffle(ladders)
ladders = ladders.reshape((self.ladder_num,2))
re_ladders = list()
for i in ladders:
re_ladders.append([i[1],i[0]])
re_ladders = np.array(re_ladders)
# dict()可以把nx2维数组转化为字典形式
self.ladders = dict(np.append(re_ladders, ladders, axis=0))
print(f'ladders info:{self.ladders} dice ranges:{self.dices}')
self.pos = 1
def reset(self):
self.pos = 1
return self.pos
def step(self, a):
step = np.random.randint(1, self.dices[a]+1)
self.pos += step
if self.pos == 100:
return 100, 100, 1, {}
elif self.pos > 100:
self.pos = 200 - self.pos
if self.pos in self.ladders:
self.pos = self.ladders[self.pos]
return self.pos, -1, 0, {}
def reward(self, s):
if s == 100:
return 100
else:
return -1
# 无渲染
def render(self):
pass
主要是奖励r, 策略pi, 转移概率p
class TableAgent(object):
def __init__(self, env):
self.s_len = env.observation_space.n
self.a_len = env.action_space.n
self.r = [env.reward(s) for s in range(0, self.s_len)]
# 确定性策略
self.pi = np.zeros(self.s_len, dtype=int)
# A x S x S
self.p = np.zeros([self.a_len, self.s_len, self.s_len], dtype=float)
# 函数参数向量化,参数可以传入列表
ladder_move = np.vectorize(lambda x: env.ladders[x] if x in env.ladders else x)
# based-model 初始化表格所有位置的概率p[A,S,S]
for i, dice in enumerate(env.dices):
prob = 1.0 / dice
for src in range(1, 100):
# 因为arange只给一个数字的时候,是从0开始取到end-1,所以在此处+1
step = np.arange(dice) + 1
step += src
step = np.piecewise(step, [step>100, step<=100], [lambda x: 200-x, lambda x: x])
step = ladder_move(step)
for dst in step:
# 在当前位置pos=src的情况下,采取i投掷色子的方式,得到最终位置dst
# 概率直接求和的方式是否合理?
self.p[i, src, dst] += prob
# 因为src最多到99,所以p[:, 100, 100]是0,此处进行填补
self.p[:, 100, 100] = 1
self.value_pi = np.zeros((self.s_len))
self.value_q = np.zeros((self.s_len, self.a_len))
self.gamma = 0.8
def play(self, state):
return self.pi[state]
def eval_game(env, agent):
state = env.reset()
total_reward = 0
state_action = []
while True:
act = agent.play(state)
state_action.append((state,act))
state, reward, done, _ = env.step(act)
total_reward += reward
if done:
break
return total_reward, state_action
算法部分, 这里实现三种迭代算法,分别是策略迭代算法、价值迭算法和泛化迭代算法。
注意:图片来源于网上,仅用于学习,请勿恶意传播。截图来源: https://www.bilibili.com/video/BV1VY411W7Mm/?p=2
class PolicyIteration(object):
dice = [3,6]
def policy_evaluation(self, agent, max_iter=-1):
iteration = 0
while True:
iteration += 1
new_value_pi = agent.value_pi.copy()
# 遍历所有的state 1~100 (s.len=101)
for i in range(1, agent.s_len):
ac = agent.pi[i]
for j in range(0, agent.a_len):
# 选择确定性策略的action
if ac != j:
continue
transition = agent.p[ac, i, :]
value_sa = np.dot(transition, agent.r + agent.gamma * agent.value_pi)
# 放在j循环外部会报错:UnboundLocalError 因为跳过action无法算value_sa的值。
# 未求得value_sa就用该值就会报错(UnboundLocalError)
new_value_pi[i] = value_sa
diff = np.sqrt(np.sum(np.power(agent.value_pi - new_value_pi, 2)))
# 判断是否收敛
if diff < 1e-6:
print('policy evaluation proceed {} iters.'.format(iteration))
break
else:
agent.value_pi = new_value_pi
if iteration == max_iter:
print('policy evaluation proceed {} iters.'.format(iteration))
break
def policy_improvement(self, agent):
new_policy = np.zeros_like(agent.pi)
for i in range(1, agent.s_len):
for j in range(0, agent.a_len):
transition = agent.p[j, i, :]
agent.value_q[i,j] = np.dot(transition, agent.r + agent.gamma * agent.value_pi)
# update policy
max_act = np.argmax(agent.value_q[i,:])
# 选择使value_q最大的action
new_policy[i] = max_act
# 如果没有更新(新策略和旧策略一致),返回False;如果不相等就赋值新策略/优化策略后,返回True
if np.all(np.equal(new_policy, agent.pi)):
return False
else:
agent.pi = new_policy
return True
def policy_iteration(self, agent, max_iter=-1):
iteration = 0
with timer('Timer PolicyIter'):
while True:
iteration += 1
with timer('Timer PolicyEval'):
# 通过迭代求得agent.value_pi 准确估计值函数
self.policy_evaluation(agent, max_iter)
with timer('Timer PolicyImprove'):
# 获得最优策略
ret = self.policy_improvement(agent)
if not ret:
break
print('Iter {} rounds converge'.format(iteration))
运行 策略迭代 的函数(就是上述实现的 策略迭代算法)。
def policy_iteration_demo(env):
agent = TableAgent(env)
pi_algo = PolicyIteration()
pi_algo.policy_iteration(agent)
print('agent.pi={}'.format(agent.pi))
total_reward, state_action = eval_game(env, agent)
print('total_reward={0}, state_action={1}'.format(total_reward, state_action))
注意:图片来源于网上,仅用于学习,请勿恶意传播。截图来源: https://www.bilibili.com/video/BV1VY411W7Mm/?p=2
def value_iteration(agent, max_iter=-1):
iteration = 0
dice = [3,6]
with timer('Timer ValueIter'):
while True:
iteration += 1
new_value_pi = agent.value_pi.copy()
for i in range(1, agent.s_len):
value_sas = []
for j in range(0, agent.a_len):
value_sa = np.dot(agent.p[j,i,:], agent.r + agent.gamma * agent.value_pi)
value_sas.append(value_sa)
new_value_pi[i] = max(value_sas)
diff = np.sqrt(np.sum(np.power(agent.value_pi - new_value_pi, 2)))
if diff < 1e-6:
break
else:
agent.value_pi = new_value_pi
if iteration == max_iter:
break
print('Iter {} rounds converge'.format(iteration))
for i in range(1, agent.s_len):
for j in range(0, agent.a_len):
agent.value_q[i,j] = np.dot(agent.p[j,i,:], agent.r + agent.gamma * agent.value_pi)
max_act = np.argmax(agent.value_q[i,:])
agent.pi[i] = max_act
对比运行上面两种迭代算法(策略迭代和价值迭代)的时间和reward。
# 价值迭代和策略迭代的对比
def policy_vs_value_demo(env):
policy_agent = TableAgent(env)
value_agent = TableAgent(env)
pi_algo = PolicyIteration()
pi_algo.policy_iteration(policy_agent)
print('agent.pi={}'.format(policy_agent.pi))
total_reward, state_action = eval_game(env, policy_agent)
print('total_reward={0}, state_action={1}'.format(total_reward, state_action))
value_iteration(value_agent)
print('agent.pi={}'.format(value_agent.pi))
total_reward, state_action = eval_game(env, value_agent)
print('total_reward={0}, state_action={1}'.format(total_reward, state_action))
泛化迭代 = 价值迭代 + 策略迭代 (先让值函数 V π ( s ) V_{\pi}(s) Vπ(s)最优,再用值函数进行策略改进)
下面代码进行泛化迭代,同时对比前面两种算法。
# 泛化迭代和前两种迭代的对比
def generalized_policy_compare(env):
policy_vs_value_demo(env)
gener_agent = TableAgent(env)
with timer('Timer GeneralizedIter'):
value_iteration(gener_agent, 10)
pi_algo = PolicyIteration()
pi_algo.policy_iteration(gener_agent, 1)
print('agent.pi={}'.format(gener_agent.pi))
total_reward, state_action = eval_game(env, gener_agent)
print('total_reward={0}, state_action={1}'.format(total_reward, state_action))
if __name__ == '__main__':
# 策略迭代 两个demo梯子数不同
# env1 = SnakeEnv(0,[3,6])
env2 = SnakeEnv(10,[3,6])
# policy_iteration_demo(env1)
# policy_iteration_demo(env2)
# 价值迭代和策略迭代的对比
# policy_vs_value_demo(env2)
# 泛化迭代
generalized_policy_compare(env2)
输出:
Output exceeds the size limit. Open the full output data in a text editor
ladders info:{90: 91, 22: 73, 8: 28, 5: 92, 27: 78, 34: 42, 50: 64, 44: 21, 37: 98, 77: 79, 91: 90, 73: 22, 28: 8, 92: 5, 78: 27, 42: 34, 64: 50, 21: 44, 98: 37, 79: 77} dice ranges:[3, 6]
policy evaluation proceed 93 iters.
Timer PolicyEval COST:0.09976077079772949
Timer PolicyImprove COST:0.002475261688232422
policy evaluation proceed 71 iters.
Timer PolicyEval COST:0.0747995376586914
Timer PolicyImprove COST:0.0029921531677246094
policy evaluation proceed 60 iters.
Timer PolicyEval COST:0.06582498550415039
Timer PolicyImprove COST:0.00299072265625
Timer PolicyIter COST:0.24884343147277832
Iter 3 rounds converge
agent.pi=[0 1 0 0 0 0 0 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0
1 1 0 0 0 0 0 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 0 0 0 0]
total_reward=92, state_action=[(1, 1), (2, 0), (4, 0), (6, 0), (7, 0), (28, 0), (29, 0), (32, 1), (98, 0)]
Iter 94 rounds converge
Timer ValueIter COST:0.1904895305633545
agent.pi=[0 1 0 0 0 0 0 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0
1 1 0 0 0 0 0 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 0 0 0 0]
total_reward=88, state_action=[(1, 1), (7, 0), (10, 1), (11, 1), (16, 1), (20, 1), (23, 0), (25, 1), (29, 0), (32, 1), (98, 0), (99, 0), (99, 0)]
Iter 10 rounds converge
Timer ValueIter COST:0.02293848991394043
policy evaluation proceed 1 iters.
...
agent.pi=[0 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 0 0 0 1 1 1 0 0 0
1 1 0 0 0 0 0 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 0 0 0 0]
total_reward=93, state_action=[(1, 1), (3, 0), (92, 0), (95, 1), (99, 0), (99, 0), (99, 0), (99, 0)]