现在开始用Q-Learning算法解决冰湖挑战问题。前面说过,冰湖挑战是一个4×4的迷宫,共有16个状态(0~15)。在每个状态中,有上、下、左、右4种选择(也就是智能体的4种可能的移动方向,其中左、下、右、上分别对应0、1、2、3)。因此Q-table就形成了一个16×4的矩阵。

11.4.1 环境的初始化

下面就导入Gym库:

import gym # 导入Gym库

import numpy as np # 导入Num Py库

初始化冰湖挑战的环境:

env = gym.make('Frozen Lake-v0', is_slippery=False) # 生成冰湖挑战的环境

env.reset() # 初始化冰湖挑战的环境

print("状态数:", env.observation_space.n)

print("动作数:", env.action_space.n)

状态数:16

动作数:4

随机走20步:

for _ in range(20): # 随机走20步

env.render() # 生成环境

env.step(env.action_space.sample()) # 随机乱走

env.close() # 关闭冰湖挑战的环境

这一盘试玩中,到了第7步之后,就掉进了冰窟窿,游戏结束,如下所示:

这个问题,我们唯一的目标,就是对该环境创建一个有用的Q-Table,以作为未来冰湖挑战者的行动指南和策略地图。

首先,来初始化Q-Table:

# 初始化Q-Table

Q = np.zeros([env.observation_space.n, env.action_space.n])

print(Q)

输出结果如下:

[[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]]

此时Q-Table是16×4的全0值矩阵。

11.4.2 Q-Learning算法的实现

下面来实现Q-Learning算法:

# 初始化参数

alpha = 0.6 # 学习速率

gamma = 0.75 # 奖励折扣

episodes = 500 # 游戏盘数

r_history = [] # 奖励值的历史信息

j_history = [] # 步数的历史信息

for i in range(episodes):

s = env.reset() # 重置环境

r All = 0

d = False

j = 0

#Q-Learning算法的实现

while j < 99:

j+=1

# 通过Q-Table选择下一个动作, 但是增加随机噪声, 该噪声随着盘数的增加而减小

# 所增加的随机噪声其实就是ε-Greedy策略的实现, 通过它在探索和利用之间平衡

a = np.argmax(Q[s, :] +

np.random.randn(1, env.action_space.n)*(1./(i+1)))

# 智能体执行动作, 并从环境中得到新的状态和奖励

s1, r, d, _ = env.step(a)

# 通过贪心策略更新Q-Table, 选择新状态中的最大Q值

Q[s, a] = Q[s, a] + alpha*(r + gamma*np.max(Q[s1, :]) - Q[s, a])

r All += r

s = s1

if d == True:

break

j_history.append(j)

r_history.append(r All)

print(Q)

其中两个最重要的,就是智能体动作的选择和Q-Table的更新这两段代码。

■智能体动作的选择,采用的是ε-Greedy策略,力图在探索和利用之间平衡。ε值在此处实际上是一个随机值。

■Q-Table的更新,则采用贪心策略,总是选择长期奖励最大Q值来更新Q-Table。

200盘游戏过后,Q-Table如下:

[[0.00896807 0.23730469 0.     0.0110088 ]

[0.00697516 0.     0.     0.    ]

[0.    0.     0.     0.    ]

[0.     0.     0.     0.    ]

[0.03321506  0.31640625  0.     0.00498226 ]

[0.     0.     0.     0.    ]

[0.     0.     0.     0.    ]

[0.     0.     0.     0.    ]

[0.     0.     0.421875  0.    ]

[0.     0.     0.5625   0.    ]

[0.     0.75    0.     0.    ]

[0.     0.     0.     0.    ]

[0.     0.     0.     0.    ]

[0.     0.     0.     0.    ]

[0.     0.     1.     0.    ]

[0.     0.     0.     0.    ]]

Q-Table里面的内容,就是强化学习学到的经验,也就是后续盘中智能体的行动指南。可以看出,越是接近冰湖挑战终点的状态,Q值越大。这是因为γ值的存在(γ=0.75),Q值逆向传播的过程呈现出逐步的衰减。

还可以绘制出游戏的奖惩值随迭代次数而变化的曲线,以及每盘游戏的步数:

import matplotlib.pyplot as plt # 导入Matplotlib库

plt.figure(figsize=(16, 5))

plt.subplot(1, 2, 1)

plt.plot(r_history)

plt.subplot(1, 2, 2)

plt.plot(j_history)

绘制出的曲线如下图所示。

Q-Learing算法中Q-Table的收敛很快

左图告诉我们的信息是,开始的时候,智能体只是盲目地走动,有时走运,可以得到1分,大部分是得到0分,直到Q-Table里面的知识比较丰富了,几乎每盘游戏都得到1分。而右图告诉我们的信息是,智能体没有知识的初期,游戏的步数摇摆不定,要么很快掉进冰窟窿,要么来回地乱走。但是到了大概50盘之后,Q-Table的知识积累完成了,这以后,智能体可以保证每次都得到奖励,而且按照6步的速度迅速地走到飞盘所在地,完成冰湖挑战。

11.4.3 Q-Table的更新过程

下面详细地看一下Q-Table的更新过程。

在开始阶段,智能体将做出大量尝试(甚至可能是10次、100次的尝试),然而,因为没有任何方向感和Q-Table作为指引,智能体的知识空间如同一张白纸。这时智能体处于婴儿态,经常性地坠入冰窟窿,而且得不到系统的任何奖赏。因为在冰湖挑战的环境设定中,掉进冰窟窿只是结束游戏,并没有惩罚分数,因此初始很多盘的尝试并不能带来Q-Table的更新。

如果用env.render方法显示出所有的环境状态,可以看出智能体的探索过程。

第1盘:

第2盘:

第3盘:

直到终于有一天,奇迹发生了,智能体达到了状态14(如第N盘的输出所示),并且随机地选择出了正确的动作,它终于可以得到它梦寐以求的奖励—就好像抓来抓去的婴儿终于抓到了一颗能吃的糖果。

N盘:

第一个Q-Table状态的更新可以称之为奇点:

[[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.0.]

[0.0.0.75 0.]

[0.0.0.0.]]

此时Q[14,2]被更新为0.75(加了γ折扣的奖励值)。以后,有了这个值做引导,Q-Table自身更新的能力被增强不少:

Q[s, a] = Q[s, a] + lr*(r + y*np.max(Q[s1, :]) - Q[s, a])

因为在Q-Table的更新策略中,任何奖励r或者Q[s1,:]的值都会带来之前状态Qsa]的更新。也就是说,当智能体未来有朝一日,从状态10或13踩进状态14,都会带来状态10或13中Q值的更新,智能体也能够根据Q-Table的指引从状态14走到终点。之后开始发生蝴蝶效应,第一个奖励值就像波浪一样,它荡起的涟漪越传越远,从后面的状态向前传递。

咖哥发言

冰湖挑战的环境设定的一些细节如下。

第一,环境初始化时有一个is_slippery开关,默认是Ture值,这代表冰面很滑,此时智能体不能顺利地走到自己想去的方向。这种设定大大增加了随机性和游戏的难度。为了降低难度,我把这个开关,设为False,以演示Q-Table的正常更新过程。同学们可以尝试设置is_slippery=Ture,重新进行冰湖挑战。

第二,冰湖挑战设定所有的状态Q值的最大值为1。也就是说,当累积得到的奖励分大于1时,就不继续增加该状态Qsa]的Q值了。