Python 深度学习(三)(1)

简介: Python 深度学习(三)

第八章:深度学习与电脑游戏

上一章关注的是解决棋盘游戏问题。在本章中,我们将研究更复杂的问题,即训练人工智能玩电脑游戏。与棋盘游戏不同,游戏规则事先是不知道的。人工智能不能预测它采取行动会发生什么。它不能模拟一系列按钮按下对游戏状态的影响,以查看哪些获得最高分。它必须纯粹通过观察、玩耍和实验来学习游戏的规则和约束。

在本章中,我们将涵盖以下主题:

  • Q 学习
  • 经验重演
  • 演员-评论家
  • 基于模型的方法

游戏的监督学习方法

强化学习中的挑战在于找到我们网络的良好目标。我们在上一章中就一种方法,策略梯度。如果我们能够将强化学习任务转化为监督任务问题,那么问题就会变得容易得多。因此,如果我们的目标是构建一个玩电脑游戏的人工智能代理,我们可能会尝试观察人类的游戏方式,并让我们的代理从他们那里学习。我们可以录制一个专家玩家玩游戏的视频,同时跟踪屏幕图像和玩家按下的按钮。

正如我们在计算机视觉章节中所看到的,深度神经网络可以从图像中识别模式,因此我们可以训练一个以屏幕为输入,以每一帧中用户按下的按钮为目标的网络。这类似于上一章中 AlphaGo 的预训练。这种方法在一系列复杂的 3D 游戏上进行了尝试,例如《超级大乱斗》和《马里奥网球》。卷积网络用于其图像识别质量,而 LTSM 用于处理帧之间的长期依赖关系。使用这种方法,一个针对《超级大乱斗》训练过的网络可以在最困难的难度设置下击败游戏内 AI:

从人类身上学习是一个很好的起点,但我们进行强化学习的目标应该是实现超越人类的表现。此外,用这种方式训练的智能体将永远受到其能力的限制,而我们真正想要的是能够真正自我学习的智能体。在本章的其余部分,我们将介绍一些旨在超越人类水平的方法。

应用遗传算法玩游戏

长期以来,AI 在视频游戏环境中的最佳结果和大部分研究都围绕着遗传算法展开。这种方法涉及创建一组模块,这些模块接受参数以控制 AI 的行为。参数值的范围由一组基因的选择来确定。然后将创建一组代理,使用这些基因的不同组合,然后在游戏上运行。最成功的一组代理基因将被选择,然后将使用成功代理的基因的组合创建一个新的代理一代。这些代理再次在游戏上运行,直到达到停止条件,通常是达到最大迭代次数或游戏中的性能水平。偶尔,在创建新一代时,一些基因可以发生突变以创建新基因。一个很好的例子是 MarI/O,这是一个使用神经网络遗传演化学习玩经典的 SNES 游戏 超级马里奥世界 的 AI:


图 1:使用遗传算法学习马里奥(https://www.youtube.com/watch?v=qv6UVOQ0F44

这些方法的一个很大的缺点是,它们需要大量的时间和计算能力来模拟所有参数的变化。每一代的每个成员都必须运行整个游戏直到终止点。该技术也没有利用游戏中人类可以使用的丰富信息。每当收到奖励或惩罚时,都会有关于状态和采取的行动的上下文信息,但遗传算法只使用运行的最终结果来确定适应度。它们不是那么多的学习而是试错。近年来,已经找到了更好的技术,利用反向传播来允许代理在玩耍时真正学习。与上一章一样,这一章也非常依赖代码;如果您不想花时间从页面上复制文本,您可以在 GitHub 仓库中找到所有代码:github.com/DanielSlater/PythonDeepLearningSamples

Q 学习

想象一下,我们有一个代理将在一个迷宫环境中移动,其中某处有一个奖励。我们的任务是尽快找到到达奖励的最佳路径。为了帮助我们思考这个问题,让我们从一个非常简单的迷宫环境开始:

图 2:一个简单的迷宫,代理可以沿着线移动从一个状态到另一个状态。如果代理到达状态 D,将获得奖励 4。

在所示的迷宫中,代理可以在任何节点之间来回移动,通过沿着线移动。代理所在的节点是它的状态;沿着线移动到不同的节点是一种行动。如果代理达到状态D的目标,就会得到4的奖励。我们希望从任何起始节点找到迷宫的最佳路径。

让我们思考一下这个问题。如果沿着一条直线移动将我们置于状态D,那么这将永远是我们想要采取的路径,因为这将在下一个时间步给我们4的奖励。然后退回一步,我们知道如果我们到达状态C,它直接通往D,我们可以获得那个 4 的奖励。

要选择最佳行动,我们需要一个能够为行动让我们置于的状态提供预期奖励的函数。在强化学习中,这个函数的名称是 Q 函数:

state, action => expected reward

如前所述,到达状态D的奖励是4。那么到达状态C的奖励应该是多少呢?从状态C,可以采取一个行动转移到状态D并获得4的奖励,所以也许我们可以将C的奖励设为4。但是如果我们在所示的迷宫中采取一系列随机行动,我们最终总是会到达状态D,这意味着每个行动都会获得相同的奖励,因为从任何状态,我们最终都会到达状态D4奖励。

我们希望我们的预期奖励考虑到获得未来奖励需要的行动数。我们希望这种期望能够产生这样的效果,即当处于状态A时,我们直接转移到状态C而不是通过状态B,这将导致到达D需要更长的时间。所需的是一个考虑到未来奖励的方程,但与更早获得的奖励相比打折。

另一种思考这个问题的方式是考虑人们对待金钱的行为,这是对人们对待奖励的行为的良好代理。如果在一周后和十周后选择收到 1 美元的选择,人们通常会选择尽快收到 1 美元。生活在不确定的环境中,我们对以较少不确定性获得的奖励更加重视。我们推迟获得奖励的每一刻都是世界不确定性可能消除我们奖励的更多时间。

为了将这个应用于我们的代理,我们将使用用于评估奖励的时间差方程;它如下所示:


在这个方程中,V 是采取一系列动作的奖励,r [t] 是在这个序列中在时间 t 收到的奖励,g 是一个常数,其中 0 < g < 1,这意味着将来的奖励不如更早获得的奖励有价值;这通常被称为折扣因子。如果我们回到我们的迷宫,这个函数将为在一个动作中到达奖励的动作提供更好的奖励,而不是在两个或更多动作中到达奖励的动作。如果将 g 的值设为 1,方程简化为随时间的奖励总和。这在 Q 学习中很少使用;它可能导致代理不收敛。

Q 函数

现在我们可以评估代理在迷宫中移动的路径,那么如何找到最优策略呢?对于我们的迷宫问题,简单的答案是,在面临动作选择时,我们希望选择导致最大奖励的动作;这不仅适用于当前动作,还适用于当前动作后我们将进入的状态的最大动作。

这个函数的名称是 Q 函数。如果我们有完美的信息,这个函数将给出我们在任何状态下的最优动作;它看起来如下:

在这里,s 是一个状态,a 是在该状态下可以采取的动作,而 0 < g < 1 是折扣因子。rewards 是一个函数,它返回在某个状态下采取某个动作的奖励。actions 是一个函数,它返回在状态 s 中采取动作 a 后转移到的状态 s’ 以及在该状态下所有可用的动作 a’

让我们看看如果我们将 Q 函数应用于折扣因子为 g=0.5 的迷宫会是什么样子:


图 3:简单迷宫,现在带有 Q 值。箭头显示了在每个末端两个状态之间移动的预期奖励

您会注意到所示的 Q 函数是无限递归的。这是一个假设的完美 Q 函数,所以不是我们可以在代码中应用的东西。为了在代码中使用它,一个方法是简单地预先设定一个最大的动作数;那么它可能是这样的:

def q(state, action, reward_func, apply_action_func, actions_for_state_func, max_actions_look_ahead, discount_factor=0.9):
    new_state = apply_action_func(state, action)
    if max_actions_look_ahead > 0:
        return reward_func(new_state) + discount_factor \ * max(q(new_state, new_action, reward_func, apply_action_func, actions_for_state_func, max_actions_look_ahead-1) 
for new_action in actions_for_state_func(new_state))
    else:
        return reward_func(new_state)

在这里,state 是定义环境状态的某个对象。action 是定义在状态中可以采取的有效动作的某个对象。reward_func 是一个函数,它返回给定状态的浮点值奖励。apply_action_func 返回将给定动作应用于给定状态后的新状态。actions_for_state_func 是一个函数,它返回给定状态的所有有效动作。

如果我们不必担心未来的奖励并且我们的状态空间很小,上述方法将获得良好的结果。它还要求我们能够准确地从当前状态模拟到未来状态,就像我们可以为棋盘游戏做的那样。但是,如果我们想要训练一个代理来玩动态电脑游戏,那么这些约束都不成立。当被提供来自电脑游戏的图像时,我们不知道在按下给定按钮后图像将会变成什么,或者我们将获得什么奖励,直到我们尝试为止。

Q 学习的实践

一个游戏可能每秒有 16-60 帧,并且经常会根据许多秒前所采取的动作来获得奖励。此外,状态空间是广阔的。在电脑游戏中,状态包含作为游戏输入的屏幕上的所有像素。如果我们想象一个屏幕被降低到 80 x 80 像素,所有像素都是单色和二进制,黑色或白色,那仍然是 2⁶⁴⁰⁰ 个状态。这使得状态到奖励的直接映射变得不切实际。

我们需要做的是学习 Q 函数的近似值。这就是神经网络可以应用其通用函数近似能力的地方。为了训练我们的 Q 函数近似值,我们将存储游戏状态、奖励和我们的代理在游戏中采取的行动。我们网络的损失函数将是其对前一状态的奖励的近似值与其在当前状态获得的实际奖励之间的差的平方,加上其对游戏中达到的当前状态的奖励的近似值乘以折扣因子的差的平方:


s 是先前的状态,a 是在该状态下采取的动作,而 0 < g < 1 是折扣因子。rewards 是返回在状态中采取行动的奖励的函数。actions 是返回在状态 s 中采取行动后你过渡到的 s’ 状态和该状态中所有可用的动作 a’Q 是先前介绍的 Q 函数。

通过以这种方式训练连续的迭代,我们的 Q 函数逼近器将慢慢收敛到真实的 Q 函数。

让我们先为世界上最简单的游戏训练 Q 函数。环境是一个一维状态地图。一个假设的代理必须通过向左或向右移动来最大化其奖励来导航迷宫。我们将为每个状态设置奖励如下:

rewards = [0, 0, 0, 0, 1, 0, 0, 0, 0]

如果我们要可视化它,它可能会看起来像这样:


图 4:简单的迷宫游戏,代理可以在相连节点之间移动,并可以在顶部节点获得奖励 1。

如果我们把我们的代理放到这个“迷宫”中的第一个位置,他可以选择移动到 0 或 2 的位置。我们想要构建一个学习每个状态的价值的网络,并通过此可推断出采取移动到该状态的动作的价值。网络的第一次训练将仅学习每个状态的内在奖励。但在第二次训练中,它将利用从第一次训练中获得的信息来改进奖励的估计。在训练结束时,我们预期看到一个金字塔形状,在 1 个奖励空间中具有最大的价值,然后在离中心更远的空间上递减价值,因为您必须更进一步旅行,从而应用更多的未来折扣以获得奖励。以下是代码中的示例(完整示例在 Git 存储库中的q_learning_1d.py中):

import tensorflow as tf
import numpy as np
states = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]
NUM_STATES = len(states)

我们创建一个states列表;列表中每个项的值是代理移动到该位置时获得的奖励。在这个示例中,它获得到第 5 个位置的奖励:

NUM_ACTIONS = 2
DISCOUNT_FACTOR = 0.5
def one_hot_state(index):
    array = np.zeros(NUM_STATES)
    array[index] = 1.
    return array

这个方法将使用一个数字,并将其转换为我们状态空间的独热编码,例如,3 变为[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]:

session = tf.Session()
state = tf.placeholder("float", [None, NUM_STATES])
targets = tf.placeholder("float", [None, NUM_ACTIONS])

我们创建了一个 TensorFlow session和用于输入和目标的占位符;数组中的None用于小批量维度:

weights = tf.Variable(tf.constant(0., shape=[NUM_STATES, NUM_ACTIONS]))
output = tf.matmul(state, weights)

对于这个简单的例子,我们可以使用状态和动作奖励之间的线性关系来准确地评估一切,所以我们只需要创建一个output层,它是weights的矩阵乘法。不需要隐藏层或任何非线性函数:

loss = tf.reduce_mean(tf.square(output - targets))
train_operation = tf.train.GradientDescentOptimizer(0.05).minimize(loss)
session.run(tf.initialize_all_variables())

我们使用均方误差(MSE)作为损失函数和标准梯度下降训练。这就是我们最终将用作目标值的 Q-learning 的一部分:

for _ in range(1000):
    state_batch = []
    rewards_batch = []
    for state_index in range(NUM_STATES):
        state_batch.append(one_hot_state(state_index))

我们创建一个state_batch,其中的每个项目都是游戏中的每个状态,以独热编码形式表示。例如,[1, 0, 0, 0, 0, 0, 0, 0, 0],[0, 1, 0, 0, 0, 0, 0, 0, 0],以此类推。然后,我们将训练网络来逼近每个状态的值:

minus_action_index = (state_index - 1) % NUM_STATES
         plus_action_index = (state_index + 1) % NUM_STATES

对于每个状态,我们现在获取如果我们从该状态采取每个可能动作后所在的位置。注意,在这个示例中,状态会循环,所以从位置 0 向-1 移动会使您处于位置 8:

minus_action_state_reward = session.run(output, feed_dict={state: [one_hot_state(minus_action_index)]})
        plus_action_state_reward = session.run(output, feed_dict={state: [one_hot_state(plus_action_index)]})

我们使用我们的网络,即我们的 q 函数近似器,来获取它认为如果我们采取每个动作(minus_action_indexplus_action_index),我们将获得的奖励,即网络认为我们在它将我们放入的状态中能够获得的奖励:

minus_action_q_value = states[minus_action_index] + DISCOUNT_FACTOR * np.max(minus_action_state_reward)
        plus_action_q_value = states[plus_action_index] + DISCOUNT_FACTOR * np.max(plus_action_state_reward)]

在这里,我们有了现在常见的 Q 函数方程的 Python 版本。我们将移动到一个状态的初始奖励与DISCOUNT_FACTOR乘以我们在该状态下采取的动作所能获得的最大奖励相加:

action_rewards = [minus_action_q_value, plus_action_q_value]
rewards_batch.append(action_rewards)

我们将这些添加到rewards_batch中,它将用作训练操作的目标值:

session.run(train_operation, feed_dict={
        state: state_batch,
        targets: rewards_batch})
print([states[x] + np.max(session.run(output, feed_dict={state: [one_hot_state(x)]}))
    for x in range(NUM_STATES)])

一旦我们获得了每个状态的完整奖励集,我们就会运行实际的训练步骤。如果我们运行此脚本并查看输出,我们可以感受到算法是如何迭代更新的。在第一次训练运行之后,我们看到了这个:

[0.0, 0.0, 0.0, 0.05, 1.0, 0.05, 0.0, 0.0, 0.0, 0.0]

一切都是 0,除了奖励状态两边的项目。现在这两个状态基于你可以从它们移动到奖励方块上获得奖励。再向前走几步,你会发现奖励开始在状态之间传播:

[0.0, 0.0, 0.013, 0.172, 1.013, 0.172, 0.013, 0.0, 0.0, 0.0]

此程序的最终输出将类似于这样:

[0.053, 0.131, 0.295, 0.628, 1.295, 0.628, 0.295, 0.131, 0.053, 0.02]

正如你所看到的,数组中最高的奖励位于第五个位置,我们最初设置为有奖励的位置。但是我们给出的奖励只有 1;那么为什么这里的奖励比这个要高呢?这是因为 1.295 是在当前空间获得的奖励与我们可以在未来从这个空间移开并反复返回时获得的奖励的总和,这些未来的奖励通过我们的折扣因子 0.5 减少。

学习这种未来的无限奖励是好的,但是奖励通常是在执行具有固定结束的任务过程中学到的。例如,任务可能是在架子上堆放物体,当堆栈倒塌或所有物体都堆放完毕时结束。要将这个概念添加到我们的简单 1-D 游戏中,我们需要添加终端状态。这些将是达到后,任务就结束的状态;所以与其他任何状态相比,在评估其 Q 函数时,我们不会通过添加未来奖励来训练。要进行此更改,首先我们需要一个数组来定义哪些状态是终止状态:

terminal = [False, False, False, False, True, False, False, False, False, False]

这将设置为第五个状态,我们从中获得奖励的状态为终止状态。然后,我们只需要修改我们的训练代码以考虑这个终止状态:

if terminal[minus_action_index]:
            minus_action_q_value = DISCOUNT_FACTOR * states[minus_action_index]
        else:
            minus_action_state_reward = session.run(output, feed_dict={state: [one_hot_state(minus_action_index)]})
            minus_action_q_value = DISCOUNT_FACTOR *(states[minus_action_index] + np.max(minus_action_state_reward))
        if terminal[plus_action_index]:
            plus_action_q_value = DISCOUNT_FACTOR * states[plus_action_index]
        else:
            plus_action_state_reward = session.run(output,
            feed_dict={state: [one_hot_state(plus_action_index)]})
            plus_action_q_value = DISCOUNT_FACTOR * (states[plus_action_index] + np.max(plus_action_state_reward))

如果我们现在再次运行代码,输出将稳定为这样:

[0.049, 0.111, 0.242, 0.497, 1.0, 0.497, 0.242, 0.111, 0.0469, 0.018]

动态游戏

现在我们已经学习了世界上最简单的游戏,让我们尝试学习一些更加动态的内容。Cart pole 任务是一个经典的强化学习问题。代理必须控制一个小车,上面平衡着一个杆,通过一个关节连接到小车上。在每一步,代理可以选择将小车向左或向右移动,并且每一步平衡杆的时候都会获得奖励 1。如果杆与竖直方向偏差超过 15 度,游戏就结束:


图 5:Cart pole 任务

要运行 Cart pole 任务,我们将使用 OpenAIGym,这是一个于 2015 年创建的开源项目,它以一致的方式提供了一种运行强化学习代理与一系列环境进行交互的方法。在撰写本文时,OpenAIGym 支持运行一系列 Atari 游戏,甚至还支持一些更复杂的游戏,如 doom,而且设置最少。可以通过运行以下命令来安装它:

pip install gym[all]

在 Python 中运行 Cart pole 可以通过以下方式实现:

import gym
env = gym.make('CartPole-v0')
current_state = env.reset()

gym.make方法创建了我们的代理将在其中运行的环境。传入"CartPole-v0"字符串告诉 OpenAIGym 我们希望这是车杆任务。返回的env对象用于与车杆游戏进行交互。env.reset()方法将环境置于其初始状态,并返回描述它的数组。调用env.render()将以可视方式显示当前状态,并对env.step(action)的后续调用允许我们与环境进行交互,以响应我们调用它的动作返回新的状态。

我们需要如何修改我们简单的一维游戏代码以学习车杆挑战?我们不再有一个明确定义的位置;相反,车杆环境将一个描述车和杆位置和角度的四个浮点值的数组作为输入给我们。这些将成为我们的神经网络的输入,它将由一个具有 20 个节点和一个tanh激活函数的隐藏层组成,导致一个具有两个节点的输出层。一个输出节点将学习当前状态向左移动的预期奖励,另一个输出节点将学习当前状态向右移动的预期奖励。以下是代码示例(完整的代码示例在 git repo 的deep_q_cart_pole.py中):

feed_forward_weights_1 = tf.Variable(tf.truncated_normal([4,20], stddev=0.01))
feed_forward_bias_1 = tf.Variable(tf.constant(0.0, shape=[20]))
feed_forward_weights_2 = tf.Variable(tf.truncated_normal([20,2], stddev=0.01))
feed_forward_bias_2 = tf.Variable(tf.constant(0.0, shape=[2]))
input_placeholder = tf.placeholder("float", [None, 4])
hidden_layer = tf.nn.tanh(tf.matmul(input_placeholder, feed_forward_weights_1) + feed_forward_bias_1)
output_layer = tf.matmul(hidden_layer, feed_forward_weights_2) + feed_forward_bias_2 

为什么使用一个具有 20 个节点的隐藏层?为什么使用tanh激活函数?挑选超参数是一门黑暗的艺术;我能给出的最好答案是,当尝试时,这些值表现良好。但是知道它们在实践中表现良好,以及知道一些关于解决车杆问题需要什么样的复杂程度的信息,我们可以猜测为什么这可能指导我们选择其他网络和任务的超参数。

一个关于在监督学习中隐藏节点数量的经验法则是,它应该在输入节点数量和输出节点数量之间。通常,输入数量的三分之二是一个不错的区域。然而,在这里,我们选择了 20,是输入节点数量的五倍。一般来说,偏爱更少的隐藏节点有两个原因:第一个是计算时间,更少的单元意味着我们的网络运行和训练速度更快。第二个是减少过拟合并提高泛化能力。你已经从前面的章节中了解到了过拟合以及过于复杂模型的风险,即它完全学习了训练数据,但没有能力泛化到新数据点。

在强化学习中,这些问题都不那么重要。虽然我们关心计算时间,但通常瓶颈大部分时间花在运行游戏上;因此,额外的几个节点不太重要。对于第二个问题,在泛化方面,我们没有测试集和训练集的划分,我们只有一个环境,一个代理人在其中获得奖励。因此,过度拟合不是我们必须担心的事情(直到我们开始训练能够跨多个环境运行的代理人)。这也是为什么你经常看不到强化学习代理人使用正则化器的原因。这个警告是,随着训练过程的进行,我们的训练集的分布可能会因为我们的代理人在训练过程中的变化而发生显著变化。存在的风险是代理人可能会对我们从环境中获得的早期样本过度拟合,并导致学习在后来变得更加困难。

鉴于这些问题,选择隐藏层中的任意大数量节点是有意义的,以便给予最大可能性学习输入之间的复杂交互。但是唯一真正的方法是测试。图 6显示了运行具有三个隐藏节点的神经网络与小车杆任务的结果。正如您所看到的,尽管它最终能够学会,但其表现远不及具有 20 个隐藏节点的情况,如图 7所示:

图 6:具有三个隐藏节点的小车杆,y = 最近 10 场比赛的平均奖励,x = 已玩的比赛

为什么只有一个隐藏层?任务的复杂性可以帮助我们估计这一点。如果我们考虑小车杆任务,我们知道我们关心输入参数的相互关系。杆的位置可能好也可能不好,这取决于小车的位置。这种交互水平意味着仅仅是权重的纯线性组合可能不够。通过快速运行,这个猜测可以得到确认,它将显示出尽管没有隐藏层的网络可以比随机更好地学习这个任务,但它的表现远不如单隐藏层网络。

更深的网络会更好吗?也许,但是对于只有这种轻微复杂性的任务来说,更多的层次往往不会改善事情。运行网络将确认额外的隐藏层似乎几乎没有什么影响。一个隐藏层为我们提供了我们在这个任务中所需要的容量。

至于选择tanh,有几个因素需要考虑。relu 激活函数之所以在深度网络中流行,是因为饱和。当运行一个具有激活函数范围受限的多层网络时,例如 logistic 函数的 0 到 1,许多节点会学会在接近 1 的最大值处激活。它们在 1 处饱和。但我们经常希望在输入更极端时信号更明显。这就是为什么 relu 如此流行的原因——它给一个层增加了非线性,同时不限制其最大激活。这在许多层网络中尤为重要,因为早期层可能会获得极端的激活,这对于向未来层发出信号是有用的。

只有一个层时,这不是一个问题,所以 sigmoid 函数是合理的。输出层将能够学习将来自我们隐藏层的值缩放到它们需要的值。有没有理由更喜欢 tanh 而不是 logistic 函数呢?我们知道我们的目标有时会是负数,并且对于一些参数组合,这可能是好的或坏的,具体取决于它们的相对值。这表明,提供由 tanh 函数提供的 -1 到 1 的范围可能比 logistic 函数更可取,其中要判断负相关性,首先必须学习偏差。这是事后的大量推测和推理;最好的答案最终是这种组合在这个任务中非常有效,但希望它能够在面对其他类似问题时给出一些最佳超参数的开始猜测的感觉。

要回到代码,我们的损失和训练函数在我们的推车杆任务中将是这样的:

action_placeholder = tf.placeholder("float", [None, 2])
target_placeholder = tf.placeholder("float", [None])
q_value_for_state_action = tf.reduce_sum(tf.mul(output_layer, action_placeholder),reduction_indices=1)

q_value_for_state_action 变量将是网络为给定状态和动作预测的 q-value。将 output_layer 乘以 action_placeholder 向量,除了我们采取的动作外,其他所有值都将为 0,然后对其求和,这意味着我们的输出将是我们的神经网络对于仅仅那个动作的预期值的近似:

cost = tf.reduce_mean(tf.square(target_placeholder – 
                        q_value_for_state_action))
train_operation = tf.train.RMSPropOptimizer(0.001).minimize(cost)

我们的成本是我们认为的状态和动作的预期回报与由 target_placeholder 定义的应该是的回报之间的差异。

描述在第七章棋盘游戏的深度学习中的策略梯度方法的一个缺点是,所有训练都必须针对环境进行。一组策略参数只能通过观察其对环境奖励的影响来评估。而在 Q 学习中,我们试图学习如何评估一个状态和动作的价值。随着我们对特定状态价值的理解能力提高,我们可以利用这些新信息更好地评估我们曾经经历过的先前状态。因此,与其总是在当前经历的状态上进行训练,我们可以让我们的网络存储一系列状态,并针对这些状态进行训练。这被称为经验回放

经验回放

每次我们采取一个动作并进入一个新状态时,我们都会存储一个元组previous_state, action_taken, next_reward, next_statenext_terminal。这五个信息片段就足以运行一个 Q 学习训练步骤。当我们玩游戏时,我们将把这些信息存储为一系列观察。

另一个经验回放有助于解决的困难是,在强化学习中,训练很难收敛。部分原因是我们训练的数据之间存在非常强的相关性。学习代理人经历的一系列状态将密切相关;如果一系列状态和行动的时间序列一起训练,会对网络的权重产生很大影响,并可能撤销大部分之前的训练。神经网络的一个假设是训练样本都是来自某个分布的独立样本。经验回放有助于解决这个问题,因为我们可以让训练的小批量样本从内存中随机抽样,这样样本之间就不太可能相关。

从记忆中学习的学习算法称为离线学习算法。另一种方法是在线学习,其中我们只能根据直接玩游戏来调整参数。策略梯度、遗传算法和交叉熵方法都是其示例。

运行带有经验回放的车杆的代码如下:

from collections import deque
observations = deque(maxlen=20000)
last_action = np.zeros(2)
last_action[0] = 1
last_state = env.reset()

我们从我们的observations集合开始。在 Python 中,一个 deque 是一个队列,一旦达到容量就会开始从队列开头删除项目。在这里创建 deque 时,其 maxlen 为 20,000,这意味着我们只会存储最近的 20,000 个观察。我们还创建了最后一个动作,np.array,它将存储我们从上一个主循环中决定的动作。它将是一个独热向量:

while True:
    env.render()
    last_action = choose_next_action(last_state)
    current_state, reward, terminal, _ = env.step(np.argmax(last_action))

这是主循环。我们首先渲染环境,然后根据我们所处的last_state决定采取什么动作,然后采取该动作以获得下一个状态:

if terminal:
        reward = -1

OpenAIGym 中的小车杆任务始终在每个时间步长给出奖励 1。当我们达到终止状态时,我们将强制给予负奖励,以便代理有信号学习避免它:

observations.append((last_state, last_action, reward, current_state, terminal))
    if len(observations) > 10000:
        train()

我们将此转换的信息存储在我们的观察数组中。如果我们有足够的观察结果存储,我们也可以开始训练。只有在我们有足够多的样本时才开始训练非常重要,否则少量早期观察结果可能会严重偏倚训练:

if terminal:
        last_state = env.reset()
   else:
        last_state = current_state

如果我们处于终止状态,我们需要重置我们的env以便给我们一个新的游戏状态。否则,我们可以将last_state设置为下一个训练循环的current_state。我们现在还需要根据状态决定采取什么行动。然后是实际的train方法,使用与我们之前的 1-D 示例相同的步骤,但改为使用来自我们观察的样本:

def _train():
    mini_batch = random.sample(observations, 100)

从我们的观察中随机取 100 个项目;这些将是要进行训练的mini_batch

previous_states = [d[0] for d in mini_batch]
    actions = [d[1] for d in mini_batch]
    rewards = [d[2] for d in mini_batch]
    current_states = [d[3] for d in mini_batch]

mini_batch元组解包为每种类型数据的单独列表。这是我们需要馈入神经网络的格式:

agents_reward_per_action = session.run(_output_layer, feed_dict={input_layer: current_states})

获取由我们的神经网络预测的每个current_state的奖励。这里的输出将是一个大小为mini_batch的数组,其中每个项目都是一个包含两个元素的数组,即采取左移动作的 Q 值估计和采取右移动作的 Q 值估计。我们取其中的最大值以获取状态的估计 Q 值。连续的训练循环将改进此估计值以接近真实的 Q 值:

agents_expected_reward = []
    for i in range(len(mini_batch)):
        if mini_batch[i][4]:
            # this was a terminal frame so there is no future reward...
            agents_expected_reward.append(rewards[i])
        else:
            agents_expected_reward.append(rewards[i] + FUTURE_REWARD_DISCOUNT * np.max(agents_reward_per_action[i]))

如果是非终止状态,将我们实际获得的奖励与我们的网络预测的奖励相结合:

session.run(_train_operation, feed_dict={
        input_layer: previous_states,
        action: actions,
        target: agents_expected_reward})

最后,对网络运行训练操作。

Epsilon 贪心

Q 学习的另一个问题是,最初,网络会非常糟糕地估计动作的奖励。但是这些糟糕的动作估计决定了我们进入的状态。我们早期的估计可能非常糟糕,以至于我们可能永远无法从中学习到奖励的状态。想象一下,在小车杆中,网络权重被初始化,使代理始终选择向左移动,因此在几个时间步长后失败。因为我们只有向左移动的样本,所以我们永远不会开始调整我们的权重以向右移动,因此永远无法找到具有更好奖励的状态。

对此有几种不同的解决方案,例如为网络提供进入新颖情境的奖励,称为新颖性搜索,或者使用某种修改来寻找具有最大不确定性的动作。

最简单的解决方案,也是被证明效果良好的解决方案之一,是从随机选择动作开始,以便探索空间,然后随着网络估计变得越来越好,将这些随机选择替换为网络选择的动作。这被称为 epsilon 贪心策略,它可以作为一种轻松实现一系列算法的探索方法。这里的 epsilon 是指用于选择是否使用随机动作的变量,贪心是指如果不是随机行动,则采取最大行动。在单车杆示例中,我们将这个 epsilon 变量称为probability_of_random_action。它将从 1 开始,表示 0 的随机动作几率,然后在每个训练步骤中,我们将其减小一些小量,直到它为 0 为止:

if probability_of_random_action > 0.and len(_observations) > OBSERVATION_STEPS:
 probability_of_random_action -= 1\. / 10000

在最后一步,我们需要的是将我们的神经网络输出转换为智能体动作的方法:

def choose_next_action():
    if random.random() <= probability_of_random_action:
        action_index = random.randrange(2)

如果随机值小于probability_of_random_action,则随机选择一个动作;否则选择我们神经网络的最大输出:

else:
        readout_t = session.run(output_layer, feed_dict={input_layer: [last_state]})[0]
        action_index = np.argmax(readout_t)
    new_action = np.zeros([2])
new_action[action_index] = 1
return new_action

这是训练进度与单车杆任务的图表:


图 7:单车杆任务,y = 过去 10 次游戏的平均长度 x = 所玩游戏的数量

看起来很不错。单车杆任务的成功定义为能够持续超过 200 轮。在 400 次游戏后,我们轻松击败了这个标准,平均每局游戏持续时间远远超过 300 轮。因为我们使用 OpenAIGym 设置了这个学习任务,现在可以轻松地设置到其他游戏中。我们只需要将gym.make行更改为以新输入游戏字符串为输入,然后调整我们网络的输入和输出数量以适应该游戏。在 OpenAIGym 中还有一些其他有趣的控制任务,例如摆锤和杂技,q-learning 在这些任务上也应该表现良好,但作为挑战,让我们来玩一些 Atari 游戏。

Atari Breakout

Breakout 是一款经典的 Atari 游戏,最初于 1976 年发布。玩家控制一个挡板,必须用它将球弹到屏幕顶部的彩色方块上。每次命中一个方块都会得分。如果球下落到屏幕底部超出挡板范围,玩家将失去一条生命。游戏在所有方块被销毁或玩家失去最初的三条生命后结束:


图 8:Atari Breakout

想想学习像 Breakout 这样的游戏比我们刚刚看过的单车杆任务要难多少。对于单车杆来说,如果做出了导致杆倾斜的错误动作,我们通常会在几个动作内收到反馈。在 Breakout 中,这样的反馈要少得多。如果我们将挡板定位不正确,可能是因为进行了 20 多次移动才导致的。

Python 深度学习(三)(2)https://developer.aliyun.com/article/1511978

相关文章
|
1天前
|
机器学习/深度学习 人工智能 算法
中草药识别系统Python+深度学习人工智能+TensorFlow+卷积神经网络算法模型
中草药识别系统Python+深度学习人工智能+TensorFlow+卷积神经网络算法模型
14 0
|
9天前
|
机器学习/深度学习 自然语言处理 TensorFlow
使用Python实现深度学习模型:注意力机制(Attention)
使用Python实现深度学习模型:注意力机制(Attention)
22 0
使用Python实现深度学习模型:注意力机制(Attention)
|
11天前
|
机器学习/深度学习 数据可视化 PyTorch
使用Python实现深度学习模型:迁移学习与预训练模型
使用Python实现深度学习模型:迁移学习与预训练模型
33 0
|
12天前
|
机器学习/深度学习 人工智能 算法
食物识别系统Python+深度学习人工智能+TensorFlow+卷积神经网络算法模型
食物识别系统采用TensorFlow的ResNet50模型,训练了包含11类食物的数据集,生成高精度H5模型。系统整合Django框架,提供网页平台,用户可上传图片进行食物识别。效果图片展示成功识别各类食物。[查看演示视频、代码及安装指南](https://www.yuque.com/ziwu/yygu3z/yhd6a7vai4o9iuys?singleDoc#)。项目利用深度学习的卷积神经网络(CNN),其局部感受野和权重共享机制适于图像识别,广泛应用于医疗图像分析等领域。示例代码展示了一个使用TensorFlow训练的简单CNN模型,用于MNIST手写数字识别。
37 3
|
14天前
|
机器学习/深度学习 存储 TensorFlow
Python 深度学习(一)(4)
Python 深度学习(一)
29 2
|
14天前
|
机器学习/深度学习 算法 算法框架/工具
Python 深度学习(一)(3)
Python 深度学习(一)
34 2
|
14天前
|
机器学习/深度学习 算法 自动驾驶
Python 深度学习(一)(2)
Python 深度学习(一)
31 1
|
14天前
|
机器学习/深度学习 人工智能 算法
Python 深度学习(一)(1)
Python 深度学习(一)
16 1
|
14天前
|
机器学习/深度学习 分布式计算 算法
Python 深度学习(三)(4)
Python 深度学习(三)
19 0
|
14天前
|
机器学习/深度学习 运维 算法
Python 深度学习(三)(3)
Python 深度学习(三)
15 0
Python 深度学习(三)(3)