强化学习和 Q-Learning
在你的上方是一枚硬币。这种策略可能仍然会导致代理返回到它已经探索过的位置,但是,正如您从下面的代码中看到的那样,它会导致到所需位置的平均路径非常短(请记住,一开始,当 Q-Table 值都相同时,它对应于随机选择,但随着我们对环境的了解更多,我们更有可能遵循最优路径,同时允许智能体选择未探索的路径偶尔。请注意,我们将 Q-Table 的所有值初始化为相等的值,在我们的示例中为 0.25。现在我们需
🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎
📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】 深度学习【DL】
🖍foreword
✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。
如果你对这个系列感兴趣的话,可以关注订阅哟👋
强化学习涉及三个重要概念:代理、一些状态和每个状态的一组动作。通过在指定状态下执行动作,代理会获得奖励。再次想象电脑游戏超级马里奥。你是马里奥,你在一个游戏关卡中,站在悬崖边上。在你的上方是一枚硬币。你是马里奥,在游戏级别,在特定位置……这就是你的状态。向右移动一步(一个动作)会让你越过边缘,这会给你一个低分数。然而,按下跳跃按钮会让你得分,你会活下来。这是一个积极的结果,应该会给你一个积极的数字分数。
通过使用强化学习和模拟器(游戏),您可以学习如何玩游戏以最大化奖励,即保持活力并获得尽可能多的分数。
先决条件和设置
在本课中,我们将在 Python 中试验一些代码。您应该能够在您的计算机或云中的某个位置运行本课程中的 Jupyter Notebook 代码。
您可以打开课程笔记本并通过本课程进行构建。
注意:如果您从云端打开此代码,您还需要获取rlboard.py文件,该文件用于笔记本代码。将其添加到与笔记本相同的目录中。
介绍
在本课中,我们将探索彼得与狼的世界,灵感来自俄罗斯作曲家谢尔盖·普罗科菲耶夫的音乐童话。我们将使用强化学习让彼得探索他的环境,收集美味的苹果并避免遇到狼。
强化学习(RL) 是一种学习技术,它允许我们通过运行许多实验来学习代理在某些环境中的最佳行为。这种环境中的代理应该有一些目标,由奖励函数定义。
环境
为简单起见,让我们将彼得的世界视为一个大小为width
x的方板height
,如下所示:
此板上的每个单元格都可以是:
- 地面,彼得和其他生物可以在上面行走。
- 水,你显然不能在上面行走。
- 一棵树或草,一个可以休息的地方。
- 一个苹果,代表彼得很乐意找到的东西来养活自己。
- 狼,这是危险的,应该避免。
有一个单独的 Python 模块 ,rlboard.py其中包含用于此环境的代码。因为这段代码对于理解我们的概念并不重要,我们将导入模块并使用它来创建示例板(代码块 1):
from rlboard import *
width, height = 8,8
m = Board(width,height)
m.randomize(seed=13)
m.plot()
此代码应打印与上述类似的环境图片。
行动和政策
在我们的示例中,彼得的目标是找到一个苹果,同时避开狼和其他障碍物。为此,他基本上可以四处走动,直到找到一个苹果。
因此,在任何位置,他都可以选择以下动作之一:上、下、左和右。
我们将这些动作定义为字典,并将它们映射到对应的坐标变化对。例如,向右移动 ( R
) 将对应一对(1,0)
。(代码块 2):
actions = { "U" : (0,-1), "D" : (0,1), "L" : (-1,0), "R" : (1,0) }
action_idx = { a : i for i,a in enumerate(actions.keys()) }
综上所述,本场景的策略和目标如下:
-
我们的代理 (Peter)的策略由所谓的策略定义。策略是在任何给定状态下返回操作的函数。在我们的例子中,问题的状态由棋盘表示,包括玩家的当前位置。
-
强化学习的目标是最终学习一个好的策略,让我们能够有效地解决问题。但是,作为基线,让我们考虑最简单的策略,称为随机游走。
随机游走
让我们首先通过实施随机游走策略来解决我们的问题。使用随机游走,我们将从允许的动作中随机选择下一个动作,直到我们到达苹果(代码块 3)。
-
使用以下代码实现随机游走:
def random_policy(m): return random.choice(list(actions)) def walk(m,policy,start_position=None): n = 0 # number of steps # set initial position if start_position: m.human = start_position else: m.random_start() while True: if m.at() == Board.Cell.apple: return n # success! if m.at() in [Board.Cell.wolf, Board.Cell.water]: return -1 # eaten by wolf or drowned while True: a = actions[policy(m)] new_pos = m.move_pos(m.human,a) if m.is_valid(new_pos) and m.at(new_pos)!=Board.Cell.water: m.move(a) # do the actual move break n+=1 walk(m,random_policy)
调用
walk
应该返回相应路径的长度,该长度可能因一次运行而异。 -
多次运行 walk 实验(比如 100 次),并打印结果统计信息(代码块 4):
def print_statistics(policy): s,w,n = 0,0,0 for _ in range(100): z = walk(m,policy) if z<0: w+=1 else: s += z n += 1 print(f"Average path length = {s/n}, eaten by wolf: {w} times") print_statistics(random_policy)
请注意,路径的平均长度约为 30-40 步,这是相当多的,因为到最近的苹果的平均距离约为 5-6 步。
您还可以看到 Peter 在随机游走过程中的动作:
奖励功能
为了使我们的政策更加明智,我们需要了解哪些举措比其他举措“更好”。为此,我们需要定义我们的目标。
可以根据奖励函数定义目标,该函数将为每个状态返回一些得分值。数字越高,奖励功能越好。(代码块 5)
move_reward = -0.1
goal_reward = 10
end_reward = -10
def reward(m,pos=None):
pos = pos or m.human
if not m.is_valid(pos):
return end_reward
x = m.at(pos)
if x==Board.Cell.water or x == Board.Cell.wolf:
return end_reward
if x==Board.Cell.apple:
return goal_reward
return move_reward
奖励函数的一个有趣之处在于,在大多数情况下,我们只会在游戏结束时获得可观的奖励。这意味着我们的算法应该以某种方式记住最终导致积极奖励的“好”步骤,并增加它们的重要性。同样,应该劝阻所有导致不良结果的举动。
Q-学习
我们将在这里讨论的一种算法称为Q-Learning。在该算法中,策略由称为Q-Table的函数(或数据结构)定义。它记录了给定状态下每个动作的“优点”。
它被称为 Q-Table 是因为通常方便地将其表示为表或多维数组。由于我们的电路板具有尺寸width
x height
,我们可以使用形状为width
x height
x的 numpy 数组来表示 Q-Table len(actions)
:(代码块 6)
Q = np.ones((width,height,len(actions)),dtype=np.float)*1.0/len(actions)
请注意,我们将 Q-Table 的所有值初始化为相等的值,在我们的示例中为 0.25。这对应于“随机游走”策略,因为每个状态中的所有移动都一样好。我们可以将 Q-Table 传递给plot
函数,以便可视化板上的表格m.plot(Q)
:
每个单元格的中心都有一个“箭头”,表示首选的移动方向。由于所有方向都相同,因此会显示一个点。
现在我们需要运行模拟,探索我们的环境,并学习更好的 Q-Table 值分布,这将使我们能够更快地找到通往苹果的路径。
Q-Learning 的本质:贝尔曼方程
一旦我们开始移动,每个动作都会有相应的奖励,即理论上我们可以根据最高的即时奖励选择下一个动作。但是,在大多数州,此举不会实现我们到达苹果的目标,因此我们无法立即决定哪个方向更好。
请记住,重要的不是即时结果,而是最终结果,我们将在模拟结束时获得。
为了解释这种延迟的奖励,我们需要使用动态规划的原理,它允许我们递归地思考问题。
假设我们现在处于状态s,并且我们想要移动到下一个状态s'。通过这样做,我们将获得由奖励函数定义的即时奖励r(s,a)以及一些未来奖励。如果我们假设我们的 Q-Table 正确地反映了每个动作的“吸引力”,那么在状态s'我们将选择一个对应于Q(s',a')最大值的动作a。因此,我们可以在状态s获得的最佳未来奖励将被定义为a' Q(s',a') (这里的最大值是在状态s' 的所有可能的动作a'上计算的)。max
在给定动作a的情况下,这给出了用于计算状态s的 Q 表值的贝尔曼公式:
这里 γ 是所谓的折扣因子,它决定了你应该在多大程度上更喜欢当前的奖励而不是未来的奖励,反之亦然。
学习算法
给定上面的等式,我们现在可以为我们的学习算法编写伪代码:
- 为所有状态和动作初始化 Q-Table Q
- 设置学习率 α ← 1
- 多次重复模拟
- 从随机位置开始
- 重复
- 选择状态s的动作a
- 通过移动到新状态s'来执行操作
- 如果我们遇到游戏结束的情况,或者总奖励太少 - 退出模拟
- 计算新状态下的奖励r
- 根据贝尔曼方程更新 Q-Function:Q(s,a) ← (1-α)Q(s,a)+α(r+γ max a' Q(s',a'))
- s ← s'
- 更新总奖励并减少α。
利用与探索
在上面的算法中,我们没有指定在步骤 2.1 中我们应该如何准确地选择一个动作。如果我们随机选择动作,我们将随机探索环境,我们很可能经常死去,以及探索我们通常不会去的区域。另一种方法是利用我们已经知道的 Q-Table 值,从而在状态s选择最佳动作(具有更高的 Q-Table 值) 。但是,这将阻止我们探索其他状态,并且很可能我们可能找不到最佳解决方案。
因此,最好的方法是在探索和利用之间取得平衡。这可以通过在状态s选择与 Q 表中的值成比例的概率的动作来完成。一开始,当 Q-Table 值都相同时,它对应于随机选择,但随着我们对环境的了解更多,我们更有可能遵循最优路径,同时允许智能体选择未探索的路径偶尔。
Python 实现
我们现在准备实现学习算法。在我们这样做之前,我们还需要一些函数将 Q-Table 中的任意数字转换为相应动作的概率向量。
-
创建一个函数
probs()
:def probs(v,eps=1e-4): v = v-v.min()+eps v = v/v.sum() return v
我们将一些添加
eps
到原始向量中,以避免在初始情况下被除以 0,此时向量的所有分量都相同。
通过 5000 个实验运行他们的学习算法,也称为epochs:(代码块 8)
for epoch in range(5000):
# Pick initial point
m.random_start()
# Start travelling
n=0
cum_reward = 0
while True:
x,y = m.human
v = probs(Q[x,y])
a = random.choices(list(actions),weights=v)[0]
dpos = actions[a]
m.move(dpos,check_correctness=False) # we allow player to move outside the board, which terminates episode
r = reward(m)
cum_reward += r
if r==end_reward or cum_reward < -1000:
lpath.append(n)
break
alpha = np.exp(-n / 10e5)
gamma = 0.5
ai = action_idx[a]
Q[x,y,ai] = (1 - alpha) * Q[x,y,ai] + alpha * (r + gamma * Q[x+dpos[0], y+dpos[1]].max())
n+=1
执行此算法后,应使用定义每个步骤中不同动作的吸引力的值来更新 Q-Table。我们可以尝试通过在每个单元格处绘制一个指向所需运动方向的向量来可视化 Q 表。为简单起见,我们画了一个小圆圈而不是箭头。
检查政策
由于 Q-Table 列出了每个状态下每个动作的“吸引力”,因此很容易使用它来定义我们世界中的有效导航。在最简单的情况下,我们可以选择最高 Q-Table 值对应的动作:(代码块 9)
def qpolicy_strict(m):
x,y = m.human
v = probs(Q[x,y])
a = list(actions)[np.argmax(v)]
return a
walk(m,qpolicy_strict)
如果你多次尝试上面的代码,你可能会注意到它有时会“挂起”,你需要按下笔记本中的 STOP 按钮来中断它。发生这种情况是因为可能存在两个状态在最佳 Q 值方面相互“指向”的情况,在这种情况下,代理最终会无限期地在这些状态之间移动。
🚀挑战
任务 1:修改
walk
函数以将路径的最大长度限制为一定的步数(例如 100),并观察上面的代码不时返回此值。
任务 2:修改
walk
函数,使其不会回到之前已经存在的位置。这将防止walk
循环,但是,代理仍可能最终被“困”在无法逃脱的位置。
导航
更好的导航策略是我们在训练期间使用的,它结合了开发和探索。在这个策略中,我们将以一定的概率选择每个动作,与 Q-Table 中的值成比例。这种策略可能仍然会导致代理返回到它已经探索过的位置,但是,正如您从下面的代码中看到的那样,它会导致到所需位置的平均路径非常短(请记住,print_statistics
运行模拟 100 次) : (代码块 10)
def qpolicy(m):
x,y = m.human
v = probs(Q[x,y])
a = random.choices(list(actions),weights=v)[0]
return a
print_statistics(qpolicy)
运行此代码后,您应该得到比以前小得多的平均路径长度,范围为 3-6。
调查学习过程
正如我们所提到的,学习过程是探索和探索获得的关于问题空间结构的知识之间的平衡。我们已经看到学习的结果(帮助智能体找到通往目标的短路径的能力)有所改善,但观察平均路径长度在学习过程中的表现也很有趣:
学习可以总结为:
-
平均路径长度增加。我们在这里看到的是,起初,平均路径长度增加了。这可能是因为当我们对环境一无所知时,我们很可能会陷入糟糕的状态,水或狼。随着我们了解更多并开始使用这些知识,我们可以探索更长时间的环境,但我们仍然不知道苹果在哪里非常好。
-
随着我们了解更多,路径长度会减少。一旦我们学得足够多,代理就更容易实现目标,并且路径长度开始减少。然而,我们仍然对探索持开放态度,因此我们经常偏离最佳路径,并探索新的选择,使路径比最佳路径更长。
-
长度陡然增加。我们在这张图上还观察到,在某些时候,长度突然增加了。这表明了该过程的随机性质,并且我们可以在某些时候通过用新值覆盖 Q-Table 系数来“破坏”它们。理想情况下,这应该通过降低学习率来最小化(例如,在训练结束时,我们只将 Q-Table 值调整一个很小的值)。
总的来说,重要的是要记住,学习过程的成功和质量在很大程度上取决于参数,例如学习率、学习率衰减和折扣因子。这些通常称为超参数,以将它们与我们在训练期间优化的参数(例如,Q-Table 系数)区分开来。寻找最佳超参数值的过程称为超参数优化,值得单独讨论。
更多推荐
所有评论(0)