1 前言
- 玩个游戏(滑动杆CartPole),看下图。我们的目标是,我们通过向左或向右滑动滑块来保证杆子始终在滑块的上方。下图我设置随机滑动滑块,展现出来就是这样一个效果。
- 经过该篇文章学习后,你能通过强化学习操控滑块,达到如下的一个效果。Amazing?Let’s make it .
回到正题 - 首先需要安装Gym,即游戏仿真平台。使用pip install gym,即可完成安装。需要注意的是,如果使用google colab等在线编程平台就不能成功弹出可视化图像。建议本地运行。
- CartPole游戏比较有名,大多数强化学习入门教材都会使用这个例子。
- 文章末尾会给出完整源代码,建议先跑一下,看看效果,再进入如下的学习。
2 如何做
教学大纲:
- 介绍gym如何使用
- 强化学习之QLearning介绍
- 使用QLearning来学习CartPole游戏,得到Q表
- 运用Q表进行游戏中的决策
2.1 gym使用
import gym
import matplotlib.pyplot as plt
from PIL import Image
env = gym.make('CartPole-v1')
env.reset()
image_append = []
for _ in range(100):
# (400, 600, 3)
# 如下代码只是为了生成git动图,只有env.render才是关键代码
current_img = env.render(mode="rgb_array")
current_img = Image.fromarray(current_img)
image_append.append(current_img)
env.step(env.action_space.sample())
env.close()
image_append[0].save('./sample_out.gif',save_all = True,append_images = image_append)
- 如上代码, gym.make:来生成对应的游戏环境。env.reset:将游戏环境初始化;env.render:将此可游戏环境画出;env.step:表示游戏画面更新,其参数为0时,表示滑块向左滑动,参数为1时,滑块向右滑动;env.action_space.sample:随机返回0或1。
- 看到这里不难发现,该游戏的行为(action)只有向左或向右移动滑块。也就是说,想要玩好这个游戏。我们需要有一个策略决定如何在不同的环境下,来向左或向右移动滑块。
- 什么是不同的环境?在该游戏的背景下,滑块的位置,滑块的速度,滑块的杆的角度以及滑块杆的角速度,这四点确定,是不是我们就可以说环境确定下来了。OK,先不管这么多,看结果输出。
Output:
- 对了,还有一个很重要的没讲。如下代码env.step将返回四个值。
obs, rew, done, info = env.step(env.action_space.sample())
- obs是一个数组,包含滑块的位置,滑块的速度,滑块的杆的角度以及滑块杆的角速度。
- rew即reward,强化学习里面的概念。如果游戏始终进行,其值就为1;game over 该值为0。
- done:要不怎么说这是游戏仿真器呢,如果滑块上的杆不在滑块上方,done为True,否则为False。当done为True是,也就意味着游戏结束。看如下代码,你的感触会更深。
- info:没什么用。
import gym
import time
import matplotlib.pyplot as plt
from PIL import Image
env = gym.make('CartPole-v1')
env.reset()
done = False
while not done:
env.render()
_,_,done,_ = env.step(env.action_space.sample())
time.sleep(0.1)
env.close()
Output:
2.2 QLearning介绍
就这个游戏进行展开:
问题定义:
玩好这个游戏,我们可以换一种具体的表述,即在环境观察值 observation确定的情况下,如何对行为 action进行预测。更具体点就是,给定滑块的位置,滑块的速度,滑块的杆的角度以及滑块杆的角速度,我们如何判断下一步的行为,即滑块向左还是向右滑动。emmmm,这样是不是太断然了?更合理的定义应该是:给定环境观测值以及相应地行为标识,我们输出对应行为的概率。
( ( o b s e r v a t i o n ) , a c t i o n ) → ( p r o b a b i l i t y ) ((observation),action) \rightarrow (probability) ((observation),action)→(probability)
问题分析:
我们自然想到使用字典来维护这个映射。
- 字典的keys由环境观察值的四元组+行为的标识组成(行为值为0向左,1向右)构成。
- values由一个数值构成,代表进行对应行为概率(置信度)。
- 如此,只要我们成功维护该字典,玩游戏时,就能通过字典的key(环境观察值及行为标识),找到其value,与环境观察值相同的其他行为标识value值进行比较,进而选择可能性更大的走法。
- Qlearning中的Q,其实就是我们这里的字典
字典如何维护?
刚玩游戏时,字典Q是空的,代表我们没有任何经验。引入一个概念,利用与探索(即经验与试错)。在让模型玩游戏的过程中,以一定的概率( ϵ \epsilon ϵ)来进行利用与探索。利用:代表使用当前已有的Q字典进行决策,按照决策行动后,还没有game over的话,那就给这个决策奖励,即相应的行为probability增加。探索:代表随机进行决策,同样的,按照决策行动后,还没有game over的话,那就也给这个决策奖励。
- Tips:利用过程刚开始为字典空,那就意味着向左向右的概率相同,也就相当于探索。
- 那如何通过具体的奖励(reward)来更新行动的概率值呢?QLearning这样做,如下。
- QLearning公式: Q ( s , a ) ← Q ( s , a ) + α ( r + γ ⋅ m a x a ′ Q ( s ′ , a ′ ) − Q ( s , a ) ) Q(s,a) ← Q(s,a)+α \left( r+γ \cdot max_{a'}Q(s',a')-Q(s,a)\right) Q(s,a)←Q(s,a)+α(r+γ⋅maxa′Q(s′,a′)−Q(s,a))
- 其中 s s s 就是当前环境观察值, a a a 就是行为标识, α \alpha α 就是学习率, r r r 就是reward奖励, s ′ 与 a ′ s'与a' s′与a′ 就是决策后的环境与行为, γ \gamma γ 是对未来 reward 的衰减值。为什么如上公式有效,全网有很多理论证明,我这里就不细说了。
- 不断玩游戏,Q字典就不断得到完善。如上表示可能还不是很清晰,具体细节可看如下代码,也很简单。
2.3 生成Q表
其实讲到现在还有个问题没有解决。你想啊,我们的环境观察值虽然是四元组,但是其中每一个值都是连续的。这不就代表游戏中有无数多种环境吗?那这个字典还怎么维护的了。我们需要将其离散化。使用如下函数即可。
def discretize(x):
return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))
训练生成Q表:
import gym
import matplotlib.pyplot as plt
import numpy as np
import random
import warnings
from PIL import Image
warnings.filterwarnings('ignore')
env = gym.make("CartPole-v1")
def discretize(x):
return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))
Q = {}
# 0代表向左,1戴代表向右
actions = (0,1)
# 设置QLearning需要的超参数
alpha = 0.3
gamma = 0.9
epsilon = 0.90
# 获取当前环境下的value值,即向左向右的概率值;如果不存在设置为0
def qvalues(state):
return [Q.get((state,a),0) for a in actions]
# 将向左,向右的可能性压缩到(0,1),且相加为1
def probs(v,eps=1e-4):
v = v-v.min()+eps
v = v/v.sum()
return v
for epoch in range(10000):
obs = env.reset()
done = False
# == do the simulation ==
while not done:
s = discretize(obs)
if random.random()<epsilon:
# exploitation - chose the action according to Q-Table probabilities
v = probs(np.array(qvalues(s)))
a = random.choices(actions,weights=v)[0]
else:
# exploration - randomly chose the action
a = np.random.randint(env.action_space.n)
obs, rew, done, info = env.step(a)
ns = discretize(obs)
Q[(s,a)] = Q.get((s,a),0) + alpha * (rew + gamma * max(qvalues(ns))-Q.get((s,a),0))
- 如上代码进行了1万次的游戏。Q表已经维护的比较好了。接下来我们就要利用Q表信息,完成可视化。
2.4 Q表决策
obs = env.reset()
done = False
image_append = []
while not done:
s = discretize(obs)
# 以下代码主要用来生成gif图像
current_img = env.render(mode="rgb_array")
current_img = Image.fromarray(current_img)
image_append.append(current_img)
v = probs(np.array(qvalues(s)))
a = random.choices(actions,weights=v)[0]
obs,_,done,_ = env.step(a)
image_append[0].save('./out.gif',save_all = True,append_images = image_append)
env.close()
Output:
3 源代码(可直接运行)
import gym
import matplotlib.pyplot as plt
import numpy as np
import random
import warnings
from PIL import Image
warnings.filterwarnings('ignore')
env = gym.make("CartPole-v1")
def discretize(x):
return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))
Q = {}
# 0代表向左,1戴代表向右
actions = (0,1)
# 设置QLearning需要的超参数
alpha = 0.3
gamma = 0.9
epsilon = 0.90
# 获取当前环境下的value值,即向左向右的概率值;如果不存在设置为0
def qvalues(state):
return [Q.get((state,a),0) for a in actions]
# 将向左,向右的可能性压缩到(0,1),且相加为1
def probs(v,eps=1e-4):
v = v-v.min()+eps
v = v/v.sum()
return v
for epoch in range(10000):
obs = env.reset()
done = False
# == do the simulation ==
while not done:
s = discretize(obs)
if random.random()<epsilon:
# exploitation - chose the action according to Q-Table probabilities
v = probs(np.array(qvalues(s)))
a = random.choices(actions,weights=v)[0]
else:
# exploration - randomly chose the action
a = np.random.randint(env.action_space.n)
obs, rew, done, info = env.step(a)
ns = discretize(obs)
Q[(s,a)] = Q.get((s,a),0) + alpha * (rew + gamma * max(qvalues(ns))-Q.get((s,a),0))
obs = env.reset()
done = False
image_append = []
while not done:
s = discretize(obs)
# 以下代码主要用来生成gif图像
current_img = env.render(mode="rgb_array")
current_img = Image.fromarray(current_img)
image_append.append(current_img)
v = probs(np.array(qvalues(s)))
a = random.choices(actions,weights=v)[0]
obs,_,done,_ = env.step(a)
image_append[0].save('./out1.gif',save_all = True,append_images = image_append)
env.close()
4 总结
-
关于QLearning中的超参数设置有讲究。不同的超参数得到的实验结果相差很大,如果是做一个陌生的课题,调参是很有必要的。
-
QLearning算法,像这种环境简单,决策也简单的模型,尚可驾驭。但象棋,围棋甚至王者荣耀这种决策游戏,环境状态可以多到比天上的星星还多,恐怕计算机内存再大也无法存储下Q表。这时候神经网络就来了,这两者的结合就是天造地设。神经网络能够识别模式,也就是说环境状态虽然多,但只要让网络学习到了环境状态到决策的映射,我们无需维护Q表,使用网络直接就能生成Q值。
-
想了解更多?正在码字途中(手动狗头)。
5 附录
参考资料:
- https://mofanpy.com/tutorials/machine-learning/reinforcement-learning/intro-q-learning/
- https://github.com/microsoft/ML-For-Beginners/tree/main/8-Reinforcement/1-QLearning