本文概述
- 可学习的参数和超参数
- 有监督, 无监督和强化学习算法
- 泛化, 过度拟合和拟合不足
- 可扩展性
- 神经网络
- 卷积神经网络
- 应用领域
- 强化学习
- 深度Q学习
- 使用Flappy Bird进行深度Q学习的示例
- 深度强化学习:2D, 3D甚至真实生活
在经典编程中, 软件指令是由程序员明确编写的, 而从数据中什么也学不到。相反, 机器学习是计算机科学领域, 它使用统计方法使计算机能够学习数据并从中提取知识而无需进行显式编程。
在此强化学习教程中, 我将展示如何使用PyTorch来教强化学习神经网络如何玩Flappy Bird。但是首先, 我们需要涵盖许多构建基块。
机器学习算法可以大致分为两部分:传统学习算法和深度学习算法。传统的学习算法通常比深度学习算法具有更少的可学习参数, 并且学习能力也更少。
而且, 传统的学习算法无法进行特征提取:人工智能专家需要找出良好的数据表示形式, 然后将其发送给学习算法。传统机器学习技术的示例包括SVM, 随机森林, 决策树和$ k $ -means, 而深度学习的核心算法是深度神经网络。
深度神经网络的输入可以是原始图像, 并且人工智能专家不需要找到任何数据表示形式—神经网络可以在训练过程中找到最佳表示形式。
很长时间以来, 人们就已经知道许多深度学习技术, 但是硬件方面的最新进展迅速推动了深度学习的研究和开发。 Nvidia负责该领域的扩展, 因为其GPU已启用了快速的深度学习实验。
可学习的参数和超参数
机器学习算法由在训练过程中调整的可学习参数和在训练过程之前设置的非学习参数组成。在学习之前设置的参数称为超参数。
网格搜索是找到最佳超参数的常用方法。这是一种蛮力方法:这意味着尝试在定义的范围内尝试所有超参数组合, 并选择可以最大化预定义指标的组合。
有监督, 无监督和强化学习算法
对学习算法进行分类的一种方法是在有监督和无监督算法之间划清界限。 (但这并不一定那么简单:强化学习就介于这两种类型之间。)
当我们谈论监督学习时, 我们要看$(x_i, y_i)$对。 $ x_i $是算法的输入, $ y_i $是输出。我们的任务是找到一个函数, 该函数将从$ x_i $到$ y_i $进行正确的映射。
为了调整可学习的参数, 以便它们定义将$ x_i $映射到$ y_i $的函数, 需要定义损失函数和优化器。优化器使损失函数最小化。损失函数的一个示例是均方误差(MSE):
在这里, $ y_i $是地面真相标签, $ \ widehat {y_i} $是预测标签。深度学习中非常流行的一种优化器是随机梯度下降。有许多尝试改进随机梯度下降方法的变体:Adam, Adadelta, Adagrad等。
无监督算法会尝试在数据中查找结构, 而无需明确为其提供标签。 $ k $ -means是试图在数据中找到最佳聚类的无监督算法的示例之一。下图是具有300个数据点的图像。 $ k $ -means算法在数据中找到了结构, 并为每个数据点分配了一个簇标签。每个群集都有其自己的颜色。
强化学习使用奖励:稀疏, 延时标签。代理商采取行动, 改变环境, 从中获得新的观察和奖励。观察是代理商从环境中感受到的刺激。它可以是代理看到, 听到, 闻到的东西等等。
当代理商采取行动时会给予奖励。它告诉代理该动作有多好。通过感知观察和奖励, 代理可以学习如何在环境中表现最佳。我将在下面详细介绍。
主动, 被动和逆向强化学习
此技术有几种不同的方法。首先, 我们在这里使用了积极的强化学习。相反, 有被动强化学习, 奖励只是另一种观察方式, 而是根据固定的政策做出决定。
最后, 逆向强化学习尝试根据各种状态下的动作及其奖励的历史来重建奖励函数。
泛化, 过度拟合和拟合不足
参数和超参数的任何固定实例都称为模型。机器学习实验通常包括两个部分:培训和测试。
在训练过程中, 使用训练数据调整可学习的参数。在测试过程中, 可学习的参数将被冻结, 任务是检查模型对先前看不见的数据进行预测的程度。泛化是学习机在经历了学习数据集之后, 能够在新的, 看不见的示例或任务上准确执行的能力。
如果模型相对于数据而言过于简单, 则将无法拟合训练数据, 并且在训练数据集和测试数据集上的表现都将很差。在这种情况下, 我们说该模型不适合。
如果机器学习模型在训练数据集上表现良好, 但在测试数据集上表现不佳, 则说明它是过拟合的。过度拟合是指模型相对于数据而言过于复杂的情况。它可以很好地适合训练数据, 但是对训练数据集的适应性很大, 以至于它在测试数据上的表现很差-也就是说, 它不能泛化。
下图是与整体数据和预测函数之间的平衡情况相比, 欠拟合和过拟合的图像。
可扩展性
数据对于建立机器学习模型至关重要。通常, 传统的学习算法不需要太多的数据。但是由于容量有限, 性能也会受到限制。下图显示了深度学习方法与传统机器学习算法相比如何很好地扩展。
神经网络
神经网络由多层组成。下图显示了一个具有四层的简单神经网络。第一层是输入层, 最后一层是输出层。输入和输出层之间的两层是隐藏层。
如果一个神经网络具有多个隐藏层, 我们称之为深度神经网络。将输入集合$ X $提供给神经网络, 并获得输出$ y $。使用反向传播算法完成学习, 该算法结合了损失函数和优化器。
反向传播包括两个部分:正向传递和反向传递。在前向传递中, 将输入数据放在神经网络的输入上并获得输出。计算地面真实情况和预测之间的损失, 然后在反向传递中, 针对损失对神经网络的参数进行调整。
卷积神经网络
卷积神经网络是神经网络的一种变体。它主要用于计算机视觉任务。
卷积神经网络中最重要的层是卷积层(因此得名)。它的参数由可学习的过滤器(也称为内核)组成。卷积层将卷积运算应用于输入, 将结果传递到下一层。卷积运算减少了可学习参数的数量, 起到了一种启发式的作用, 并使神经网络更易于训练。
以下是卷积层中一个卷积内核的工作方式。将该核应用于图像并获得卷积特征。
ReLU层用于在神经网络中引入非线性。非线性很重要, 因为我们可以使用它们对所有函数建模, 而不仅仅是线性函数, 从而使神经网络成为通用函数逼近器。这使得ReLU函数的定义如下:
ReLU是用于在神经网络中引入非线性的所谓激活函数的示例之一。其他激活函数的示例包括S型和超正切函数。 ReLU是最流行的激活函数, 因为它表明与其他激活函数相比, 它使神经网络训练更加有效。
下面是ReLU函数的图。
如你所见, 此ReLU函数只是将负值更改为零。这有助于防止梯度消失的问题。如果梯度消失, 则对调整神经网络的权重不会有太大影响。
卷积神经网络由多层组成:卷积层, ReLU层和完全连接的层。完全连接的层将一层中的每个神经元连接到另一层中的每个神经元, 如本节开头的图像中的两个隐藏层所示。最后一个完全连接的层将前一层的输出映射到number_of_actions值。
应用领域
深度学习是成功的, 并且在多个机器学习子领域中优于传统的机器学习算法, 包括计算机视觉, 语音识别和强化学习。这些深度学习领域被应用于各种现实世界领域:金融, 医学, 娱乐等。
强化学习
强化学习是基于代理的。代理在环境中采取行动, 并从中获得观察和奖励。代理商需要接受培训以最大化累积奖励。如导言所述, 对于经典的机器学习算法, 机器学习工程师需要进行特征提取, 即创建良好的特征, 这些特征很好地表示环境并将其馈入机器学习算法中。
借助深度学习, 可以创建采用高维度输入的端到端系统, 例如视频, 并从中学习代理人采取良好行动的最佳策略。
2013年, 伦敦AI初创公司DeepMind在学习直接从高维感官输入控制代理商方面取得了重大突破。他们发表了一篇论文, 《通过深度强化学习玩Atari》, 在其中展示了他们如何教人为的神经网络, 仅通过看屏幕就能玩Atari游戏。它们被Google收购, 然后在《自然》杂志上发表了一篇新文章, 并进行了一些改进:通过深度强化学习进行人级控制。
与其他机器学习范例相反, 强化学习没有监督者, 只有奖励信号。反馈被延迟:它不是像监督学习算法那样瞬时的。数据是连续的, 代理的动作会影响它接收到的后续数据。
现在, 代理处于其处于某种状态的环境中。为了更详细地描述这一点, 我们使用马尔可夫决策过程, 这是对强化学习环境进行建模的一种正式方法。它由一组状态, 一组可能的操作以及从一种状态过渡到另一种状态的规则(例如, 概率)组成。
该代理能够执行操作, 从而改变环境。我们称奖励$ R_t $。这是一个标量反馈信号, 指示代理在步骤$ t $的运行状况。
为了获得良好的长期业绩, 不仅要考虑立即的回报, 而且还要考虑未来的回报。从时间步长$ t $开始的一个情节的总奖励为$ R_t = r_t + r_ {t + 1} + r_ {t + 2} + \ ldots + r_n $。未来是不确定的, 未来我们走得越远, 未来的预测差异就越大。因此, 使用了折价的未来奖励:$ R_t = r_t + \ gamma r_ {t + 1} + \ gamma ^ 2r_ {t + 2} + \ ldots + \ gamma ^ {nt} r_n = r_t + \ gamma R_ {t + 1} $。代理人应选择使折价的未来奖励最大化的行动。
深度Q学习
$ Q(s, a)$函数表示在状态$ s $中执行动作$ a $时的最大折现未来奖励:
未来奖励的估计由Bellman方程给出:$ Q(s, a)= r + \ gamma \ max_ {a’} Q(s, a’)$。换句话说, 给定状态$ s $和动作$ a $的最大将来奖励是立即奖励加上下一个状态的最大将来奖励。
使用非线性函数(神经网络)对Q值的逼近不是很稳定。因此, 使用经验重播可以保持稳定性。培训课程中的情节期间的经验会存储在重播内存中。使用来自重放存储器的随机小批处理, 而不是使用最新的过渡。这破坏了后续训练样本的相似性, 否则将导致神经网络陷入局部最小值。
关于深度Q学习, 还有两个要提到的重要方面:探索和开发。通过开发, 可以根据当前信息做出最佳决策。探索会收集更多信息。
当算法执行神经网络提出的动作时, 它就是在进行利用:它利用了神经网络的学习知识。相反, 算法可以采取随机动作, 探索新的可能性并将潜在的新知识引入神经网络。
下面显示了DeepMind的论文”使用深度强化学习玩Atari”中的”具有体验重播的深度Q学习算法”。
DeepMind将经过训练的卷积网络称为深度Q网络(DQN)。
使用Flappy Bird进行深度Q学习的示例
Flappy Bird是一款流行的手机游戏, 最初是由越南视频游戏艺术家兼程序员Dong Nguyen开发的。在其中, 玩家控制一只鸟, 并尝试在绿色管道之间飞行而不撞到它们。
以下是使用PyGame编码的Flappy Bird克隆的屏幕截图:
此后对克隆进行了分叉和修改:删除了背景, 声音和不同的鸟管样式, 并对代码进行了调整, 因此可以轻松地将其与简单的强化学习框架一起使用。修改后的游戏引擎来自以下TensorFlow项目:
但是, 我没有使用TensorFlow, 而是使用PyTorch构建了一个深度强化学习框架。 PyTorch是用于快速, 灵活实验的深度学习框架。它在Python中提供了具有强大GPU加速功能的张量和动态神经网络。
神经网络架构与论文”通过深度强化学习进行人级控制”中使用的DeepMind相同。
层 | 输入 | 过滤尺寸 | 大步走 | 过滤器数量 | 激活 | 输出如下 |
---|---|---|---|---|---|---|
转换1 | 84x84x4 | 8×8 | 4 | 32 | 简历 | 20x20x32 |
转换2 | 20x20x32 | 4×4 | 2 | 64 | 简历 | 9x9x64 |
转换3 | 9x9x64 | 3×3 | 1 | 64 | 简历 | 7x7x64 |
FC4 | 7x7x64 | 512 | 简历 | 512 | ||
fc5 | 512 | 2 | 线性的 | 2 |
有三个卷积层和两个完全连接的层。除最后一层使用线性激活外, 每一层都使用ReLU激活。神经网络输出代表玩家唯一可能动作的两个值:”飞起来”和”什么都不做”。
输入包含四个连续的84×84黑白图像。下面是提供给神经网络的四个图像的示例。
你会注意到图像旋转了。那是因为克隆游戏引擎的输出是旋转的。但是, 如果先教神经网络然后使用此类图像进行测试, 则不会影响其性能。
你可能还会注意到, 图像被裁剪, 因此省略了地板, 因为它与该任务无关。代表管道和鸟类的所有像素均为白色, 而代表背景的所有像素均为黑色。
这是定义神经网络的代码的一部分。将神经网络的权重初始化为遵循均匀分布$ \ mathcal {U}(-0.01, 0.01)$。神经网络参数的偏差部分设置为0.01。尝试了几种不同的初始化(Xavier统一, Xavier正常, Kaiming统一, Kaiming正常, 统一和正常), 但是上述初始化使神经网络收敛和训练最快。神经网络的大小为6.8 MB。
class NeuralNetwork(nn.Module):
def __init__(self):
super(NeuralNetwork, self).__init__()
self.number_of_actions = 2
self.gamma = 0.99
self.final_epsilon = 0.0001
self.initial_epsilon = 0.1
self.number_of_iterations = 2000000
self.replay_memory_size = 10000
self.minibatch_size = 32
self.conv1 = nn.Conv2d(4, 32, 8, 4)
self.relu1 = nn.ReLU(inplace=True)
self.conv2 = nn.Conv2d(32, 64, 4, 2)
self.relu2 = nn.ReLU(inplace=True)
self.conv3 = nn.Conv2d(64, 64, 3, 1)
self.relu3 = nn.ReLU(inplace=True)
self.fc4 = nn.Linear(3136, 512)
self.relu4 = nn.ReLU(inplace=True)
self.fc5 = nn.Linear(512, self.number_of_actions)
def forward(self, x):
out = self.conv1(x)
out = self.relu1(out)
out = self.conv2(out)
out = self.relu2(out)
out = self.conv3(out)
out = self.relu3(out)
out = out.view(out.size()[0], -1)
out = self.fc4(out)
out = self.relu4(out)
out = self.fc5(out)
return out
在构造函数中, 你会注意到已定义了超参数。出于本博客文章的目的, 未完成超参数优化。相反, 超参数通常在DeepMind的论文中使用。在这里, 某些超参数的缩放比例要比DeepMind的论文要低, 因为Flappy Bird的复杂度不及他们用于调整的Atari游戏。
同样, epsilon更改为对此游戏更加合理。 DeepMind使用的epsilon为1, 但此处使用0.1。这是因为较高的ε迫使鸟拍打得很多, 这将鸟推向屏幕的上边界, 最终最终导致鸟撞到管道中。
强化学习代码有两种模式:训练和测试。在测试阶段, 我们可以看到强化学习算法在游戏中的学习程度。但是首先, 需要训练神经网络。我们需要定义要最小化的损失函数以及将损失函数最小化的优化器。我们将亚当优化方法和均方误差用于损失函数:
optimizer = optim.Adam(model.parameters(), lr=1e-6)
criterion = nn.MSELoss()
游戏应实例化:
game_state = GameState()
重播内存定义为Python列表:
replay_memory = []
现在我们需要初始化第一个状态。一个动作是二维张量:
- [1, 0]表示”不执行任何操作”
- [0, 1]代表”飞起来”
frame_step方法为我们提供了下一个屏幕, 奖励以及关于下一个状态是否为终端的信息。每只鸟在不经过管道时不死的移动奖励为0.1;如果鸟成功通过管道, 则奖励为1;如果鸟撞毁, 则奖励为-1。
resize_and_bgr2gray函数可以裁剪地板, 将屏幕调整为84×84的图像, 并将颜色空间从BGR更改为黑白。 image_to_tensor函数将图像转换为PyTorch张量并将其放入GPU内存(如果有CUDA可用)。最后, 将最后四个顺序的屏幕连接在一起, 并准备将其发送到神经网络。
action = torch.zeros([model.number_of_actions], dtype=torch.float32)
action[0] = 1
image_data, reward, terminal = game_state.frame_step(action)
image_data = resize_and_bgr2gray(image_data)
image_data = image_to_tensor(image_data)
state = torch.cat((image_data, image_data, image_data, image_data)).unsqueeze(0)
使用以下代码行设置初始epsilon:
epsilon = model.initial_epsilon
主无限循环如下。注释写在代码中, 你可以将代码与上面编写的”具有体验重播的深度Q学习”算法进行比较。
该算法从重播内存中采样小批量, 并更新神经网络的参数。使用epsilon贪婪探索执行动作。 Epsilon会随着时间的流逝而退火。最小化的损失函数为$ L = \ frac {1} {2} \ left [\ max_ {a’} Q(s’, a’)-Q(s, a)\ right] ^ 2 $。 $ Q(s, a)$是使用Bellman方程计算的地面真值, 并且从神经网络获得$ \ max_ {a’} Q(s’, a’)$。神经网络为两个可能的动作给出两个Q值, 并且算法采用最高Q值的动作。
while iteration < model.number_of_iterations:
# get output from the neural network
output = model(state)[0]
# initialize action
action = torch.zeros([model.number_of_actions], dtype=torch.float32)
if torch.cuda.is_available(): # put on GPU if CUDA is available
action = action.cuda()
# epsilon greedy exploration
random_action = random.random() <= epsilon
if random_action:
print("Performed random action!")
action_index = [torch.randint(model.number_of_actions, torch.Size([]), dtype=torch.int)
if random_action
else torch.argmax(output)][0]
if torch.cuda.is_available(): # put on GPU if CUDA is available
action_index = action_index.cuda()
action[action_index] = 1
# get next state and reward
image_data_1, reward, terminal = game_state.frame_step(action)
image_data_1 = resize_and_bgr2gray(image_data_1)
image_data_1 = image_to_tensor(image_data_1)
state_1 = torch.cat((state.squeeze(0)[1:, :, :], image_data_1)).unsqueeze(0)
action = action.unsqueeze(0)
reward = torch.from_numpy(np.array([reward], dtype=np.float32)).unsqueeze(0)
# save transition to replay memory
replay_memory.append((state, action, reward, state_1, terminal))
# if replay memory is full, remove the oldest transition
if len(replay_memory) > model.replay_memory_size:
replay_memory.pop(0)
# epsilon annealing
epsilon = epsilon_decrements[iteration]
# sample random minibatch
minibatch = random.sample(replay_memory, min(len(replay_memory), model.minibatch_size))
# unpack minibatch
state_batch = torch.cat(tuple(d[0] for d in minibatch))
action_batch = torch.cat(tuple(d[1] for d in minibatch))
reward_batch = torch.cat(tuple(d[2] for d in minibatch))
state_1_batch = torch.cat(tuple(d[3] for d in minibatch))
if torch.cuda.is_available(): # put on GPU if CUDA is available
state_batch = state_batch.cuda()
action_batch = action_batch.cuda()
reward_batch = reward_batch.cuda()
state_1_batch = state_1_batch.cuda()
# get output for the next state
output_1_batch = model(state_1_batch)
# set y_j to r_j for terminal state, otherwise to r_j + gamma*max(Q)
y_batch = torch.cat(tuple(reward_batch[i] if minibatch[i][4]
else reward_batch[i] + model.gamma * torch.max(output_1_batch[i])
for i in range(len(minibatch))))
# extract Q-value
q_value = torch.sum(model(state_batch) * action_batch, dim=1)
# PyTorch accumulates gradients by default, so they need to be reset in each pass
optimizer.zero_grad()
# returns a new Tensor, detached from the current graph, the result will never require gradient
y_batch = y_batch.detach()
# calculate loss
loss = criterion(q_value, y_batch)
# do backward pass
loss.backward()
optimizer.step()
# set state to be state_1
state = state_1
现在所有部分都准备就绪, 下面是使用我们的神经网络对数据流进行的高级概述:
这是一个经过训练的神经网络的简短序列。
上面显示的神经网络使用高端Nvidia GTX 1080 GPU进行了几个小时的训练;而是使用基于CPU的解决方案, 此特定任务将需要几天的时间。在训练过程中, 游戏引擎的FPS设置为非常大的数字:999…999, 也就是说, 每秒尽可能多的帧。在测试阶段, FPS设置为30。
下图显示了最大Q值在迭代过程中如何变化。显示第10, 000次迭代。向下的尖峰信号意味着对于特定的帧(一次迭代就是一帧), 神经网络预测该鸟将在未来获得非常低的报酬, 即它将很快崩溃。
完整代码和预训练模型可在此处获得。
深度强化学习:2D, 3D甚至真实生活
在此PyTorch强化学习教程中, 我展示了计算机如何在不具有游戏先验知识的情况下学习玩Flappy Bird, 仅使用试错法, 就像人类初次接触游戏时所做的那样。
有趣的是, 可以使用PyTorch框架以几行代码实现该算法。此博客中的方法所基于的论文相对较旧, 并且提供了许多经过各种修改的新论文, 这些论文可以实现更快的收敛。从那时起, 深度强化学习已被用于玩3D游戏和现实世界的机器人系统中。
DeepMind, Maluuba和Vicarious等公司正在深入研究强化学习。这种技术在AlphaGo中得到了使用, 击败了Go上世界上最好的球员之一Lee Sedol。当时, 人们认为至少要花十年时间, 机器才能击败Go上最好的球员。
深度强化学习(以及一般而言, 对人工智能)的兴趣和投资泛滥甚至可能导致潜在的人工智能(AGI)—可以以算法和在计算机上模拟。但是AGI必须成为另一篇文章的主题。
参考文献:
- 揭开深度强化学习的神秘面纱
- 蓬松鸟的深度强化学习
- 维基百科上的”卷积神经网络”
- 维基百科上的”强化学习”
- 维基百科上的”马尔可夫决策过程”
- 伦敦大学学院RL课程
相关:深度学习教程:从感知器到深度网络
评论前必须登录!
注册