机器学习解决方案-三-

62 阅读1小时+

机器学习解决方案(三)

原文:annas-archive.org/md5/b07f17bc9671b217e2992493907316c0

译者:飞龙

协议:CC BY-NC-SA 4.0

第八章. 开发聊天机器人

2017 年是关于聊天机器人的年份,这一趋势在 2018 年继续。聊天机器人根本不是什么新鲜事物。聊天机器人的概念自 20 世纪 70 年代以来就已经存在。有时,聊天机器人应用程序也被称为问答系统。这是一个更具体的聊天机器人技术术语。让我们回顾一下历史。Lunar 是第一个基于规则的问答系统。使用这个系统,地质学家可以就阿波罗任务中的月球岩石提出问题。为了改进在阿波罗任务中使用的基于规则的系统,我们必须找到一种方法来编码基于模式的问题和答案。为此,我们使用了人工智能标记语言,也称为AIML。这有助于程序员用更少的代码行来实现我们通过使用硬编码的基于模式系统所生成相同的结果。随着机器学习(ML)领域的最新进展,我们可以构建一个无需硬编码响应的聊天机器人。

由于聊天机器人具有许多优势,它们现在被用于应用程序中;例如,用户不需要在他们的手机上安装不同种类的应用程序。如果有聊天机器人可以提供新闻,那么你可以要求 CNN 或《经济学人》上的新闻。像 Facebook、Hike、微信、Snapchat、Slack 等大型科技公司提供聊天机器人以实现更好的客户互动。他们通过创建一个可以引导客户执行某些操作的聊天机器人来实现这一点;它还提供了有关产品及其平台的有用信息。

聊天机器人提供不同的服务。通过使用 Facebook 聊天机器人平台,你可以订购鲜花并查看新闻。这听起来酷吗?从技术上讲,这些聊天机器人是当前时代的应用程序。我简要讨论了聊天机器人的好处,但我们将在本章中详细探讨它们。

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

  • 介绍问题陈述

  • 理解数据集

  • 构建聊天机器人的基本版本:

    • 理解基于规则的系统

    • 理解方法

    • 理解架构

  • 实施聊天机器人的基于规则系统

  • 测试基于规则的聊天机器人

  • 现有方法的缺点:

    • 理解优化方法的关键概念
  • 实施修订的方法:

    • 数据准备

    • 实施序列到序列(seq2seq)模型

  • 测试修订的方法:

    • 理解测试指标

    • 测试聊天机器人的修订版本

  • 修订方法的缺点:

    • 理解解决现有问题的关键概念
  • 最佳方法:

    • 实施最佳方法
  • 摘要

介绍问题陈述

在本章中,我们的主要目标是了解如何构建一个聊天机器人。聊天机器人,或问答系统QA 系统),非常有用。让我们考虑一个有趣的例子。假设你是一名学生,你有五本书要读。阅读和理解五本书可能需要时间。如果你能将这些五本书的内容输入到电脑中,只提出相关的问题呢?通过这样做,学生可以更快地学习概念和新信息。众所周知,主要的互联网产品公司正在整理信息,使其易于访问。聊天机器人或 QA 系统将帮助我们理解这些信息背后的含义。这就是为什么聊天机器人是 2017 年的热门词汇。无论你能想到什么应用,你都可以为它制作一个聊天机器人。现在许多消息平台都托管了开发者构建的聊天机器人,包括 Facebook Messenger、Slack、微信等等。聊天机器人是新的应用程序,因为它们已经存在于你每天可能使用十几次的已安装应用程序中。使用 Facebook 聊天机器人 API 开发的聊天机器人位于 Facebook Messenger 应用程序中。你可能每天会使用 Messenger 十几次。因此,用户不需要为特定的功能安装一个单独的应用程序。这将帮助公司更好地与客户互动。

在我们继续之前,我想介绍一些重要的术语,这些术语可以帮助我们了解在本章中我们针对的是哪种聊天机器人开发。首先,让我们了解开发聊天机器人的不同方法:

  • 基于检索的方法

  • 基于生成的方法

基于检索的方法

在基于检索的方法中,我们需要定义一组预定义的响应,并将某种启发式方法应用于预定义的响应,以便聊天机器人可以为给定的问题生成最佳可能的答案。这些答案非常依赖于输入问题和该输入问题的上下文。

在开发基于检索的模型的过程中,我们之前只使用了表达式匹配,这可以帮助我们得到适当的答案,但仅使用表达式匹配在这里不会有所帮助。因此,最近研究人员和程序员开始使用表达式匹配和高级机器学习(ML)分类器技术。让我们通过一个例子来了解机器学习分类器如何有助于构建基于检索的聊天机器人。

假设 Alice 需要在朋友的生日时送花。她的一个朋友 Emma 喜欢玫瑰,另一个朋友 Lucy 喜欢百合。Alice 使用一个花订购聊天机器人来预订她的订单。她写道:“我想预订一束多色的玫瑰花束和一束百合花。”在这种情况下,如果我们实现一个基本的机器学习分类器,聊天机器人可以轻松地识别 Alice 正在预订的两个不同的订单,并且它还能够解释每个订单的数量。聊天机器人还会要求提供不同的地址等。通过使用机器学习技术,我们可以编写更复杂的启发式算法,这有助于我们生成更合适的聊天机器人回答。Facebook messenger 聊天机器人 API 就是这样一个例子。

有另一个有趣的例子可以通过使用机器学习启发式算法来解决。比如说,你问聊天机器人:“今天是什么日子?”或者“今天是星期几?”如果我们实现了高级机器学习技术,那么它可以识别出这两个问题虽然措辞不同,但意图相同。在聊天机器人的开发过程中,意图和上下文检测是更复杂的任务,可以通过机器学习技术和一些启发式算法来实现。

现在让我们转向更困难的方法,即基于生成的方法。

基于生成的方法

在基于生成的方法中,没有为聊天机器人提供任何预定义的响应。聊天机器人从头开始生成响应。为了构建基于生成的聊天机器人,我们需要提供大量数据,机器将仅通过观察数据来学习如何回答用户提出的问题。2015 年,谷歌研究人员 Oriol Vinyals 和 Quoc V. Le 提出了一种称为 A 神经对话网络的方法。您可以参考以下论文:arxiv.org/pdf/1506.05869v2.pdf

在本文中,研究人员使用了康奈尔电影对白数据集。这个数据集被输入到机器中,以便它能够学习基本的英语语言。为此,他们使用了序列到序列seq2seq)神经网络架构。之后,他们使用了 IT 支持数据集,以便机器获得领域知识。一旦机器在这个数据集上训练完毕,他们就在 IT 支持部门测试了聊天机器人,这个聊天机器人将能够以极高的准确性回答问题。在接下来的章节中,我们将构建自己的神经对话网络。这种方法耗时较少,并克服了我们在基于检索的模型中面临的挑战,如意图识别、上下文识别等。

在此我们还需要讨论一些其他的重要术语。在开发聊天机器人之前,我们需要考虑一些重要的约束条件。第一个与对话领域相关:

  • 开放域

  • 封闭域

开放域

首先,让我们了解什么是开放域。有时候对话是模糊和不确定的。让我给你举一个例子。假设你遇到了一个多年未见的老同学。在对话的过程中,你不知道你们将要谈论哪个特定的话题。在这种情况下,对话可以随意展开。因此,对话的领域是不固定的。你可以谈论生活、工作、旅行、家庭等等。你可以谈论无数的话题。这种我们无法限制谈话领域的对话,被称为开放域。开发一个开放域聊天机器人是困难的,因为理想情况下,这种聊天机器人能够以人类水平的准确性回答来自任何领域的每一个问题。

目前,这类聊天机器人尚未被制造。当我们能够制造这种聊天机器人时,它将必须通过图灵测试。让我给你一个图灵测试的简要介绍,以便你能更好地理解解释。这个实验是由伟大的计算机科学家艾伦·图灵在 1950 年创造的。在这个实验中,一个被称为裁判的人向一个人和一个机器提出一系列问题。现在,裁判不知道哪个答案是来自人类,哪个答案是来自机器。但是,在看到或听到答案后,如果裁判无法区分哪些答案是来自人类,哪些答案是来自机器,那么机器就通过了图灵测试,我们可以说机器表现出了人类水平的智能,因为它表现得像人类一样聪明。到目前为止,还没有任何一个聊天机器人能够以人类水平的准确性通过图灵测试。你可以通过访问en.wikipedia.org/wiki/Turing_test了解更多关于图灵测试的信息。这一段技术正在快速发展,所以接下来的五年可能会非常激动人心。

谷歌在开发开放域聊天机器人方面非常积极。它正以谷歌助手的形式构建这个产品,但通过图灵测试的准确性和功能性仍然有限。现在让我们了解第二种类型的领域。

封闭域

封闭域是开放域的对立面。对于封闭域,我们需要限制对话话题。让我们举一个例子:在办公室,我们有时会有会议。在会议之前,参与者知道将要讨论的话题。因此,在会议期间,我们只关注那些话题。在这里,我们不会有无限的话题和领域可以谈论。这种我们限制了可以谈论的领域和话题的对话,被称为封闭域。

如果一家金融机构,如银行,为他们的客户推出聊天机器人,那么开发的聊天机器人不能回答诸如“你能告诉我今天新加坡的天气吗?”这样的问题。但它可以帮助你检查申请信用卡的程序,这是因为聊天机器人可以理解与特定领域相关的提问。一个针对封闭领域的聊天机器人是肯定可能的,而且有很多公司正在构建特定领域的聊天机器人,因为这对与客户群互动是有益的。所以在本章中,我们将重点关注封闭领域聊天机器人。

让我们尝试理解最后一个约束条件;对话长度,这意味着我们将从聊天机器人那里得到的答案的长度。基于这一点,我们需要理解以下术语:

  • 简短对话

  • 长对话

简短对话

这种类型的聊天机器人可以生成简短答案。在聊天机器人的开发过程中,我们需要问自己是否期望简短对话。如果我们期望简短答案,那么你应该很高兴,因为基于简短对话的聊天机器人可以很容易地构建。以下是一个简短对话的例子:

人:嗨

机器:你好

人:你好吗?

机器:我很好

这个例子表明了聊天机器人生成的简短对话。

长对话

这种类型的聊天机器人可以生成长答案。机器学习长对话是困难的,因此构建能够生成长对话的聊天机器人是困难的。让我们看看一个长对话的例子:

人:我想给你讲一个故事。

机器:请继续。

人:给你。约翰去了市场。丹尼尔正在去印度的路上。Siri 有一个苹果。Siri 在厨房。所以我的问题是,Siri 在哪里?

机器:根据你的故事,我认为 Siri 在厨房。

正如你在本例中看到的,为了生成正确的答案,机器也应该存储和处理给定的信息,以便它能生成正确的答案。因此,长对话和基于推理的聊天机器人开发起来有点困难。

到目前为止,你已经学习了很多术语。现在让我们看看它们在我们开发聊天机器人时将如何影响我们。根据方法和领域类型,我们可以构建不同类型的聊天机器人。

开放领域和基于生成的方法

我们希望使用基于生成的方法来构建聊天机器人,这种方法在开放领域上运行。这意味着聊天机器人需要从头开始学习如何回答来自任何领域的问题。这里的对话可以朝任何方向发展。这种类型的聊天机器人是通用人工智能AGI)的一个例子,我们还没有达到那个阶段。

因此,开发这种类型的聊天机器人不是本章的一部分。

开放领域和基于检索的方法

如果我们想要构建一个可以使用基于检索的方法在开放域上运行的聊天机器人,那么作为编码者,我们需要硬编码几乎所有的响应以及可能的问题和变体。这种方法消耗了大量的时间,因此这种类型的聊天机器人也不是本章的一部分。

封闭域和基于检索的方法

我们已经了解到我们无法在开放域进行操作,但关于封闭域呢?我们当然可以在封闭域工作,因为用户可以向聊天机器人提出有限数量的问题,而聊天机器人可以回答。如果我们为封闭域使用基于检索的方法,那么我们可以编写相对简单的问题。我们可以集成一些 NLP 工具,例如解析器、命名实体识别(NER)等,以便生成最准确的答案。

让我们举一个例子。假设我们想要构建一个可以为我们提供任何位置的实时天气信息的聊天机器人。如果我们使用基于检索的方法构建聊天机器人,那么用户将肯定能够得到关于“孟买的天气如何?加利福尼亚的天气如何?今天有降雨的可能吗?”等问题准确的答案。聊天机器人会很好地回答前两个问题,但在第三个问题时,它会感到困惑,因为我们没有提供降雨可能性的位置。如果聊天机器人使用了某些启发式方法,那么你可能会得到一个响应。聊天机器人可能会询问你想要了解降雨可能性的位置,但大多数情况下,这种情况不会发生。聊天机器人会直接告诉你,例如,加利福尼亚的降雨可能性。实际上,我想知道孟买的降雨可能性。所以,这类与上下文相关的问题在基于检索的方法中很常见。我们需要实施基于生成的方法来克服与上下文相关的问题。

封闭域和基于生成的方法

当我们为封闭域使用基于生成的方法时,这种类型聊天机器人的开发所需编码时间更少,答案的质量也得到了提高。如果我们想让我们的聊天机器人理解用户一系列问题中的长上下文和意图,那么基于生成的方法是正确的选择。经过在大语料库上的训练和优化后,聊天机器人可以理解问题的上下文和意图,并且能够提出推理类型的问题。这个聊天机器人开发空间对于研究和实施新想法来说既令人兴奋又有趣。

让我们举一个例子。假设我们构建了一个聊天机器人来向银行申请住房贷款。当用户运行这个聊天机器人时,它可能会问这些问题:我的住房贷款申请状态如何?我这边还有哪些文件需要上传?我会在接下来的 2 天内获得批准吗?您收到我的税务报表和工资条了吗?最后一个问题的上下文取决于第二个问题。这些类型的问题及其答案可以很容易地通过基于生成的方法生成。

参考以下图,它将帮助我们总结前面的讨论:

封闭域和基于生成的方法

图 8.1:开发聊天机器人的方法示意图

在本章中,我们将构建一个基于封闭域的聊天机器人,它将使用基于检索和基于生成的方法。

现在让我们看看本章我们将使用的数据集。

理解数据集

为了开发聊天机器人,我们使用了两个数据集。这些数据集如下:

  • 康奈尔电影对话数据集

  • bAbI 数据集

康奈尔电影对话数据集

这个数据集已被广泛用于开发聊天机器人。您可以从以下链接下载康奈尔电影对话语料库:www.cs.cornell.edu/~cristian/Cornell_Movie-Dialogs_Corpus.html。这个语料库包含从原始电影剧本中提取的大量丰富元数据的虚构对话集合。

这个语料库包含 10,292 对电影角色之间的 220,579 个对话交换。它涉及来自 617 部电影中的 9,035 个角色。总共有 304,713 个话语。这个数据集还包含电影元数据。以下是一些元数据类型:

  • 与电影相关的元数据包括以下细节:

    • 电影类型

    • 发布年份

    • IMDb 评分

  • 与角色相关的元数据包括以下细节:

    • 3,774 个角色的性别

    • 电影中的角色总数

当您下载这个数据集时,您会注意到我们将在这个章节中使用两个文件。这两个文件的名称是movie_conversations.txtmovie_lines.txt。让我们看看每个文件的内容细节。

movie_conversations.txt 的内容细节

这个文件包含movie_lines.txt文件的line_id。您可以在以下图中看到movie_conversations.txt的内容:

movie_conversations.txt 的内容细节

图 8.2:movie_conversations.txt 文件的内容示例

正如您在前面的图中可以看到,这个文件包含行号,实际对话内容位于movie_lines.txt中。*+++$+++*充当分隔符。您肯定非常想知道如何处理这个数据集;请稍等片刻,我们将在接下来的章节中介绍这一方面。

现在让我们看看下一个文件的内容。

movie_lines.txt 的内容细节

这个文件包含实际的电影对白。你可以在下面的图像中看到 movie_lines.txt 的样本内容:

movie_lines.txt 的内容细节

图 8.3:movie_lines.txt 的样本内容

正如你在前面的图像中可以看到的,每一行都有一个唯一的对话行 ID。这个 line_id 指的是 movie_conversations.txt 文件。这个文件包含相同的行分隔符和参与对话的角色名称。

如果你同时看到这两个文件,那么可能对你来说更有意义。在 movie_conversations.txt 文件中,参考 行号 194, 195, 196197 上的对话。所有这些对话都可以在 movie_lines.txt 文件中找到。在前面的图像中,你可以看到 行号 194 包含这个问题:我们能快点吗?Roxanne Korrine 和 Andrew Barrett 在广场上正在进行一场可怕的公开分手。再次。 另一方面,行号 195 包含这个答案:嗯,我想如果我们从发音开始,如果你觉得可以的话。

在将数据集以问答格式准备并输入到机器之前,我们需要准备这个数据集。我们将在使用它进行训练之前实现数据准备步骤。

现在,让我们看看 bAbI 数据集。

bAbI 数据集

这个数据集是由 Facebook AI Research (FAIR) 构建的,其中 AI 代表人工智能。这个数据集属于 bAbI 项目。你可以从 research.fb.com/downloads/babi/ 下载这个数据集。这是一个维护良好的数据集。bAbI 项目的目标是尝试构建一个自动文本理解和推理系统。这个数据集包括以下子数据集:

  • (20) QA bAbI 任务

  • (6) 对话 bAbI 任务

  • 儿童书籍测试

  • 电影对话数据集

  • WikiMovies 数据集

  • 基于对话的语言学习数据集

  • 简单问题数据集

  • HITL 对话模拟器

我们在这里将只使用一个子集,即 (20) QA bAbI 任务,因为它对于构建聊天机器人最有用。

(20) QA bAbI 任务

让我们详细看看这个子数据集。在这里,使用这个 (20) QA bAbI 数据集执行了 20 个不同的任务。让我们看看这些任务是什么。这些任务赋予机器进行某些推理的能力,基于这些推理,机器可以回答问题。你可以参考以下图像中给出的任务名称:

 (20) QA bAbI 任务

图 8.4: (20) QA bAbI 任务细节

图片来源:www.thespermwhale.com/jaseweston/…

Facebook 的研究员 Jason Weston、Antoine Bordes、Sumit Chopra、Alexander M. Rush、Bart van Merriënboer、Armand Joulin 和 Tomas Mikolov 发表了一篇论文,在论文中他们提出了一种有趣的基于 AI 的 QA 系统。你可以通过访问arxiv.org/abs/1502.05698来参考他们的研究论文。在本章中,我们将尝试实现任务 T1 的结果,并将重新生成其结果。

这个数据集包含两种语言的语料库,英语和印地语。这里有两种类型的文件夹:名为en的文件夹有 1,000 个训练示例,而en-10K有 10,000 个训练示例。每个任务数据集的格式如下图所示:

(20)QA bAbI 任务

图 8.5:单个支持 QA bAbI 任务的格式

支持的事实被称为故事。基于故事,用户可以向机器提问,机器应该给出可以从提供的支持文本中推导出的逻辑上正确的答案。这是一个困难的任务,因为在这种情况下,机器应该记住它可以随时使用的长上下文。我们很快就会使用这个有趣的数据集。

现在我们开始构建聊天机器人的基本版本。

构建聊天机器人的基本版本

在本节中,我们将构建聊天机器人的基本版本。对于任何公司来说,获取数据都不是问题,但获取特定领域的对话数据集具有挑战性。

现在有那么多公司的目标是要创建一个创新的特定领域聊天机器人,但他们的主要挑战是获取正确的数据。如果你面临同样的问题,那么这个基本方法可以帮助你。这个聊天机器人的基本版本是基于封闭领域和基于检索的方法,它使用基于规则的系统。所以,让我们开始了解基于规则的系统的每个方面。

为什么基于规则的系统有效?

如我之前提到的,基于规则的系统是实现基于检索的方法的方式。现在,你可能想知道为什么我们需要基于规则的系统。考虑到我们生活在机器学习(ML)的时代,这难道听起来不古老吗?让我与你分享我的个人经历。我密切合作了许多初创公司。其中一些在金融领域运营,一些在人力资源领域运营,还有一些在法律领域运营。在这个聊天机器人的时代,初创公司非常热衷于开发特定领域的聊天机器人,以帮助用户。最初,他们使用一些通用数据集,以便机器可以学习语言并为它们生成逻辑因果答案,但他们很快意识到他们没有足够的特定领域数据来帮助他们构建一个好的聊天机器人。让我给你举一个例子。

我与一家金融科技初创公司合作,我们需要构建一个聊天机器人。聊天机器人的具体要求是它应该帮助想要申请住房贷款的客户,以及那些已经申请并需要一些帮助的客户。现在,这家金融科技初创公司刚刚开始运营 1 年半。因此,他们没有关于客户可能提出的问题的大量聊天记录。简而言之,公司没有足够的特定领域数据,例如住房贷款申请人可能提出的问题类型,以及如何将这些客户查询同步到这家金融科技公司遵循的贷款程序。在这种情况下,我们需要关注两个主要方面:

  • 我们需要构建一个最小可行聊天机器人,以帮助客户处理基本的常见问题解答(FAQs)。

  • 通过这个最小可行聊天机器人的帮助,你还可以了解人们提出的问题类型,并根据这些问题,调整聊天机器人。

在这类情况下,当我们没有特定领域的数据集时,基于规则的或基于检索的模型将为我们工作。从下一节开始,我们将探讨基于规则的系统以及开发基本聊天机器人和其架构的方法。

理解基于规则的系统

在本节中,我们将介绍基于规则的系统,这样当你我们开始开发基于检索的聊天机器人时,你不会感到被排除在外。基于规则RB)系统定义为:使用可用的知识或规则,我们开发一个使用规则的系统,在语料库上应用可用的系统规则,并尝试生成或推断结果。从聊天机器人的角度来看,RB 系统包含所有可能的问题和答案,并且它们是硬编码的。我们绝对可以使用正则表达式和模糊逻辑来实现某种启发式方法,以使 RB 系统更加准确。

参考以下图示,它将给你一个关于使用基于检索方法的聊天机器人工作流程的思路:

理解基于规则的系统

图 8.6:基于规则的聊天机器人工作流程

根据前面的图示,你知道在一个基于规则的(RB)系统中,我们将手动编写所有可能的问题和答案,以及实现正则表达式和模糊逻辑,这将使聊天机器人具备生成适当答案的能力。根据业务需求,可以从这个系统中添加和删除问题。现在让我们讨论我们的方法,并基于这个方法,我们将构建聊天机器人的基本版本。

理解方法

在本节中,我们将查看有助于我们实现聊天机器人基本版本的步骤。在这里,我正在为金融领域构建一个聊天机器人,它将帮助用户申请住房贷款。我们将编写一些问题,以便你知道如何开发基于规则的聊天机器人。我们需要执行以下步骤:

  1. 列出可能的问题和答案。

  2. 决定标准消息。

  3. 理解架构。

列出可能的问题和答案

首先,我们需要列出所有我们可以代表用户想到的问题。一旦我们决定了这些问题,然后我们需要逐一决定这些问题的答案。假设我们要求用户提供全名、电子邮件 ID、电话号码和贷款金额,以便在用户中途退出时,客户代表可以回电。之后,我们询问用户他们需要什么样的帮助,然后他们可以提出问题。他们可能会询问资格标准、申请状态、文件要求等等。在第一次迭代中,您需要添加用户经常提出的最基本问题。一旦我们决定了问题和答案,编码它们就会变得容易。

例如,我在这个金融领域聊天机器人的基本版本中包含以下问题:

  • 请告知我获得住房贷款的资格标准

  • 请告知我的贷款申请状态

  • 告知我需要提交的文件清单

每个问题的答案如下:

  • 我们需要至少 3 年的工作经验、3 年的 IT 税单以及至少 35 万卢比的收入

  • 您的申请已提交至我们的信用风险管理团队

  • 您需要提交过去 6 个月的工资条、身份证明、3 年的 IT 税单以及您房屋的租赁文件。

我们还需要决定一些标准消息,这些内容将在下一节中介绍。

决定标准消息

我们需要决定标准消息,例如聊天机器人发出的欢迎信息。如果用户提出聊天机器人无法回答的问题,那么应该弹出什么信息?我们还需要决定用户结束聊天时的消息。

这些标准消息帮助用户了解他们可以向聊天机器人提出什么问题以及不能提出什么问题。现在让我们看看聊天机器人基本版本中的架构部分。

理解架构

在本节中,让我们谈谈架构。当我们构建一个特定领域的基于规则的聊天机器人时,我们需要存储用户提出的所有问题。我们还需要构建一个快速且可扩展的聊天机器人。在这种方法中,我们构建了 Web 服务。Web 服务的 REST API 将很容易与网站和前端集成。我们需要一个可以存储用户对话的数据库。这些对话数据将有助于我们改进聊天机器人或用于机器学习训练。我使用的库如下所示:

  • 使用 Flask 实现 Web 服务和 REST API

  • 使用 MongoDB 来存储对话。选择 NoSQL 数据库的原因是对话没有特定的格式。NoSQL 是存储无模式数据的良好选择。我们需要存储原始对话,因此 NoSQL 是一个很好的选择。

您可以参考以下图表,它将帮助您理解整个架构和流程:

理解架构

图 8.7:聊天机器人基本版本的架构设计

根据这个架构,您会发现聊天机器人基本版本的处理流程相当简单。这个流程涉及七个简单的步骤:

  1. 用户将向聊天机器人提问。

  2. 聊天机器人的基于规则的引擎将处理这个问题。在这里,已经调用了 REST API 来生成响应。

  3. 如果提出的问题对 RB 系统可用,那么用户将得到一个适当的答案。

  4. 如果提出的问题对 RB 系统不可用,那么用户将不会得到答案,而是收到一个标准错误信息。

  5. 用户的对话将被存储在 MongoDB 数据库中。这个响应是 JSON 格式的。

  6. REST API 将发送相同的 JSON 响应到前端。在前端,JavaScript 解析这个响应并弹出适当的答案。

  7. 当用户得到他们的答案后,他们可能会结束聊天或提出另一个问题。

我还想强调的一个主要观点是,在将数据存储到 MongoDB 之前,我们需要最终确定 JSON 响应的属性,这些属性实际上在用 JavaScript 解析 JSON 响应时对我们有帮助。您可以参考以下截图,这将帮助您了解我决定采用哪种类型的 JSON 模式:

理解架构

图 8.8:理解 JSON 响应属性

每个 JSON 属性的使用方法如下:

  • current_form_action: 这个属性指示当前正在调用的 REST API。

  • message_bot: 这个字段携带来自机器人的答案。

  • message_human: 这个字段携带用户的查询。

  • next_field_type: 如果我们需要在下一个问题中填充文本框或按钮,这个属性对于生成动态 HTML 组件很有用。

  • next_form_action: 这个属性指示在即将到来的请求中我们应该调用哪个 REST API。

  • placeholder_text: 如果您想在文本框中放置水印文本,那么这个属性可以帮助您实现 HTML 功能。

  • previous_field_type: 这个属性跟踪最后一个字段类型。

  • previous_form_action: 这个属性跟踪我们最后调用的 REST API。

  • suggestion_message: 有时,我们需要一条消息来调用特定的规则。这就像您说“OK Google”并调用 Google Home 助手一样。这个属性基本上指导用户在提问时可以期待什么。

现在,让我们开始实现基于规则的聊天机器人。

实现基于规则的聊天机器人

在本节中,我们将了解聊天机器人的实现。这个实现分为两部分。您可以通过访问以下链接找到此代码:github.com/jalajthanaki/Chatbot_Rule_Based

  • 实现对话流程

  • 使用 flask 实现 RESTful API

实现对话流程

为了实现对话逻辑,我们编写了一个单独的 Python 脚本,这样我们每次需要添加或删除一些逻辑时都会很容易。在这里,我们创建了一个 Python 包,我们将这个对话逻辑放入其中。文件名为 conversationengine.py,它使用 JSON、BSON 和 re 作为 Python 依赖项。

在这个文件中,我们将每个对话以函数的形式实现。当用户第一次打开聊天机器人时,应该会弹出一条欢迎信息。你可以在下面的屏幕截图中查看给出的代码:

实现对话流程

图 8.9:欢迎信息的代码片段

现在用户需要输入 Hi 来开始对话。当用户输入 hi 时,start_coversation_action 函数将被调用,聊天机器人将询问一些信息,以便它能给出更准确、个性化的答案。首先,它会询问用户的姓名,然后询问他们的电子邮件 ID 和电话号码。你可以在下面的屏幕截图中查看:

实现对话流程

图 8.10:询问基本用户信息的代码片段

同样,还有 borrowers_name_askingborrowers_email_id_askingmobilenumber_asking 函数,它们要求用户提供他们的姓名、电子邮件 ID 和电话号码。除此之外,还有一些问题可以帮助用户了解他们的贷款申请状态。如果客户是新的,他们可以提出诸如 为了申请住房贷款需要哪些文件? 之类的问题。你可以在 other_cases 函数中找到这些状态和文件相关的问题。你可以在下面的屏幕截图中查看该函数的代码:

实现对话流程

图 8.11:与贷款申请状态和申请住房贷款所需文件相关的代码片段

如前图所示,我们在这里使用了一个正则表达式,以便聊天机器人能够回答状态和文件相关的问题。这是纯粹使用基于关键字的逻辑编写的。

现在,让我们看看如何使用 Flask 构建这个函数的 Web 服务。

使用 Flask 实现 RESTful API

到目前为止,我们只编写了接收用户输入查询并基于该查询调用相应函数的功能。为了更好的维护和易于集成,我们需要使用 Flask 实现 RESTful API。为了实现这一点,我们使用了 flaskjsonosuuiddatetimepytzflsk_pymongo 库。Flask 是一个易于使用的 Web 框架。你可以在下面的图中找到代码片段:

使用 Flask 实现 RESTful API

图 8.12:为聊天机器人创建 RESTful API 的代码片段

如前图所示,每个路由调用一个不同的方法,这些方法都是我们之前提到的conversationengine.py文件的一部分。为了运行这个 Flask 引擎,我们需要使用 Flask 的app.run()命令。你可以通过访问以下链接找到所有 API 及其功能:github.com/jalajthanaki/Chatbot_Rule_Based/blob/master/flaskengin.py

现在我们来测试这个基于规则的聊天机器人。

测试基于规则的聊天机器人

在本节中,我们将测试聊天机器人的基本版本。让我们从聊天机器人从用户那里询问的基本个人信息开始。在这里,我将生成由 Flask RESTful API 生成的 JSON 响应。如果我们将这些 API 与前端集成,我们需要 JavaScript 来解析这个 JSON 响应。我不会在这里解释前端集成部分,所以让我们分析 JSON 响应。

对于欢迎信息,请参考以下截图:

测试基于规则的聊天机器人

图 8.13:欢迎信息的 JSON 响应

当聊天机器人询问用户姓名时的 JSON 响应如下所示:

测试基于规则的聊天机器人

图 8.14:询问用户姓名的 JSON 响应

如果用户询问其应用程序的状态,那么他们将得到以下图中给出的 JSON 响应:

测试基于规则的聊天机器人

图 8.15:获取状态相关信息的 JSON 响应

如果用户用印地语-英语(Hinglish)混合提问并使用查询中的单词状态,那么聊天机器人将生成响应。你可以在以下图中看到响应:

测试基于规则的聊天机器人

图 8.16:获取印地语-英语(Hinglish)语言相关信息的 JSON 响应

如果用户提出未编码的查询,它将生成以下 JSON 响应:

测试基于规则的聊天机器人

图 8.17:未知问题的 JSON 响应

经过测试,我们了解到已经编码的查询运行良好,但这个聊天机器人的基本版本对于未编码的问题处理不正确。我想在测试基于规则的聊天机器人后指出一些优点。然而,这种方法也有各种缺点,我们将在下一节中讨论。

基于规则的聊天机器人的优点

你可以参考以下基于规则的聊天机器人的优点:

  • 编码简单。

  • 计算能力需求较低。

  • 使用模式匹配方法,因此如果用户在对话中使用英语和其他语言,他们仍然会得到答案。这是因为聊天机器人识别出用户在问题中提供的特定关键词。假设用户用英语问:“你能提供我需要提交的文档列表吗?”另一个用户可能用印地语提问:“Kya aap mujhe bata sakte hain mujhe kaun se documents submit karne hain?”对于这个问题,聊天机器人将生成答案,因为它从用户查询中找到特定的关键词,如果这些关键词存在,那么聊天机器人将生成答案,而不考虑语言。

现在,让我们看看与这种方法相关的问题,我们需要解决这些问题来改进聊天机器人。

现有方法的问题

在本节中,我们将讨论我们聊天机器人的基本版本存在的问题。正如我们所知,对于未见过的查询,这种方法不起作用,这意味着基本方法无法正确地泛化用户的问题。

我在这里列出了一些问题:

  • 耗时,因为我们需要为每个场景硬编码,这根本不可行

  • 它无法处理未见过的用例

  • 用户应该处理对话的严格流程

  • 它无法理解长上下文

大多数这些问题可以通过基于生成的方法来解决。让我们看看将帮助我们改进这种方法的关键概念。

理解优化方法的关键概念

在本节中,我们将讨论一些关键概念,这些概念可以帮助我们改进聊天机器人的基本版本。我们之前列出的问题可以通过使用深度学习DL)技术来解决,这些技术可以帮助我们在更短的时间内构建一个更通用的聊天机器人。

在继续前进之前,我们需要决定我们将使用哪种深度学习技术来改进我们的方法。深度学习帮助我们取得很好的成果。在这里,我们需要使用端到端深度学习方法,它不对数据、对话结构和用例结构做出任何假设。这正是我们想要的。为了实现这一点,我们将使用循环神经网络RNN)。现在你可能想知道为什么 RNN 是有用的。让我通过一个例子来解释这一点。假设我们想要将温度分类为热或冷;为了做到这一点,我们将使用前馈神经网络将温度分类为热或冷,但对话并不是固定大小的。对话是一系列单词。我们需要使用一个能够帮助我们处理单词序列的神经网络。RNN 最适合处理这类序列。在 RNN 中,我们在训练过程中将数据反馈到输入中,形成一个循环。

在改进的方法中,我们将使用 TensorFlow 中的序列到序列(seq2seq)模型。因此,让我们先讨论一下序列模型。

理解 seq2seq 模型

使用 seq2seq 模型的好处是我们不需要执行特征工程。像大多数深度学习技术一样,它通过自身生成特征。我们将简要讨论 seq2seq 模型。seq2seq 模型由两个长短期记忆LSTM)循环神经网络组成。第一个神经网络是一个编码器。它处理输入。第二个神经网络是一个解码器。它生成输出。通常,深度学习算法需要输入和输出的维度是固定大小的,但在这里,我们接受一个句子中的单词序列作为输入,并输出一个新的单词序列。因此,我们需要一个能够学习具有长距离记忆依赖性的数据序列模型。LSTM 架构最适合这种情况。编码器 LSTM 将可变长度的输入句子转换为一个固定维度的向量表示。我们可以将其视为一个思维向量上下文向量。我们使用 LSTM 的原因是它可以记住序列中远处的单词;在这里,我们处理 seq2seq 模型的大序列注意力机制,这有助于解码器选择性地查看与提高准确性最相关的序列部分。

您可以参考以下图中 seq2seq 模型的架构:

理解 seq2seq 模型

图 8.18:seq2seq 模型的架构

图片来源:suriyadeepan.github.io/img/seq2seq/seq2seq2.png

当我们提供一个足够大的问题和回答的数据集时,它将识别问题集的相似性,并将它们表示为一个单独的思维向量。这种表示有助于机器理解问题的意图,而不管句子的结构如何,因此机器可以识别“现在几点了?”和“现在几点?”这样的问题具有相同的意图,因此它们将落入一个单独的思维向量中。训练后,我们将拥有一个巨大的集合,不仅包括突触权重,还有思维向量。之后,在训练模型时,我们需要使用额外的超参数以及适当的损失函数。一旦训练了模型,我们就可以与它聊天。

如果您想了解更多关于 seq2seq 模型的信息,那么您应该参考由谷歌研究人员发表的标题为一个神经对话模型的研究论文。您也可以参考这篇论文:arxiv.org/pdf/1506.05869v3.pdf以及这篇关于 LSTM 的精彩文章:colah.github.io/posts/2015-08-Understanding-LSTMs/

现在,让我们使用 seq2seq 模型来实现聊天机器人。

实现改进的方法

在本节中,我们将介绍实现的每个部分。您可以通过使用此 GitHub 链接找到代码:github.com/jalajthanaki/Chatbot_tensorflow。请注意,在这里,我使用 TensorFlow 版本 0.12.1。我在 GeForce GTX 1060 6GB GPU 上训练了几小时。在这个实现中,我们不需要生成特征,因为 seq2seq 模型会为句子中给出的单词序列生成其内部表示。我们的实现部分有以下步骤:

  • 数据准备

  • 实现 seq2seq 模型

让我们开始编码。

数据准备

在这个实现过程中,我们将使用康奈尔电影对白数据集。首先,我们需要以我们可以用于训练的格式准备数据。有一个 Python 脚本用于执行数据准备。您可以在以下位置找到脚本:github.com/jalajthanaki/Chatbot_tensorflow/blob/master/data/prepare_data_script/data.py

数据准备可以细分为以下步骤:

  • 生成问答对

  • 预处理数据集

  • 将数据集分割为训练数据集和测试数据集

  • 为训练和测试数据集构建词汇表

生成问答对

为了从康奈尔电影对白数据集中生成问答对,我们使用 movie_lines.txtmovie_conversations.txt 文件。movie_lines.txt 文件提供了关于每个对话的 line_id 以及实际对话的信息,而 movie_conversations.txt 只包含 line_ids。在这种情况下,我们需要从数据集中生成适当的问答对。为此,我们将这两个文件结合起来。在 Python 脚本中,有一些函数帮助我们合并这些文件。有关函数的详细信息如下:

  • get_id2line(): 这个函数帮助我们使用 +++$+++ 模式来分割数据。我们在 movie_lines.txt 文件上执行分割。分割后,借助这个函数,我们创建了一个字典,我们将 line_id 作为键,将电影对白作为值。所以,键 = line_id值 = 文本

  • get_conversations(): 这个函数分割 movie_conversations.txt 文件中给出的数据。这将帮助我们创建一个列表。这个列表包含 line_ids 的列表。

  • gather_dataset(): 这个函数实际上生成问答对。在这个函数中,应用了一个简单的逻辑。我们取 line_ids 的列表,我们知道最后一个元素表示答案。因此,我们分离问题和答案。借助 get_id2line() 函数,我们搜索问题和相应的答案。在这里,我们使用键的值来搜索问题和答案。

您可以通过以下截图查看实际的编码:

生成问答对

图 8.19:用于生成问答对的函数

现在,让我们探索数据预处理部分。

预处理数据集

这里涉及一些预处理和过滤步骤。作为预处理的一部分,我们执行以下步骤:

  • 我们使用内置的字符串函数lower()将对话转换为小写。

  • 我们还移除了垃圾字符以及过短或过长的对话。为此,我们使用基于列表的方法移除垃圾字符,并使用filter_data()函数移除过短或过长的对话。当我们对数据集应用filter_data()函数时,*28%*的数据集被过滤。

  • 我们还过滤掉了包含大量未知词汇的对话。在这里,*2%*的数据集受到了影响。为此,我们使用了filter_unk()方法。

  • 我们还将对句子进行分词。在这个过程中,我们将文本行列表转换为单词行列表。这种分词对于训练过程很有帮助,因为机器可以处理句子中的单个单词,借助单词 ID,数据检索变得更快。

您可以参考以下屏幕截图中给出的代码:

预处理数据集

图 8.20:预处理代码片段

将数据集分为训练数据集和测试数据集

预处理完成后,我们将数据集分为训练数据集和测试数据集,为此,我们将使用以下函数:

为训练和测试数据集构建词汇表

现在是时候从数据集中生成词汇表了。对于词汇表的生成,我们将执行以下步骤:

您可以在以下屏幕截图中看到prepare_custom_data()函数的代码:

为训练和测试数据集构建词汇表

图 8.21:生成词汇的代码片段

现在我们实际上将使用 TensorFlow 实现 seq2seq 模型。

实现 seq2seq 模型

在本节中,我们将使用 seq2seq 模型进行实际训练。我们将使用 TensorFlow 来实现 seq2seq 模型。在开始训练之前,让我们看看超参数配置文件,您可以通过以下 GitHub 链接访问:github.com/jalajthanaki/Chatbot_tensorflow/blob/master/seq2seq.ini

在聊天机器人开发过程中:seq2seq 模型,构建"训练,我们的脚本使用这些文件及其参数。以下参数位于此配置文件中:

  • Mode: 这可以是 train 或 test

  • train_enc: 这包含了解码器训练数据集的路径。

  • train_dec: 这包含了解码器训练数据集的路径。

  • test_enc: 这包含了解码器测试数据集的路径。

  • test_dec: 这包含了解码器测试数据集的路径。

  • Working_directory: 这是我们可以存储我们的检查点、词汇和临时数据文件的文件夹

  • enc_vocab_size: 这个数字定义了解码器的词汇量大小。我们将其设置为 20,000。

  • dec_vocab_size: 这个数字定义了解码器的词汇量大小。我们将其设置为 20,000。

  • num_layers: 这表示 LSTM 层的数量。在这里,我们将其设置为 3。

  • layer_size: 这表示 seq2seq 模型中的层数。我们将其设置为 256。

  • steps_per_checkpoint: 在检查点,模型的参数被保存,模型被评估,并打印结果。

  • learning_rate: 这表示我们训练模型的速度有多快或多慢。我们目前将其值设置为 0.5。

大多数前面的参数都可以更改以获得最佳结果。在训练期间,我们需要将模式设置为 train 并运行以下命令:

$ python execute.py

现在是时候了解 execute.py 文件内部的内容了。您可以通过以下 GitHub 链接访问此文件:github.com/jalajthanaki/Chatbot_tensorflow/blob/master/execute.py

在这个脚本中,我们调用 TensorFlow API。这个脚本可以分为以下几部分:

  • 创建模型

  • 训练模型

创建模型

我们在这里使用 TensorFlow 的Seq2SeqModel()函数。这个函数读取配置文件并使用配置文件中定义的值。为了存储训练模型,我们使用saver.restore()函数,为了获取检查点的状态,我们使用get_checkpoint_state()函数。您可以参考以下图中的代码片段:

创建模型

图 8.22:创建 seq2seq 模型的代码片段

训练模型

我们在execute.py文件中定义了train()方法。此函数初始化 TensorFlow 会话并开始训练。您可以参考以下截图中的代码片段:

训练模型

图 8.23:训练模型的代码片段

现在是训练模型的时候了。当我们执行python execute.py命令时,您将看到以下截图中的输出:

训练模型

图 8.24:使用 TensorFlow 训练 seq2seq 模型

在这里,训练是在 GPU 上进行的。我训练了这个模型 3 个小时。我已经训练了这个模型 15,000 个检查点。您可以参考以下截图:

训练模型

图 8.25:seq2seq 训练的输出

在 CPU 上,训练将花费很多时间,因此我还上传了预训练模型供您使用。您可以通过以下 GitHub 链接下载它们:github.com/jalajthanaki/Chatbot_tensorflow/tree/master/working_dir

现在是了解帮助我们评估训练模型的测试指标的时候了。

测试改进的方法

在本节中,我们将对改进的方法进行测试。在执行实际测试并查看聊天机器人对话的好坏之前,我们需要了解我们将为此方法和最佳方法使用的测试指标。这些测试指标帮助我们评估模型精度。让我们首先了解测试指标,然后我们将继续对改进的方法进行测试。

理解测试指标

在本节中,我们需要了解以下测试指标:

  • 混淆度

  • 损失

混淆度

在 NLP 领域,混淆度也被称为每词混淆度。混淆度是衡量训练模型预测未见数据输出好坏的度量。它也用于比较概率模型。低混淆度表明概率分布擅长预测样本。即使在训练过程中,您也可以看到对于每个检查点,混淆度都在下降。理想情况下,当混淆度没有变化时,我们需要停止训练。在 seq2seq 模型的训练过程中,我在 3 小时后停止了训练,因此当您从您的端点训练模型时,您可以等待混淆度不再进一步下降。

混淆度是使用熵的概念。如果您想了解混淆度,可以参考www.youtube.com/watch?v=BAN3NB_SNHY。每词混淆度基于熵。因此,为了理解熵,您可以参考以下链接:

一旦你理解了熵,理解困惑度方程就会变得容易。参考以下图中给出的方程:

困惑度

图 8.26:困惑度方程

图片来源:www.tensorflow.org/tutorials/r…

在这里,N 是样本数量,P 是一个概率函数。我们使用自然对数函数来计算熵。现在让我们看看另一个测试指标。

损失

训练损失表示训练进展的方向。通常,当我们开始训练时,损失值较高,训练准确率较低,但在训练过程中,损失值会下降,训练准确率会上升。在深度学习算法中使用了多种错误函数。在这里,我们使用交叉熵作为损失函数。交叉熵和对数损失在上下文中略有不同,但在机器学习中,当计算 0 到 1 之间的错误率时,它们是同一回事。你可以参考以下图中给出的方程:

损失

图 8.27:交叉熵方程

图片来源:ml-cheatsheet.readthedocs.io/en/latest/l…

如果你想要探索交叉熵损失函数,可以参考:neuralnetworksanddeeplearning.com/chap3.html

我们在这里没有深入数学细节,因为仅仅通过跟踪训练过程,你就可以知道损失值是增加还是减少。如果在训练时间内损失值在下降,那么训练就是在正确的方向上。这也适用于困惑度。最初,困惑度值很大,但在训练过程中它会逐渐下降,在某个时刻既不增加也不减少。那时,我们需要停止训练。现在让我们测试修改后的聊天机器人。

测试聊天机器人的修改版本

在本节中,我们将对修改后的聊天机器人进行测试。我仅在 GPU 上训练了 3 小时;现在让我们看看我们的聊天机器人能告诉我们多少。为了测试,我们需要在seq2seq.ini配置文件中进行一些小的更改。我们需要将模式值设置为测试,然后执行python execute.py命令。

执行给定的命令后,你将得到以下图中给出的输出:

测试聊天机器人的修改版本

图 8.28:修改后方法的输出

如果你训练的时间更长,那么你将得到一个更令人印象深刻的结果。我觉得深度学习算法可以帮助我们,如果我们想使用基于生成的方法来构建聊天机器人。现在让我们讨论如何改进这个修订方法。

修订方法的问题

在本节中,我们将讨论修订方法存在的问题。我们是否有任何方法可以优化修订方法?所以首先,让我们讨论改进的领域,以便在即将到来的方法中,我们可以专注于那个特定的点。

我想强调的一些要点如下:

  • 在我们聊天机器人的上一个版本中,缺乏推理能力,这意味着聊天机器人不能通过应用基本推理来回答问题。这正是我们需要改进的地方。

  • 让我给你举一个例子。假设我告诉聊天机器人一个故事:“约翰在厨房。丹尼尔在浴室。”之后,我说,我向聊天机器人提出这个问题:“约翰在哪里?”我们迄今为止构建的聊天机器人将无法回答这个简单的问题。我们作为人类,很好地回答这类问题。

  • 我们试图在我们的下一个方法中实现这种功能,以便我们可以在聊天机器人中启用一些人工智能功能。

让我们看看可以帮助我们构建人工智能聊天机器人的重要概念。

理解关键概念以解决现有问题

Facebook 人工智能研究小组发表了一篇论文,提出了一个可以证明机器也可以回答基于推理的问题的记忆网络。你当然可以参考这篇题为《迈向 AI 完整问答:一系列先决条件玩具任务》的论文。链接。你还可以参考这篇关于记忆网络的论文:链接

在这里,我们将使用 bAbI 数据集,并基于改进的记忆网络来训练模型。一旦训练完成,我们将检查我们的聊天机器人是否能够根据使用简单的推理能力来回答问题。我们将重新创建 Facebook 研究论文的结果。在我们进入实现部分之前,我们需要了解记忆网络是什么,以及我们如何构建一个将使用逻辑来回答问题的系统。所以,让我们简要地看看记忆网络。

记忆网络

在本节中,我们将探讨记忆网络,以便我们能够理解在我们即将在下一节中实现它时幕后实际发生的事情。

在 LSTM 网络中,记忆是通过使用隐藏状态和权重进行编码的。这些隐藏状态和权重对于极长的数据序列来说太小了,无论是书籍还是电影。因此,在语言翻译应用中,为了选择适合上下文的翻译,会使用多个 LSTM 状态,并结合注意力机制。Facebook 研究人员开发了一种名为记忆网络的新策略,在问答系统中优于 LSTM。

记忆网络背后的基本思想是允许神经网络使用外部数据结构作为记忆存储,并且以监督方式学习从这种外部记忆结构中检索所需记忆的位置。您可以参考以下图示中给出的记忆网络架构:

记忆网络

图 8.29:记忆网络架构

当涉及到回答由少量数据生成的问题时,使用记忆网络处理信息相当容易,但在现实世界中,处理具有长期依赖关系的数据是一个具有挑战性的任务。在 Kaggle 上,有一个名为 The Allen AI Science Challenge 的竞赛,获胜者使用了一种特殊的记忆网络变体,称为动态记忆网络(DMN),这是我们用来构建聊天机器人的。

动态记忆网络(DMN)

DMN 的架构在以下图示中给出:

动态记忆网络(DMN)

图 8.30:DMN 架构

图像来源:yerevann.github.io/public/2016…

DMN 的架构定义了两种类型的记忆,如下所示:

  • 语义记忆:我们使用预训练的 Glove 模型,该模型将为输入数据生成向量。这些向量是 DMN 模型的输入,并用作语义记忆。

  • 事件记忆:这种记忆包含其他知识。这种记忆的灵感来源于我们大脑的海马体功能。它能够检索由响应(如图像或声音)触发的时态状态。我们将在稍后看到这种事件记忆的用法。

这些是我们需要理解的一些重要模块:

  • 输入模块

  • 问题模块

  • 事件记忆模块

在我开始解释模块之前,请参考以下图示以更好地理解:

动态记忆网络(DMN)

图 8.31:每个 DMN 模块的详细信息

图像来源:yerevann.github.io/public/2016…

输入模块

输入模块是一个在一系列词向量上运行的 GRU(门控循环单元)。GRU 单元有点像 LSTM 单元,但它更计算高效,因为它只有两个门,并且不使用记忆单元。这两个门控制内容何时更新和何时删除。GRU 只执行两个任务:一个是 更新,另一个是 重置。你可以参考以下展示 LSTM 和 GRU 的图:

输入模块

图 8.32:LSTM 和 GRU 的示意图

图片来源:cdn-images-1.medium.com/max/1200/0*…

输入模块的隐藏状态以向量的形式表示了到目前为止的输入过程。它在每个句子后输出隐藏状态,这些输出在论文中被称为事实,因为它们代表了所提供内容的本质。你可能想知道 GRU 中隐藏状态是如何计算的。为此,你可以参考以下方程:

Ht = GRU(Xt, ht-1)

在这里,Ht 是当前时间步,ht-1 是前一个时间步,Xt 是给定的词向量。前面的方程是 GRU 隐藏状态计算的简单格式。你可以在下面的图中看到更详细和复杂的方程:

输入模块

图 8.33:GRU 中计算隐藏状态的方程

图片来源:yerevann.github.io/public/2016…

在这个方程中,借助给定的词向量和前一个时间步向量,我们计算当前时间步向量。更新给我们一个单层神经网络。我们将矩阵乘法求和并添加偏置项。然后,sigmoid 函数将其压缩到 0 到 1 之间的值列表,这就是我们的输出向量。我们用不同的权重集重复这个过程两次,然后使用重置门,它会学习在必要时忽略过去的时步。例如,如果下一句话与前面的句子无关,更新门也是类似的,它可以学习完全忽略当前的时间步。也许当前句子与答案无关,而前面的句子有关。

问题模块

此模块逐词处理问题,并使用与输入模块相同的 GRU 和相同的权重输出一个向量。我们需要为输入语句(输入数据)和我们将提出的问题进行编码。我们可以通过为它们实现嵌入层来实现这一点。现在我们需要为两者创建一个短时记忆表示。

短时记忆

如我之前所述,情景记忆的概念源于我们大脑的海马体功能。从输入中提取的事实和问题向量进入情景记忆模块。它由两个嵌套的 GRU 组成。内层 GRU 生成所谓的情景。它通过遍历输入模块中的事实来完成这一任务。在更新其内部状态时,它考虑了当前事实上的注意力函数的输出。注意力函数给每个事实分配一个介于 0 到 1 之间的分数,因此 GRU 忽略了得分低的事实。在训练过程中,在遍历所有可用事实的完整遍历之后,内层 GRU 输出一个情景,然后将其输入到外层 GRU。我们需要多个情景,以便我们的模型能够学习它应该关注句子中的哪个部分。在第二次遍历中,GRU 意识到句子中还有其他重要因素。通过多次遍历,我们可以收集越来越相关的信息。

这是对 DMN 的简要说明。您还可以参考这篇优秀的文章:yerevann.github.io/2016/02/05/implementing-dynamic-memory-networks/

现在,让我们看看这个实现的细节。

最佳方法

我们已经涵盖了所有有助于我们实现基于 DMN 的聊天机器人的概念。为了实现这种方法,我们将使用 Keras 和 TensorFlow 后端。我们不浪费时间,将直接跳到实现部分。您可以通过以下 GitHub 链接查看此方法的代码:github.com/jalajthanaki/Chatbot_based_on_bAbI_dataset_using_Keras

实施最佳方法

在这里,我们将使用给定的 bAbI 任务 1 数据集来训练我们的模型。首先,我们需要解析故事并构建词汇。您可以参考以下图中的代码:

实施最佳方法

图 8.34:解析故事和构建词汇的代码片段

我们可以初始化我们的模型,并将其损失函数设置为 Keras 中使用 RMSprop 实现的分类交叉熵。您可以在以下屏幕截图中参考:

实施最佳方法

图 8.35:构建模型的代码片段

在训练之前,我们需要设置一个超参数。借助超参数脚本中的值,我们将决定是否以训练模式或测试模式运行脚本。您可以在以下图中看到我们训练过程中需要设置的所有超参数:

实施最佳方法

图 8.36:训练的超参数值

在这里,我们使用了三个超参数。我们可以对它们进行实验。让我们讨论一下:

  • train_epochs:此参数表示训练示例在神经网络中完成前向传递和反向传递的次数。一个 epoch 意味着一个训练示例的前向传递和一个反向传递。在这里,我们将 train_epochs 设置为 100 次。你可以增加它,但这样训练时间也会增加。

  • batch_size:此参数表示在一次前向传递和反向传递中训练示例的数量。较高的批量大小需要更多的内存,所以我们把这个值设置为 32。如果你有更多的内存可用,你可以增加批量大小。请参考以下信息框中给出的简单示例。

  • lstm_size:此参数表示我们神经网络中存在的 LSTM 单元的数量。你可以增加或减少 LSTM 单元的数量。在我们的案例中,少于 64 个 LSTM 单元将不会给我们好的输出,所以我将 lstm_size 设置为 64。

注意

如果你有一千个训练示例,并且你的批量大小是 500,那么完成一个 epoch 需要 2 次迭代。

我已经在 GPU 上训练了这个模型。如果你没有使用 GPU,那么它可能需要很长时间。你可以通过执行以下命令开始训练:python main.py。训练的输出在以下图中给出:

实现最佳方法

图 8.37:训练输出的代码片段

一旦我们训练了模型,我们就可以加载并测试它。有两种测试模式可用:

  • 随机测试模式

  • 用户交互测试模式

随机测试模式

在这种模式下,脚本本身将加载一个随机故事并给出其答案。你可以在以下图中看到超参数的值:

随机测试模式

图 8.38,随机测试模式的超参数值。

对于测试,执行python main.py命令,你可以看到测试结果。这些结果在以下图中已经展示:

随机测试模式

图 8.39:随机测试模式的结果

用户交互测试模式

在这种模式下,如果测试用户可以给出自己的故事并提问,聊天机器人将生成对该问题的答案。你只需要记住,在每一个词之前,你需要提供空格。你可以在以下图中参考用户交互测试模式超参数的值:

用户交互测试模式

图 8.40:用户交互测试模式超参数的值

对于测试,执行python main.py命令,你可以看到测试结果。这些结果在以下图中已经展示:

用户交互测试模式

图 8.41:用户交互测试模式的结果

如果你想要测试所有其他任务,那么你可以使用这个网络应用:ethancaballero.pythonanywhere.com/

这种方法可以使我们的准确率达到 92%到 95%。这种方法帮助我们构建了具有 AI 功能的聊天机器人。

讨论混合方法

在实际场景中,为了构建聊天机器人,我们还可以结合这里描述的一些技术。根据业务需求,我们可以使用混合方法。

让我们举一个例子。假设你正在为金融领域构建聊天机器人。如果用户询问其账户中的可用余额,我们只需要一个基于规则的系统,它可以查询数据库并为该用户生成账户余额详情。如果用户询问如何将资金从一个账户转账到另一个账户,聊天机器人可以通过生成逐步信息来帮助用户了解如何转账。在这里,我们将使用基于深度学习的生成方法。我们应该有一个系统,它包括一个基于规则的引擎以及一个用于生成最佳可能输出的深度学习算法。在这个系统中,用户的问题首先发送到基于规则的系统。如果该问题的答案可以通过基于规则的系统生成,那么答案将被传递给最终用户。如果答案不能由基于规则的系统生成,那么问题将进一步传递到深度学习算法,并生成答案。最后,最终用户将看到他问题的回答。

摘要

在本章中,我们引用了不同的数据集来构建聊天机器人。你学习了在没有数据集的情况下可以使用的基于规则的途径。你还了解了开放和封闭领域。之后,我们使用了基于检索的方法来构建聊天机器人的基本版本。在改进的方法中,我们使用了 TensorFlow。这种改进的方法对我们来说非常好,因为它与基本方法相比节省了时间。我们在 Cornell Movie-Dialogs 数据集上实现了谷歌的神经对话模型论文。对于最佳方法,我们构建了一个使用 Facebook bAbI 数据集的模型,并建立了基本的推理功能,这有助于我们为聊天机器人生成良好的结果。尽管改进和最佳方法的训练时间真的很长,但那些想在云平台上训练模型的人可以选择这样做。到目前为止,我喜欢亚马逊网络服务(AWS)和谷歌云平台。我还将一个预训练模型上传到了我的 GitHub 仓库,以便你可以重现结果。如果你是一个初学者,并且想制作一个非常好的聊天机器人,那么谷歌的 API.AI 是一个好的聊天机器人开发平台。它现在被称为 Dialogflow,可在以下网址找到:dialogflow.com/。你还可以参考 IBM Watson API:www.ibm.com/watson/how-to-build-a-chatbot/。这些 API 可以帮助你在构建聊天机器人方面取得很大进展;而且它需要的编码知识也更少。

在下一章中,我们将构建一个基于计算机视觉的应用程序,该程序将帮助我们识别图像和视频中存在的命名对象。这个应用程序将实时检测对象。对象检测应用程序用于构建自动驾驶汽车、机器人等,所以请继续阅读!

第九章:构建实时目标识别应用

在本章中,我们将构建一个能够检测对象的应用。这个应用将帮助我们识别图像或视频流中存在的对象。我们将使用实时输入,例如来自网络摄像头的实时视频流,我们的实时目标检测应用将检测视频流中的对象。我们将使用实时视频流,这也是这种目标检测被称为实时目标检测的主要原因。在本章中,我们将使用迁移学习方法构建实时目标检测。我将在本章的讲解过程中详细解释迁移学习。

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

  • 介绍问题陈述

  • 理解数据集

  • 迁移学习

  • 设置编码环境

  • 基线模型的特征工程

  • 选择机器学习ML)算法

  • 构建基线模型

  • 理解测试指标

  • 测试基线模型

  • 现有方法的问题

  • 如何优化现有方法

    • 理解优化过程
  • 实施改进方法

    • 测试改进方法

    • 理解改进方法的问题

  • 最佳方法

    • 实施最佳方法
  • 概述

介绍问题陈述

在本章中,我们将构建一个目标检测应用。我们不仅会检测对象,还会构建一个实时检测对象的应用。这种应用可以用于自动驾驶汽车、农业领域的分离任务,甚至可以在机器人领域使用。让我们了解我们的目标和实际构建的内容。

我们希望构建一个应用,其中我们将提供实时网络摄像头视频流或实时视频流作为输入。我们的应用将使用预训练的机器学习模型,这将帮助我们预测视频中出现的对象。这意味着,如果视频中有一个人物,那么我们的应用可以将其识别为人物。如果视频包含椅子、杯子或手机,那么我们的应用应该以正确的方式识别所有这些对象。因此,本章的主要目标是构建一个能够检测图像和视频中的对象的应用。在本章中,你还将学习迁移学习的概念。我们所有的方法都是基于深度学习技术的。

在下一节中,我们将讨论数据集。

理解数据集

在本节中,我们将介绍用于训练深度学习模型的那个数据集。当我们试图构建目标检测应用时,有两个数据集被广泛使用,这些数据集如下:

  • COCO 数据集

  • PASCAL VOC 数据集

我们将逐一查看每个数据集。

COCO 数据集

COCO 代表上下文中的常见对象。因此,这个数据集的简称是 COCO 数据集。许多科技巨头,如谷歌、Facebook、微软等,都在使用 COCO 数据来构建对象检测、对象分割等令人惊叹的应用程序。你可以在这个官方网页上找到有关此数据集的详细信息:

COCO 数据集

COCO 数据集是一个大规模的对象检测、分割和标题数据集。在这个数据集中,总共有 330,000 张图像,其中超过 200,000 张被标记。这些图像包含 1.5 百万个对象实例,分为 80 个对象类别。所有标记的图像都有五个不同的标题;因此,我们的机器学习方法能够有效地泛化对象检测和分割。

通过使用 COCO 探索器,我们可以探索 COCO 数据集。你可以使用cocodataset.org/#explore URL 来探索数据集。COCO 探索器是一个出色的用户界面。你只需选择对象标签,例如我想看到图片中有人、自行车和公共汽车的图像,探索器就会提供包含人、自行车和公共汽车的图像。你可以参考以下图示:

COCO 数据集

图 9.1:COCO 数据集探索片段

在每张图像中,都为每个主要对象提供了适当的对象边界。这就是为什么如果你想要从头开始构建自己的计算机视觉应用,这个数据集非常棒的主要原因。

在这里,我们不是下载数据集,因为如果我们需要在这个数据集上从头开始训练模型,那么这将需要大量的时间和大量的 GPU 才能获得良好的准确率。因此,我们将使用预训练模型,并通过迁移学习实现实时对象检测。现在让我们继续讨论 PASCAL VOC 数据集。

PASCAL VOC 数据集

PASCAL 代表模式分析、统计建模和计算学习,VOC 代表视觉对象类别。在这个数据集中,图像被标记为 20 个类别用于对象检测。动作类别和人物布局标签也是可用的。在人物布局标签中,边界框是关于人体每个部分的标签(头部、手、脚)。你可以在这个网页上找到有关此数据集的详细信息:host.robots.ox.ac.uk/pascal/VOC/voc2012/index.html

PASCAL VOC 类别

图像已被分为四个主要类别:

  • 人类

  • 动物

  • 车辆

  • 室内

每张图像都标记了前面的主要类别,并且还为图像中的对象提供了特定的标签。前四个类别都有特定的标签,以下列表中我将描述它们:

  • 人类:人

  • 动物:鸟、猫、牛、狗、马、羊

  • 车辆:飞机、自行车、船、公共汽车、汽车、摩托车、火车

  • 室内:瓶子、椅子、餐桌、盆栽植物、沙发、电视/显示器

您可以参考以下图示,它将帮助您理解这个 PASCAL VOC 数据集中的标签:

PASCAL VOC 类别

图 9.2:PASCAL VOC 标记图像示例

如您在前面的图示中看到的,已经标记了两个主要类别:人和动物。对于图像中出现的特定对象,即人和羊,已经给出了特定的标记。我们不会下载这个数据集;我们将使用使用此数据集训练的预训练模型。

到现在为止,您已经多次听说过迁移学习和预训练模型这两个术语。让我们了解它们是什么。

迁移学习

在本节中,我们将探讨迁移学习是什么,以及它在我们构建实时目标检测时将如何对我们有用。我们将本节分为以下部分:

  • 什么是迁移学习?

  • 什么是预训练模型?

  • 我们为什么要使用预训练模型?

  • 我们如何使用预训练模型?

让我们从第一个问题开始。

什么是迁移学习?

我们首先将探讨迁移学习的直觉,然后我们将介绍其技术定义。让我通过一个简单的师生类比来解释这个概念。一位教师拥有多年教授某些特定主题或学科的经验。无论教师拥有什么信息,他们都会传授给学生。因此,教学的过程就是将知识从教师转移到学生的过程。您可以参考以下图示:

什么是迁移学习?

图 9.3:迁移学习概述

现在,请记住这个类比;我们将将其应用于神经网络。当我们训练一个神经网络时,它会从给定的数据集中获得知识。这个经过训练的神经网络有一些权重,帮助它从给定的数据集中学习;训练后,我们可以将这些权重以二进制格式存储。我们以二进制格式存储的权重可以被提取出来,然后转移到任何其他神经网络。因此,我们不是从头开始训练神经网络,而是转移先前训练的模型获得的知识。我们将学习到的特征转移到新的神经网络中,这将节省我们大量的时间。如果我们已经为特定应用训练了一个模型,那么我们将将其应用于新的但类似类型的应用,这将反过来帮助我们节省时间。

现在,是时候用更技术性的术语来定义迁移学习了。迁移学习是机器学习中的一个研究问题,它关注于在解决特定问题时获得的知识存储,并将其应用于不同但相关的问题。有时,迁移学习也被称为归纳迁移。让我们用一个具体的例子来巩固你的理解。如果我们构建一个在识别汽车时获得知识的机器学习模型,它也可以在我们尝试识别卡车时应用。在本章中,我们使用迁移学习来构建这个实时物体检测应用程序。

什么是预训练模型?

我想给你一个简单的解释。预训练模型是由其他人创建和构建来解决特定问题的。这意味着我们正在使用一个已经训练好的模型,并对其进行插入和播放。通过使用预训练模型,我们可以构建具有相似领域的新的应用程序。

让我给你举一个例子:假设我们想要创建一辆自动驾驶汽车。为了构建它,我们的第一步将是构建一个不错的物体识别系统。你可以花费一年或更长时间从头开始构建一个不错的图像和物体识别算法,或者你可以使用预训练模型,例如使用 PASCAL VOC 数据集构建的 Google Inception 模型或 YOLO 模型。

使用预训练模型有一些优势,如下所示:

  • 预训练模型可能不会给你 100%的准确率,但它可以节省大量的努力。

  • 你可以优化你正在工作的真实问题的准确度,而不是从头开始制作一个算法;正如我们有时所说的,没有必要重新发明轮子。

  • 现在有众多库可以帮助我们保存训练好的模型,这样我们就可以在需要时轻松加载和使用它们。

除了预训练模型为我们提供的优势外,我们还需要了解其他真实的原因,为什么我们应该使用预训练模型。那么,让我们在下一节中讨论这个问题。

为什么我们应该使用预训练模型?

当我们专注于以不同方式开发现有算法时,我们的目标将是构建一个能够超越所有其他现有算法并使其更高效的算法。如果我们只关注研究部分,那么这可以是一个开发高效且精确算法的好方法,但如果你的目标是开发一个应用程序,而这个算法是整个应用程序的一部分,那么你应该关注你如何快速且高效地构建应用程序。让我们通过一个例子来理解这一点。

在这一章中,我们想要构建实时目标检测技术。现在,我的主要焦点将是构建一个可以实时检测对象的应用程序。不仅如此;我还需要结合目标检测和实时跟踪活动。如果我忽略我的主要目标,开始制作新的但有效的目标检测算法,那么我会失去专注。我的焦点将是构建整个实时目标检测应用程序,而不仅仅是某个特定的算法。

有时候,如果我们失去了专注,试图从头开始构建算法,那么这将会花费很多时间。我们也会浪费我们的努力,因为世界上可能已经有聪明的人为我们构建了它。当我们使用这个已经开发的算法时,我们将节省时间并专注于我们的应用。在构建我们实际应用的原型之后,我们将有时间对其进行改进,以便它可以被许多人使用。

让我告诉你我的故事。几个月前,我开始构建一个可以用于安全领域的图像检测应用。当时,我不想使用预训练模型,因为我想要探索从头开始构建算法需要多少努力。所以,我开始自己动手做。我尝试了多种不同的算法,例如 SVM、多层感知器MLP)和卷积神经网络CNN)模型,但准确率真的很低。我失去了构建一个可以用于安全领域的图像检测算法应用的专注,只是开始专注于使算法更好。过了一段时间,我意识到,如果使用一个带有优化技术的预训练模型,将节省我的时间并使我能够构建一个更好的解决方案,那会更好。这次经历之后,我试图探索在我解决的问题陈述中使用迁移学习的选项,如果我发现没有迁移学习的空间,那么我会从头开始构建算法;否则,我更愿意使用预训练模型。所以,在构建算法之前,总是探索所有选项。了解应用的使用情况,并基于此构建你的解决方案。假设你正在构建自己的自动驾驶汽车;那么,实时目标检测将成为自动驾驶汽车的一个重要组成部分,但它只是整个应用的一部分;因此,如果我们使用预训练模型来检测对象,那么我们可以用我们的时间来构建一个高质量的自动驾驶汽车。

我们如何使用预训练模型?

通常,预训练模型是二进制格式,可以下载后用于我们的应用程序。一些库,如 Keras、TensorFlow、Darknet 等,已经包含了那些可以加载和使用的预模型,你可以通过某些可用的 API 来使用这些预训练网络。这些预训练网络具有通过迁移学习泛化 PASCAL VOC 或 COCO 数据集之外图像的能力。我们可以通过微调模型来修改预存在的模型。我们不想修改权重太多,因为它是在大量数据集上使用大量 GPU 训练的。预训练模型具有泛化预测和对象分类的能力,因此我们知道这个预训练模型可以泛化到足够好的程度,以给出最佳的可能结果。然而,如果我们想从头开始训练模型,我们可以改变一些超参数。这些参数可以是学习率、周期数、层大小等。让我们讨论其中的一些:

  • 学习率:学习率基本上控制了我们应该更新神经元权重多少。我们可以使用固定学习率、递减学习率、基于动量的方法或自适应学习率。

  • 周期数:周期数表示整个训练数据集应该通过神经网络的次数。我们需要增加周期数以减少测试误差和训练误差之间的差距。

  • 批处理大小:对于卷积神经网络,小批量大小通常更可取。对于卷积神经网络来说,从 16 到 128 的范围确实是一个很好的起点。

  • 激活函数:正如我们所知,激活函数向模型引入非线性。ReLU 激活函数是卷积神经网络的第一个选择。你也可以使用其他激活函数,如 tanh、sigmoid 等。

  • Dropout 用于正则化:正则化技术用于防止过拟合问题。Dropout 是深度神经网络的正则化技术。在这个技术中,我们会丢弃神经网络中的一些神经元或单元。神经元的丢弃基于概率值。默认值为 0.5,这是一个很好的起点,但我们可以根据观察到的训练误差和测试误差来改变这个值以进行正则化。

在这里,我们将使用预训练模型,如 Caffe 预训练模型、TensorFlow 目标检测模型和你只需看一眼YOLO)。对于从我们的摄像头进行实时流,我们使用 OpenCV,这对于绘制边界框也非常有用。所以,首先,让我们设置 OpenCV 环境。

设置编码环境

在本节中,我们将列出运行即将到来的代码所需的库和设备。你需要有一个至少可以以良好清晰度流式传输视频的摄像头。我们将使用 OpenCV、TensorFlow、YOLO、Darkflow 和 Darknet 库。我不会解释如何安装 TensorFlow,因为它是一个简单的过程,你可以通过点击www.tensorflow.org/install/install_linux来找到安装文档。

在本节中,我们将探讨如何首先设置 OpenCV,在接下来的章节中,我们将看到如何设置 YOLO、Darkflow 和 DarkNet。

设置和安装 OpenCV

OpenCV 代表开源计算机视觉。它旨在提高计算效率,重点在于实时应用。在本节中,你将学习如何设置 OpenCV。我使用的是 Ubuntu 16.04,并且我有一个 GPU,因此我已经安装了 CUDA 和 CUDNN。如果你还没有安装 CUDA 和 CUDNN,那么你可以参考这个 GitHub 链接:gist.github.com/vbalnt/a0f789d788a99bfb62b61cb809246d64。完成这些后,开始执行以下步骤:

  1. 这将更新软件和库:$ sudo apt-get update

  2. 这将升级操作系统并安装操作系统级别的更新:$ sudo apt-get upgrade

  3. 这是用于编译软件的:$ sudo apt-get install build-essential

  4. 这个命令安装 OpenCV 的先决条件:$ sudo apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev

  5. 这个命令安装 OpenCV 的可选先决条件:$ sudo apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev

  6. 使用此命令创建一个目录:$ sudo mkdir ~/opencv

  7. 跳转到我们刚刚创建的目录:$ cd ~/opencv

  8. 在 opencv 目录内从 GitHub 克隆以下 OpenCV 项目:

    1. $ sudo git clone https://github.com/opencv/opencv.git

    2. $ sudo git clone https://github.com/opencv/opencv_contrib.git

  9. 在 opencv 文件夹内,创建一个名为 build 的子目录: $ sudo mkdir ~/opencv/build

  10. 跳转到 build 目录或文件夹: $ cd ~/opencv/build

  11. 一旦你处于 build 文件夹位置,运行此命令。这可能需要一些时间。如果你运行此命令没有错误,则继续下一步:

    $ sudo cmake -D CMAKE_BUILD_TYPE=RELEASE \
    -D CMAKE_INSTALL_PREFIX=/usr/local \
    -D INSTALL_C_EXAMPLES=ON \
    -D INSTALL_PYTHON_EXAMPLES=ON \
    -D WITH_TBB=ON \
    -D WITH_V4L=ON \
    -D WITH_QT=ON \
    -D WITH_OPENGL=ON \
    -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules \
    -D BUILD_EXAMPLES=ON ..
    
  12. 使用此命令识别你的 CPU 核心数:$ nproc

  13. 一旦你知道 CPU 的核心数,你可以用它来处理多线程。我分配了四个 CPU 核心,这意味着有四个线程同时运行。这个命令是 $ make -j4,它会编译 OpenCV 中用 C 编写的所有类。

  14. 现在,执行以下命令以实际安装 OpenCV: $ sudo make install

  15. 将配置文件的路径添加到配置文件中:$ sudo sh -c 'echo "/usr/local/lib" >> /etc/ld.so.conf.d/opencv.conf'

  16. 使用此命令检查适当的配置:$ sudo ldconfig

一旦成功安装了 OpenCV,我们就可以使用这个库来流式传输实时视频。现在,我们将开始构建我们的基线模型。让我们开始吧!

基线模型的特征工程

为了构建基线模型,我们将使用预训练权重的 Google MobileNet SSD 检测网络的 Caffe 实现。这个模型已经在 PASCAL VOC 数据集上进行了训练。因此,在本节中,我们将探讨谷歌如何训练这个模型的方法。我们将了解 MobileNet SSD 背后的基本方法,并使用预训练模型来帮助节省时间。为了创建这种精确的模型,我们需要大量的 GPU 和训练时间,所以我们使用预训练模型。这个预训练的 MobileNet 模型使用卷积神经网络CNN)。

让我们看看 MobileNet 如何使用 CNN 提取特征。这将帮助我们理解 CNN 背后的基本思想,以及 MobileNet 是如何被使用的。CNN 网络由层组成,当我们向 CNN 提供图像时,它会扫描图像的区域,并尝试使用区域提议方法提取可能的物体。然后,它找到包含物体的区域,使用扭曲的区域,并生成 CNN 特征。这些特征可以是像素的位置、边缘、边缘的长度、图像的纹理、区域的尺度、图片的明暗度、物体部分等等。CNN 网络通过自身学习这些类型的特征。您可以参考以下图示:

基线模型的特征工程

图 9.4:理解 CNN 中的特征提取

如您在前面的图像中看到的,图像区域已经被扫描,由 C1 和 S1 组成的第一个 CNN 层将生成特征。这些特征可以识别构成整个物体的边缘表示。在第二阶段,CNN 层学习有助于神经网络识别物体部分的特征表示。在最后阶段,它学习所有必要的特征,以便在给定的输入图像中识别存在的物体。如果您想探索 CNN 网络的每个方面,可以参考cs231n.github.io/convolutional-networks/。不用担心;我们将在下一节中概述 CNN 架构,以便您了解目标检测是如何工作的。现在,是时候探索 CNN 了。

选择机器学习算法

我们已经知道我们正在使用卷积神经网络CNN)来开发这个应用程序。你可能想知道为什么我们选择了 CNN 而不是其他神经网络。你可能已经知道这个问题的答案。我们选择 CNN 有三个原因:

  • 现今存在的、经过精心手工标注的视觉数据量

  • 通过 GPU 打开优化大门的负担得起的计算机器

  • CNN 的各种架构优于其他算法

由于这些原因,我们选择了带有 SSD 的 CNN。在开发基线模型的过程中,我们将使用 MobileNet,它使用带有单次检测器SSD)技术的 CNN。因此,在本节中,我们将查看开发 MobileNet 时使用的 CNN 架构。这将帮助我们理解预训练模型。

MobileNet SSD 模型的架构

MobileNet SSD 速度快,在图像和视频中执行对象检测任务很好。这个模型比基于区域的卷积神经网络R-CNN)更快。SSD 之所以能达到这种速度,是因为它以不同的方式扫描图像和视频帧。

在 R-CNN 中,模型在两个不同的步骤中执行区域提议和区域分类,如下所示:

  • 首先,他们使用区域提议网络来生成感兴趣的区域

  • 之后,全连接层或正敏感宪法层对所选区域中的对象进行分类。

这些步骤与 R-CNN 相当,但 SSD 在单次操作中完成这些步骤,这意味着它同时预测边界框和边界框中出现的对象的类别。SDD 在图像或视频流以及一组基本真实标签作为输入时执行以下步骤:

  • 将图像通过一系列卷积层,生成不同尺寸矩阵形式的特征图集合。输出可以是 10×10 矩阵、6×6 矩阵或 3×3 矩阵。

  • 对于每个特征图中的每个位置,使用 3×3 卷积滤波器来评估一组默认边界框。这些生成的默认边界框相当于锚框,这些锚框是使用 Faster R-CNN 生成的。

  • 对于每个框,同时预测对象的边界框偏移和类别概率。

  • 在训练过程中,将真实框与这些预测框进行匹配。这种匹配是通过使用交集并集(IoU)来完成的。最佳预测框将被标记为正边界框。这发生在每个边界框上。这里考虑的 IoU 的真实值超过 50%。

看看以下图,以图形方式表示。MobileNets 具有简化的架构,使用深度可分离卷积来构建适用于移动和嵌入式视觉应用的超轻量级深度神经网络。MobileNets 是计算机视觉应用中更高效的机器学习模型。您也可以参考 MobileNet 的原始论文,链接为arxiv.org/pdf/1704.04861.pdf

MobileNet SSD 模型架构

图 9.5:MobileNet SSD 的基本架构模块

图片来源:arxiv.org/pdf/1704.04…

如前图所示,我们使用了标准的卷积网络和深度卷积网络。MobileNet SDD 使用了 ReLU 激活函数。您可以通过以下图了解该网络具有什么样的滤波器形状:

MobileNet SSD 模型架构

图 9.6,MobileNet 主体架构

图片来源:arxiv.org/pdf/1704.04…

如果您想解释这个表格,那么让我们考虑一个例子。如果我们有一个原始图像,像素大小为 224×224,那么这个 Mobilenet 网络将图像缩小到 7×7 像素;它还有 1,024 个通道。之后,有一个平均池化层作用于所有图像,生成一个 1×1×1,024 大小的向量,实际上就是一个包含 1,024 个元素的向量。如果您想了解更多关于 MobileNet SSD 的信息,请参考以下资源:

现在,让我们继续到实现部分。

构建基线模型

在本节中,我们将探讨编码部分。您可以参考以下 GitHub 链接中的代码:github.com/jalajthanaki/Real_time_object_detection/tree/master/base_line_model

首先,从提供的链接下载项目并安装 OpenCV,如本章前面所述的信息。当您下载此项目文件夹时,其中包含一个使用 Caffe 库实现的预训练的 MobileNet SSD,但在这里,我们使用预训练的二进制模型。我们使用 OpenCV 加载预训练模型以及从摄像头流式传输视频流。

在代码中,首先,我们指定需要导入的库和定义用于运行脚本的命令行参数。我们需要提供参数文件和预训练模型。参数文件的名称是MobileNetSSD_deploy.prototxt.txt,预训练模型的文件名是MobileNetSSD_deploy.caffemodel。我们还定义了模型可以识别的类别。之后,我们将使用 OpenCV 加载预训练模型。您可以在以下屏幕截图中参考此阶段的编码:

构建基线模型

图 9.7:基线模型的代码片段

现在,让我们看看如何从我们的网络摄像头中流式传输视频。在这里,我们使用imutils库及其视频 API 从网络摄像头流式传输视频。使用 start 函数,我们将开始流式传输,然后定义帧大小。我们获取帧大小并将其转换为 blob 格式。此代码始终验证检测到的对象置信度分数将高于最小置信度分数或置信度分数的最小阈值。一旦我们得到更高的置信度分数,我们将为这些对象绘制边界框。我们可以看到到目前为止已经检测到的对象。您可以参考以下图中的基线模型视频流:

构建基线模型

图 9.8:基线模型视频流的代码片段

为了停止流式传输,我们需要通过按 Q 键或 Ctrl + C 来中断循环,并且我们需要注意,当我们关闭程序时,所有窗口和进程都将适当地停止。您可以在以下屏幕截图中看到这一点:

构建基线模型

图 9.9:结束脚本的代码片段

在我们运行脚本测试之前,让我们了解对象检测应用程序的测试指标。一旦我们理解了测试指标,我们将运行代码并检查我们获得了多少精度。

理解测试指标

在本节中,我们将介绍测试指标。我们将查看两个矩阵,这两个矩阵将帮助我们理解如何测试对象检测应用程序。这些测试矩阵如下:

  • 交并比 (IoU)

  • 平均精度均值 (mAP)

交并比 (IoU)

对于检测,使用 IoU(交并比)是为了确定对象提议是否正确。这是确定对象检测是否完美完成的一种常规方法。IoU 通常取提议的对象像素集 A 和真实对象像素集 B,并基于以下公式计算 IoU:

交并比 (IoU)

通常,IoU > 0.5,这意味着这是一个命中或识别了对象的像素或边界框;否则,它失败。这是对 IoU 的更正式理解。现在,让我们看看其背后的直觉和含义。让我们以一个图像为参考,帮助我们理解这个矩阵的直觉。您可以参考以下截图:

交并比 (IoU)

图 9.10:理解 IoU 背后的直觉

上述截图是检测图像中停车标志的一个示例。预测的边界框用红色绘制,属于这个红色框的像素被认为是集合 A 的一部分,而真实边界框用绿色绘制,属于这个绿色框的像素被认为是集合 B 的一部分。我们的目标是计算这些边界框之间的交并比。因此,当我们的应用程序绘制边界框时,它应该至少与真实边界框匹配超过 50%,这被认为是一个好的预测。IoU 的方程如下所示:

交并比 (IoU)

图 9.11:基于直观理解的 IoU 方程

在现实中,我们的预测边界框的(x, y)坐标与真实边界框的(x, y)坐标完全匹配的机会很少。在下面的图中,您可以看到各种关于 IoU 较差、较好和优秀的示例:

交并比 (IoU)

图 9.12:各种 IoU 边界框示例

IoU 帮助我们确定应用程序识别对象边界和区分不同对象的能力。现在,是时候了解下一个测试指标了。

平均精度均值

在本节中,我们将介绍平均精度均值mAP)。在目标检测中,首先,我们识别目标边界框,然后将其分类到某个类别。这些类别有一些标签,我们将适当的标签提供给识别出的对象。现在,我们需要测试应用程序分配这些标签的效果如何,这意味着我们如何将对象分类到不同的预定义类别中。对于每个类别,我们将计算以下内容:

  • 真阳性 TP(c):预测的类别是 C,且该对象实际上属于类别 C

  • 假阳性 FP(c):预测的类别是 C,但实际上该对象不属于类别 C

  • 类 C 的平均精度由以下方程给出:平均精度均值

因此,对于所有类别,我们需要计算 mAP,其计算公式如下:

平均精度均值

如果我们想要更好的预测,那么我们需要将 IoU 从 0.5 增加到更高的值(最高可达 1.0,这将是最完美的)。我们可以用这个方程表示:mAP[@p],其中 p ∈ (0,1)是 IoU。mAP[@[0.5:0.95]]表示 mAP 是在多个阈值上计算,然后再次平均。

现在,让我们测试基线模型并检查此实现的 mAP 值。

测试基线模型

在本节中,我们将运行基线模型。为了运行脚本,我们需要跳转到放置名为real_time_object_detection.py的脚本的位置,并在命令提示符中执行以下命令:

测试基线模型

图 9.13:基线方法的执行

看看下面的图。在这里,我刚刚放置了示例图像,但当你运行脚本时,你可以看到整个视频。这里是使用基线方法进行实时物体检测的整个视频链接:drive.google.com/drive/folders/1RwKEUaxTExefdrSJSy44NugqGZaTN_BX?usp=sharing

测试基线模型

图 9.14:基线方法的输出(图像是视频流的一部分)

在这里,MobileNet SSD 的 mAP 为 71.1%。你将在下一节中学习如何优化这种方法。首先,我们将列出下一次迭代中我们可以改进的点。所以,让我们跳到下一节。

现有方法的弊端

虽然 MobileNet SSD 速度快且给出了良好的结果,但它仍然无法识别像杯子、钢笔等类别。因此,我们需要使用在多种物体上训练过的预训练模型。在即将到来的迭代中,我们需要使用预训练模型,例如 TensorFlow 对象检测 API,它将能够识别与基线方法相比的不同物体。所以现在,让我们看看我们将如何优化现有方法。

如何优化现有方法

如前所述,为了优化现有方法,我将使用 TensorFlow 对象检测 API。你可以通过以下链接参考 Google 的 TensorFlow GitHub 仓库中的此 API:github.com/tensorflow/models/tree/master/research/object_detection。此 API 使用 COCO 数据集以及 PASCAL VOC 数据集进行训练;因此,它将具有识别多种类别的功能。

理解优化过程

对于我们来说,最重要的部分是如何使用各种预训练模型。步骤如下:

  1. 首先,使用此链接拉取 TensorFlow 模型存储库:github.com/tensorflow/models

  2. 一旦您拉取了仓库,您就可以找到我提到的 iPython Notebook,以了解如何使用预训练模型,并找到 iPython 笔记本的链接github.com/tensorflow/models/blob/master/research/object_detection/object_detection_tutorial.ipynb

  3. 在这里,我们使用了 SSD 与 MobileNet,但我们使用的是检测模型库。这个模型是在 COCO 数据集上训练的,它们的版本基于模型的速度和性能。您可以从以下链接下载预训练模型:github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md。我已经将这些部分组合在一起,因此对每个人来说实现代码都很容易。

  4. 主要的是,这个模型是使用 SSD 方法训练的,但它使用了如 kitti 数据集和 Open Image 数据集等数据集。因此,这个模型能够检测更多的对象,并且更加通用。Kitti 数据集的链接是www.cvlibs.net/datasets/kitti/,Open Image 数据集的链接是github.com/openimages/dataset

一旦我们下载了仓库和预训练模型,我们将加载预训练模型。在 TensorFlow 中,众所周知,模型以.pb 文件保存。一旦我们加载了模型,我们将使用 OpenCV 来流式传输视频。在下一节中,我们将实现修订方法的代码。

实现修订方法

在本节中,我们将了解修订方法的实现。您可以参考以下 GitHub 链接:github.com/jalajthanaki/Real_time_object_detection/tree/master/revised_approach,其中包含预训练模型和 TensorFlow 的物体检测文件夹。在我们开始编写代码之前,我将提供有关此方法文件夹结构的详细信息。您可以参考以下图示:

实现修订方法

图 9.15:理解修订方法的文件夹结构

这里是下载自 TensorFlow 模型仓库的对象检测文件夹:github.com/tensorflow/models/tree/master/research/object_detection。在 utils 文件夹中,有一些辅助函数帮助我们流式传输视频。帮助我们运行脚本的主要脚本为 object_detection_app.py。预训练模型已保存在对象检测文件夹中。该文件夹中预训练模型的路径如下:~/PycharmProjects/Real_time_object_detection/revised_approach/object_detection/ssd_mobilenet_v1_coco_11_06_2017/frozen_inference_graph.pb

现在,让我们一步一步地查看编码实现。在第一步中,我们将导入依赖库并加载预训练模型。您可以参考以下图示:

实现改进方法

图 9.16:改进方法中加载预训练模型的代码片段

在这个模型中,有 90 种不同的对象可以被识别。一旦我们加载了模型,下一步就是 detect_objects() 函数,它用于识别对象。一旦对象被识别,就会为该对象绘制边界框,并且我们同时在这些对象上运行预训练模型,以便我们可以得到对象的识别标签,无论是杯子、瓶子、人等等。您可以参考以下图示:

实现改进方法

图 9.17:detect_objects()函数的代码片段

在此之后,我们有 worker() 函数,它帮助我们流式传输视频以及执行一些 GPU 内存管理。您可以参考以下图示:

实现改进方法

图 9.18:worker()函数的代码片段

如您所见,我们已定义了 GPU 内存分数,以及检测对象时使用的颜色类型。现在,让我们看看脚本的主要功能。在主函数中,我们定义了一些可选参数及其默认值。这些参数的列表如下:

  • 摄像头设备索引:--source=0

  • 视频流中帧的宽度 --width= 500

  • 视频流中帧的高度 --height= 500

  • 工作者数量 --num-workers=2

  • 队列的大小 --queue-size=5

您可以参考以下图示中显示的主函数实现:

实现改进方法

图 9.19:主函数的代码片段

当我们运行脚本时,我们可以看到即将给出的图示中的输出。在这里,我们放置了图像,但您可以通过此链接查看视频:

drive.google.com/drive/folders/1RwKEUaxTExefdrSJSy44NugqGZaTN_BX?usp=sharing

实现改进方法

图 9.20:改进方法的结果输出

一旦我们结束视频,资源和过程也应该结束。为此,我们将使用以下图中提供的代码:

实现改进方法

图 9.21:脚本终止后释放资源的代码片段

测试改进方法

一旦我们执行了这个方法,我们就可以识别杯子、钢笔等对象。因此,我们可以说我们的基线方法是肯定得到了改进的,并且有一些修改后的标签,例如沙发(在这个方法中,被识别为 couch)。除此之外,如果我们谈论这个预训练模型的 mAP,那么根据文档,在 COCO 数据集上,这个模型大约有 52.4%的准确性。在我们的输入中,我们将大约有 73%的准确性。这种方法识别了更多不同类别的对象,这是一个很大的优势。

在下一节中,我们将讨论我们可以用来找到最佳解决方案的要点。

理解改进方法中的问题

我们已经尝试了既快又准确的方法,但我们需要一个既快又准确且经过优化的方法。这些是我们开发最佳解决方案时需要记住的要点:

  • 基于 SSD 的方法很好,但当你使用 COCO 或 PASCAL VOC 数据集训练自己的模型时,它的准确性并不高。

  • TensorFlow 检测模型库在 COCO 测试数据集上的 mAP 分数将在 20%到 40%之间。因此,我们需要探索其他技术,这些技术可以帮助我们在处理和目标检测准确性方面取得更好的结果。

因此,在下一节中,我们将探讨可以帮助我们优化改进方法的方法。

最佳方法

在本节中,我们将尝试名为YOLO的方法。YOLO 代表 You Only Look Once。这项技术提供了良好的准确性,速度快,内存管理简单。本节将分为两部分:

  • 理解 YOLO

  • 使用 YOLO 实现最佳方法

在第一部分,我们将了解 YOLO 的基本知识。在实现过程中,我们将使用预训练的 YOLO 模型来使用 YOLO。那么,让我们开始吧!

理解 YOLO

YOLO 是一种最先进的实时目标检测系统。在 GPU Titan X 上,它以 40-90 FPS 的速度处理图像,在 PASCAL VOC 数据集上的 mAP 为 78.6%,在 coco 测试-dev 数据集上的 mAP 为 48.1%。因此,现在我们将探讨 YOLO 是如何工作以及如何处理图像以识别对象的。我们使用 YOLOv2(YOLO 版本 2),因为它是一个更快的版本。

YOLO 的工作原理

YOLO 重新定义了目标检测问题。它将对象识别任务视为从图像像素到边界框坐标和类别概率的单个回归问题。单个卷积网络同时预测多个边界框及其类别概率。YOLO 在完整图像上训练并直接优化检测性能。与传统方法相比,这种方法具有以下优点:

  • YOLO 非常快。这是因为,在 YOLO 中,帧检测是一个回归问题,我们不需要使用复杂的流程。

  • 我们可以在测试时简单地在我们新的图像上运行我们的神经网络来预测检测。我们的基础网络在 Titan X GPU 上以每秒 45 帧的速度运行,没有批处理,而快速版本则超过 150 fps。这意味着我们可以以小于 25 毫秒的延迟实时处理流媒体视频。

YOLO 在预测关于类别及其外观的信息时,会全局处理图像。它还学习对象的泛化。

YOLO 将输入图像划分为一个 S×S 网格。如果一个物体的中心落在网格单元中,那么该网格单元负责检测该物体。每个网格单元预测 B 个边界框及其置信度分数。这些置信度分数反映了模型对框包含对象的信心程度以及它认为预测的框有多准确。因此,正式来说,我们可以通过这个符号定义 YOLO 的置信度机制。我们定义置信度为 Pr(object) * IoU。如果该单元格中没有对象存在,那么置信度分数应该是零;否则,我们希望置信度分数等于预测框与真实框之间的 IoU。每个边界框由五个预测组成:x, y, w, h 和置信度。其中(x, y)坐标表示框相对于网格单元边界的中心。w 和 h 代表宽度和高度,它们相对于整个图像进行预测。最后,置信度分数代表 IoU。对于每个网格单元,它预测 C 个条件类别概率,Pr(Classi|Object)。这些概率是在包含对象的网格单元上条件化的。我们每个网格单元只预测一组类别概率,无论边界框 B 的数量是多少。在测试时,我们乘以条件类别概率和由这个方程定义的个体框置信度预测:

YOLO 的工作原理

前面的方程为我们提供了每个框的类别特定置信度分数。这个分数包含了该类别出现在框中的概率以及预测框与对象拟合得有多好。YOLO 整个过程的图示如下所示:

YOLO 的工作原理

图 9.22:YOLO 目标检测的图示表示

现在,让我们了解 YOLO 架构的基本知识。

YOLO 的架构

在本节中,你将学习 YOLO 架构的基本知识。在 YOLO 中,有 24 个卷积层,后面跟着两个全连接层。YOLO 没有使用 GoogleNet 中的 inception 模块,而是简单地使用 1×1 的降维层,然后是 3×3 的卷积层。Fast YOLO 使用具有较少卷积层的神经网络。我们使用 9 层而不是 24 层。在这些层中,我们也使用了较少的过滤器。除此之外,在训练和测试期间,YOLO 和 Fast YOLO 的所有参数都是相同的。你可以参考以下图中的架构:

YOLO 的架构

图 9.23:YOLO 架构

现在,让我们继续到实现部分。

使用 YOLO 实现最佳方法

为了实现 YOLO,我们需要安装 Cython 模块。除此之外,你可以使用 Darknet 或 Darkflow,它是 Darknet 上的 TensorFlow 包装器。Darknet 使用 C 和 CUDA 编写,因此速度相当快。在这里,我们将实现这两种选项。在实现之前,我们需要设置环境。实现部分应分为两个部分:

  • 使用 Darknet 实现

  • 使用 Darkflow 的实现

你可以参考此 GitHub 仓库中的所有代码:github.com/jalajthanaki/Real_time_object_detection_with_YOLO

使用 Darknet 实现

我们按照以下步骤使用 Darknet 实现 YOLO:

  • 为 Darknet 设置环境

  • 编译 Darknet

  • 下载预训练权重

  • 对图像运行目标检测

  • 对视频流运行目标检测

为 Darknet 设置环境

在这一步,我们需要下载 Darknet 的 GitHub 仓库。我们可以使用以下命令完成此操作。你可以将此仓库下载到任何路径:

$ git clone https://github.com/pjreddie/darknet

执行此命令后,将创建名为 Darknet 的目录。之后,你可以跳到下一步。

编译 Darknet

下载完 Darknet 后,我们需要切换到名为 Darknet 的目录。之后,我们需要编译 Darknet。因此,我们需要依次执行以下命令:

$ cd darknet
$ make

下载预训练权重

配置文件已经包含在 darknet 目录中的 cfg/ 子目录内。因此,通过执行以下命令,你可以下载 YOLO 模型的预训练权重:

$ wegt https://pjreddie.com/media/files/yolo.weights

此下载可能需要一些时间。一旦我们有了预训练权重,我们就可以运行 Darknet。

对图像运行目标检测

如果你想要识别图像中的对象,那么你需要执行以下命令:

./darknet detect cfg/yolo.cfg yolo.weights data/dog.jpg

你可以在以下图中查看此命令的输出:

对图像运行目标检测

图 9.24:使用 Darknet 对图像进行目标检测的输出

现在,让我们在视频流上实现 YOLO。

在视频流上运行目标检测

我们可以使用以下命令在视频流上运行 YOLO:

./darknet detector demo cfg/coco.data cfg/yolo.cfg yolo.weights <video file>

在这里,我们需要传递视频的路径。有关更多信息,您可以参考以下 Darknet 文档:pjreddie.com/darknet/yolo/。现在,让我们了解 Darkflow 的实现。

使用 Darkflow 的实现

在这个实现中,您需要参考名为 Darkflow 的文件夹中给出的代码。我们需要执行以下步骤:

  1. 安装 Cython

  2. 构建已提供的设置文件

  3. 测试环境

  4. 加载模型并在图像上运行目标检测

  5. 加载模型并在视频流上运行目标检测

安装 Cython

为了安装 Cython,我们需要执行以下命令。这个 Cython 包是必需的,因为 Darkflow 是一个 Python 包装器,它使用来自 Darknet 的 C 代码:

$ sudo apt-get install Cython

一旦安装了 Cython,我们就可以构建其他设置。

构建已提供的设置文件

在这个阶段,我们将执行设置必要的 Cython 环境的命令。命令如下:

$ python setup.py build_ext --inplace

当我们执行此命令时,我们将在克隆的 Darkflow 目录中使用./flow而不是flow,因为 Darkflow 没有全局安装。一旦此命令成功运行,我们需要测试是否完美安装了所有依赖项。您可以使用以下命令下载预训练权重:

$ wegt https://pjreddie.com/media/files/yolo.weights
测试环境

在这个阶段,我们将测试 Darkflow 是否运行得完美。为了检查这一点,我们需要执行以下命令:

$ ./flow --h

您可以参考以下图示:

测试环境

图 9.25:Darkflow 成功测试结果

一旦您看到前面的输出,您就会知道您已成功配置了 Darkflow。现在,让我们运行它。

加载模型并在图像上运行目标检测

我们可以在图像上运行 Darkflow。为此,我们需要加载 YOLO 预训练权重、配置文件和图像路径,然后您可以执行以下命令:

./flow --imgdir sample_img/ --model cfg/yolo.cfg --load ../darknet/yolo.weights

如果您想将目标检测保存为 json 格式,也是可能的。您需要执行以下命令:

./flow --imgdir sample_img/ --model cfg/yolo.cfg --load ../darknet/yolo.weights --json

您可以在sample_img/out文件夹内看到输出;参考以下图示:

加载模型并在图像上运行目标检测

图 9.26:使用 Darkflow 预测对象输出的图像

您也可以参考以下图示:

加载模型并在图像上运行目标检测

图 9.27:图像中检测到的对象的 json 输出

加载模型并在视频流上运行目标检测

在本节中,我们将对视频流运行目标检测。首先,我们将看到如何使用网络摄像头并执行目标检测。该命令如下:

./flow --model cfg/yolo.cfg --load ../darknet/yolo.weights --demo camera --saveVideo --gpu 0.60

你可以参考以下图表。你可以在以下链接中查看视频:drive.google.com/drive/folders/1RwKEUaxTExefdrSJSy44NugqGZaTN_BX?usp=sharing

加载模型并在视频流上运行目标检测

图 9.28:使用 Darkflow 对网络摄像头视频流进行目标检测的输出

我们也可以为预录制的视频运行 Darkflow。为此,你需要运行以下命令:

./flow --model cfg/yolo.cfg --load ../darknet/yolo.weights --demo ~/Downloads/Traffic.avi --saveVideo --gpu 0.60

你可以参考以下图表。你可以在以下链接中查看视频:drive.google.com/drive/folders/1RwKEUaxTExefdrSJSy44NugqGZaTN_BX?usp=sharing

加载模型并在视频流上运行目标检测

图 9.29:使用 Darkflow 对预录制的视频进行目标检测的输出

在这两个命令中,我们都使用了—save Video 标志来保存视频和—gpu 0.60 标志,它使用 GPU 的 60% 内存。使用这种方法,我们将获得 78% 的准确率。

摘要

在本章中,你学习了迁移学习。我们探索了不同的库和方法,以构建实时目标检测应用程序。你学习了如何设置 OpenCV,并了解了它在构建基线应用程序中的实用性。在基线方法中,我们使用了使用 caffe 深度学习库训练的模型。之后,我们使用 TensorFlow 构建了实时目标检测,但最终我们使用了预训练的 YOLO 模型,它优于其他所有方法。这种基于 YOLO 的方法为我们提供了更通用的目标检测应用程序方法。如果你对构建计算机视觉的创新解决方案感兴趣,那么你可以报名参加 VOC 挑战赛。这会提升你的技能,并给你一个学习的机会。你可以通过以下链接获取更多信息:host.robots.ox.ac.uk/pascal/VOC/(PASCAL VOC 挑战赛 2005-2012)。你也可以构建自己的算法,检查结果,并将你的结果与现有方法进行比较,如果它优于现有方法,你绝对可以在知名期刊上发表论文。通过使用 YOLO 方法,我们在 PASCAL VOC 数据集上获得了 78% 的平均精度均值(mAP),当将此模型应用于任何视频或图像时,它表现得相当不错。本章的代码归功于 Adrian Rosebrock、Dat Tran 和 Trieu。我们根据 COCO 数据集或 PASCAL VOC 数据集得到的 mAP 来定义 mAP 分数。

在下一章中,我们将探讨属于计算机视觉领域的另一个应用:人脸检测和面部表情检测。为了构建这个应用,我们将使用深度学习技术。所以,继续阅读吧!

第十章. 面部识别与面部情感识别

在上一章中,我们探讨了如何使用卷积神经网络和 YOLO(You Only Look Once)算法检测诸如汽车、椅子、猫和狗等物体。在本章中,我们将检测人脸。除此之外,我们还将研究人脸的表情,例如人脸看起来快乐、中性、悲伤等。因此,本章将很有趣,因为我们将关注一些最新的面部检测和面部情感识别技术。我们将把本章分为两部分:

  • 面部检测

  • 面部情感识别

首先,我们将介绍面部检测的工作原理,然后我们将继续到面部情感识别部分。总的来说,我们将在本章中涵盖以下主题:

  • 介绍问题陈述

  • 设置编码环境

  • 理解面部识别的概念

  • 面部识别实现的途径

  • 理解面部情感识别的数据集

  • 理解面部情感识别的概念

  • 构建面部情感识别模型

  • 理解测试矩阵

  • 测试模型

  • 现有方法的局限性

  • 如何优化现有方法

    • 理解优化过程
  • 最佳方法

    • 实施最佳方法
  • 总结

介绍问题陈述

我们希望开发两个应用。一个应用将识别人脸,另一个将识别人脸的情感。我们将在本节中讨论这两个应用。我们将探讨我们到底想开发什么。

面部识别应用

此应用应基本能够从图像或实时视频流中识别出人脸。参考以下照片,它将帮助你理解我所说的从图像或实时视频流中识别人脸的含义:

面部识别应用

图 10.1:理解面部识别应用演示输出

图片来源:unsplash.com/photos/Q13l…

如前图(图 10.1)所示,当我们提供任何图像作为输入时,在第一步,机器可以识别图像中存在多少人脸。作为输出,我们可以得到人脸的裁剪图像。

此外,我还希望应用能够根据面部识别出人的名字。我想你熟悉这类应用。让我提醒你。当你将图片上传到 Facebook 时,Facebook 的人脸识别机制会立即识别出图片中的人名,并建议你在图片中标记他们。在这里,我们将开发类似的功能,即面部识别应用。现在让我们继续到应用的另一部分。

面部情感识别应用

在这个应用的部分,我们想要构建一个能够检测人脸情绪类型的应用程序。我们将尝试识别以下七种情绪:

  • 愤怒

  • 厌恶

  • 恐惧

  • 幸福

  • 悲伤

  • 惊讶

  • 中性

因此,我们将面部情绪分为这七种类型。这种应用程序将有助于了解人们正在经历什么样的感受,这种洞察力将有助于进行情感分析、肢体语言分析等。

在这里,我们首先构建人脸识别应用程序,然后继续构建人脸情绪识别应用程序。

设置编码环境

在本节中,我们将设置人脸识别应用程序的编码环境。我们将查看如何安装依赖项。我们将安装以下两个库:

  • dlib

  • face_recognition

让我们开始安装过程。

安装 dlib

为了安装 dlib 库,我们需要执行以下步骤。我们可以在 Linux 操作系统(OS)或 macOS 上安装此库。让我们按照逐步说明进行:

  1. 通过执行此命令下载 dlib 的源代码:

    sudo git clone https://github.com/davisking/dlib.git.
    
  2. 现在通过执行此命令跳转到 dlib 目录:cd dlib

  3. 现在我们需要构建主要的dlib库,因此我们需要逐步执行以下命令:

    1. sudo mkdir build

    2. cd build

    3. cmake .. -DDLIB_USE_CUDA=0 -DUSE_AVX_INSTRUCTIONS=1

    4. cmake --build

一旦项目成功构建,你可以进入下一个安装步骤。你还需要安装 OpenCV。OpenCV 的安装步骤已在第十章中给出。

安装 face_recognition

为了安装 face_recognition 库,我们需要执行以下命令:

$ sudo pip install face_recognition (This command is for python 2.7)
$ sudo pip3 install face_recognition (This command is for python 3.3+)

之前的命令只有在dlib库完美安装的情况下才会安装face_recognition库。

一旦安装了前两个库,我们就可以进入下一部分,我们将讨论人脸识别的关键概念。

理解人脸识别的概念

在本节中,我们将探讨人脸识别的主要概念。这些概念将包括以下主题:

  • 理解人脸识别数据集

  • 人脸识别算法

理解人脸识别数据集

你可能会想知道为什么我直到现在还没有讨论与数据集相关的内容。这是因为我不想通过提供两个不同应用程序的数据集的所有细节来混淆你。我们将在这里覆盖的数据集将用于人脸识别

如果你想要从头开始构建人脸识别引擎,则可以使用以下数据集:

  • CAS-PEAL 人脸数据集

  • 野生人脸标签

让我们进一步详细讨论它们。

CAS-PEAL 人脸数据集

这是一个用于面部识别任务的大型数据集。它包含各种类型的人脸图像。它包含具有不同变化来源的人脸图像,特别是用于面部识别任务的姿态、情绪、配饰和光照(PEAL)。

这个数据集包含 1,040 个人的 99,594 张图片,其中 595 人是男性,445 人是女性。捕捉到的个人图像具有不同的姿态、情绪、配饰和光照。请参考以下照片查看这一点。如果您想查看样本数据集,也可以参考以下链接:www.jdl.ac.cn/peal/index.html

CAS-PEAL 人脸数据集

图 10.2:CAS-PEAL 人脸数据集样本图像

图片来源:www.jdl.ac.cn/peal/Image/…

您可以从以下链接下载这个数据集:www.jdl.ac.cn/peal/download.htm

LFW 人脸数据集

这个数据集也被称为 LFW 数据集。它在face_recognition库中被使用。我们将使用这个库来构建我们的面部识别应用。这个数据集包含了从网络上收集的超过 13,000 张人脸图片。每张人脸都标有图片中人物的姓名。因此,这个数据集是一个标记数据集。数据集中有 1,680 个人的两张或更多不同的人脸图像。您可以通过以下图表来参考样本数据集:

LFW 人脸数据集

图 10.3:LFW 数据集的样本图像

图片来源:vis-www.cs.umass.edu/lfw/person/…

注意

您可以通过点击vis-www.cs.umass.edu/lfw/index.html了解更多关于这个数据集的信息。您也可以通过相同的链接下载数据集。您还可以通过点击www.vision.caltech.edu/Image_Datasets/Caltech_10K_WebFaces/来参考 Caltech 10,000 网络人脸数据集。您还应该参考 INRIA 人脸数据集,这将非常有用。INRIA 人脸数据集的链接是pascal.inrialpes.fr/data/human/.

为了构建面部识别应用,我们将使用face_recognition库。我们正在使用这个库通过其 API 提供的预训练模型。我们肯定会探索这个预训练模型和库背后的算法和概念。所以,让我们开始吧!

面部识别算法

在本节中,我们将查看用于人脸识别的核心算法。该算法的名称是方向梯度直方图HOG)。我们将了解 HOG 在人脸识别任务中的应用。人脸识别(FR)任务基本上是一个分类任务,因为我们不仅从图像中检测人脸,还试图通过人脸识别出人的名字。HOG 是一个很好的尝试选项。

另一种方法是使用卷积神经网络(CNN)。在本节中,我们还将介绍 CNN 在人脸识别任务中的应用。因此,让我们从 HOG 开始吧!

方向梯度直方图(HOG)

HOG 算法是用于人脸识别的最佳方法之一。HOG 方法由 Dalal 和 Triggs 在他们的开创性 2005 年论文中提出,该论文可在lear.inrialpes.fr/people/triggs/pubs/Dalal-cvpr05.pdf找到。HOG 图像描述符和线性支持向量机可以用来训练高度准确的分类器,这些分类器可以分类人脸检测器。因此,HOG 也可以应用于人脸识别任务。首先,我们将介绍算法背后的基本直觉。

HOG 是一种特征描述符。特征描述符是一种通过提取有用信息并忽略其他信息来简化图像的图像表示。在这里,我们的重点将放在人脸上。因此,如果有其他物体,我们将忽略它们。LWF 数据集噪声较少,因此生成准确特征描述符的任务相对容易。逐步过程如下:

第 1 步:为了在图像中找到人脸,我们首先将彩色图像转换为黑白图像,因为我们不需要颜色数据来识别人脸。参考以下图表:

方向梯度直方图(HOG)

图 10.4:将彩色图像转换为黑白图像

第 2 步:在这个步骤中,我们将一次查看图像中的每个单独的像素。对于每个单独的像素,我们希望查看直接围绕它的像素。参考以下图表:

方向梯度直方图(HOG)

图 10.5:扫描图像中每个单独像素的过程

第 3 步:在这里,我们的目标是找出当前像素相对于其直接周围的像素有多暗。我们需要画一个箭头,指示图像像素变暗的方向。为了实现这一点,我们需要扫描整个图像。参考以下图表:

方向梯度直方图(HOG)

图 10.6:从亮像素到暗像素的箭头方向

如前图所示,我们考虑了一个像素及其周围的像素。通过观察像素,我们可以轻松地判断箭头头指向较暗的像素。

第 4 步:如果我们对图像中的每个像素重复此过程,那么最终每个像素都将被箭头替换。这些箭头被称为梯度。这些梯度显示了整个图像中从亮到暗像素的流动。参考以下图示:

方向梯度直方图(HOG)

图 10.7:整个图像的梯度箭头

在前面的图中,您可以看到在为输入图像生成梯度后的输出类型。扫描整个图像可能看起来像是一件随机的事情,但用梯度替换像素是有原因的。如果我们直接分析原始像素值,那么同一个人的非常暗和非常亮的图像将具有完全不同的像素值,这在我们尝试识别该人的面部时会使事情变得更加复杂。在这里,我们考虑像素亮度变化的方向。我们发现,同一个人的非常暗和非常亮的图像最终都会得到面部完全相同的表示。这种表示对我们处理面部识别任务来说将很容易处理。这就是生成整个图像梯度的主要原因。然而,尽管如此,我们将在下一步讨论一个挑战。

第 5 步:为每个像素保存梯度给我们提供了太多的信息,而且我们可能会以低效的方式使用这些信息。因此,我们需要获得我们将用于 FR 任务的最基本信息。我们将通过只考虑更高层次上的亮度和暗度的基本流动来实现这一点,这样我们就可以看到图像的基本模式。实现这一点的过程在第 6 步中给出。

第 6 步:我们将此图像分解成每个 16 x 16 像素的小方块。在每个方块中,我们将计算每个主要方向上的梯度点数,这意味着我们将计算有多少箭头向上、向下、向右、向左等。在计数之后,我们将用最强的箭头方向替换图像中的那个方块。最终结果是,我们将原始图像转换成一个简单的表示,该表示捕捉到了面部的基本结构。参考以下图示:

方向梯度直方图(HOG)

图 10.8:HOG 版本中的简单面部表示

这种表示对于 FR 任务来说很容易处理;它被称为图像的 HOG 版本。它代表了我们在 FR 任务中要考虑的特征,这就是为什么这种表示被称为 HOG 特征描述符。

第 7 步:为了找出这个 HOG 图像中的面部,我们必须找出我们的图像中看起来最像从其他训练面部中提取的已知 HOG 模式的部分。参考以下图示:

方向梯度直方图(HOG)

图 10.9:使用我们图像的 HOG 版本识别人脸的过程

使用这项技术,我们可以轻松地识别任何图像中的人脸。

用于 FR 的卷积神经网络(CNN)

在本节中,我们将探讨如何使用 CNN 从图像中识别人脸。本节分为两部分:

  • 简单的 CNN 架构

  • 理解 CNN 在 FR 中的应用

简单的 CNN 架构

我不想深入探讨 CNN 是如何工作的,因为我已经在第九章构建实时物体检测中提供了大部分必要的细节;然而,我想提醒你一些关于 CNN 的必要信息。首先,参考以下图表:

简单的 CNN 架构

图 10.10:CNN 架构

如您在前面的图表中所见,有一个卷积层、一个池化层、一个全连接层和一个输出层。涉及不同的激活函数、惩罚和 SoftMax 函数。这是高级信息。对于这个 FR 任务,我们可以使用三个卷积层和池化层,使用 ReLU 作为激活函数。您可以添加更多层,但这会使训练的计算成本更高。

理解 CNN 在 FR 中的应用

直观地说,CNN 模型按照以下步骤构建一个良好的 FR 应用。基本过程如下:

第 1 步:观察一张图片。裁剪只包含人脸的图像。

第 2 步:现在,在这个步骤中,我们专注于人脸,并试图理解即使人脸朝向奇怪的方向,或者图像是在不良光照下拍摄的,我们仍需要识别这类图像中人脸的正确位置。第 3 步将为我们提供解决方案。

第 3 步:为了从任何图像中识别人脸,无论图像是在不良光照条件下拍摄的,还是人脸的朝向看起来非常奇怪,我们需要识别人脸。为了实现这一点,我们挑选出人脸的独特特征,这些特征可以告诉我们关于人脸的独特信息。借助这些独特特征,我们可以识别同一个人的脸,以及不同人的脸。这些特征可以包括眼睛的大小、脸的长度等等。有 68 个特定的点需要考虑;它们被称为关键点。这些点是基于人脸关键点估计定义的。请参考以下论文以获取更多详细信息:www.csc.kth.se/~vahidk/papers/KazemiCVPR14.pdf。请查看以下图表:

理解 CNN 在 FR 中的应用

图 10.11:用于人脸关键点估计的 68 个点

第 4 步:我们需要通过名字识别某人的面孔,因此为了实现这一点,我们将比较该面孔的独特特征与我们已经知道的所有人,以确定该人的名字。假设你已经添加了比尔·盖茨、巴拉克·奥巴马等人的图片。你已经为他们生成了独特的面部特征,现在我们将比较这些已经生成的面部特征,如果特征相似,那么我们就能知道图像中的人的名字,即巴拉克·奥巴马或比尔·盖茨。基于面部特征的识别是一个分类问题,可以通过 CNN 轻松解决。我们正在生成一个 128 维度的面孔嵌入向量。作为输入,我们应该提供这个面孔嵌入向量。一旦我们完成训练,我们的应用程序将准备好识别人的名字。

第 5 步:训练好的模型会查看我们过去测量过的所有面孔,并找到与我们的面孔测量值最接近的人。那就是我们的匹配对象。

上述方法是为我们的基于 CNN 的 FR 和实时人脸识别任务。我们已经涵盖了 FR 任务中使用的算法的基本概念和背后的思想。现在让我们开始实现。

实现人脸识别的方法

在本节中,我们将实现 FR 应用程序。我们正在使用face_recognition库。我们已经为它配置了环境。我们将在这里实现以下方法:

  • 基于 HOG 的方法

  • 基于 CNN 的方法

  • 实时人脸识别

现在我们开始编码!

实现基于 HOG 的方法

在这种方法中,我们使用 HOG 算法找出两件事:图像中的面孔总数和步态。我们使用face_recognition库的 API。您可以通过点击以下 GitHub 链接找到代码:github.com/jalajthanaki/Face_recognition/blob/master/face_detection_example.py。代码片段如下所示:

基于 HOG 的方法实现

图 10.12:基于 HOG 的 FR 方法代码片段

在前面的图中,我们提供了一个图像作为输入,借助face_recognition库的 API,我们可以找到图像中面孔的像素位置。在这里,我们还将计算图像中有多少个面孔,借助Image库,我们可以从提供的图像中裁剪面孔。您可以在以下图中找到此脚本的输出:

基于 HOG 的方法实现

图 10.13:基于 HOG 的 FR 方法输出

参考以下截图中的裁剪面孔输出:

实现基于 HOG 的方法

图 10.14:裁剪后的面孔输出

如您在最后一个输出图中所见,通过简单的 API,我们可以构建一个简单的人脸识别应用程序。这种方法对我们来说是一种基线方法。

现在让我们转向基于 CNN 的方法。与基于 HOG 的方法相比,基于 CNN 的方法更准确。如果我们为基于 CNN 的方法使用 GPU,那么我们可以以更短的时间训练模型。现在让我们看看基于 CNN 方法的代码。

实现基于 CNN 的方法

在这种方法中,我们将使用face_recognition库,其中我们指定了模型的名称。我们模型的名称是cnn。这个特定的方法将通过face_recognition API 加载预训练模型,我们可以生成更准确的结果。您可以通过点击以下 GitHub 链接找到代码:github.com/jalajthanaki/Face_recognition/blob/master/face_detection_GPU_example.py。请参考以下图中的代码片段:

实现基于 CNN 的方法

图 10.15:基于 CNN 方法的 FR 代码片段

在这里,实现代码几乎与之前相同,但不同之处在于我们在 API 调用期间提供了模型名称cnn。您可以在以下图中看到这个实现的输出:

实现基于 CNN 的方法

图 10.16:基于 CNN 方法的 FR 输出

这个实现的输出与上一个相同。这个版本的实现速度快,并且准确性更高。现在让我们尝试为实时视频流实现 FR 任务。

实现实时人脸识别

在本节中,我们将为实时视频流实现 FR 任务。我们将尝试识别视频中出现的个人的名字。这难道不很有趣吗?让我们开始吧。您可以通过点击以下 GitHub 链接找到代码:github.com/jalajthanaki/Face_recognition/blob/master/Real_time_face_detection.py

再次,我们正在使用face_recognition库的 API。我们也在使用OpenCV。首先,我们需要提供带有个人名字的个人的样本图像,这样机器就可以在学习过程中识别出这个人的名字,并在测试时识别它。在这个实现中,我提供了巴拉克·奥巴马和乔·拜登的图像。您也可以添加您的图像。如果人脸特征熟悉并且与已提供的图像匹配,则脚本将返回该人的名字;如果人脸特征与给定图像不熟悉,则该人的脸被标记为未知。请参考以下图中的实现:

实现实时人脸识别

图 10.17:实时 FR 的实现

如前述代码所示,我提供了巴拉克·奥巴马和乔·拜登的样本图像。我还提供了我将图像输入脚本的人的名字。我使用了相同的面部识别 API 来检测和识别视频流中的面部。当你运行脚本时,你的网络摄像头会实时传输视频,此脚本会检测面部,如果你提供机器已知的该人的图像,那么这次它也能正确识别。

实现实时面部识别

图 10.18:实时 FR 的输出

如您所见,我没有向机器提供我的图像,因此它将我识别为未知,而它可以识别巴拉克·奥巴马的图像。您也可以通过点击github.com/jalajthanaki/Face_recognition/blob/master/img/Demo.gif找到动画图像。

我们完成了本章的第一部分,即开发一个可以识别人类面部,并根据面部识别出人的名字的应用程序。我们实现了三种不同的面部识别(FR)变体。

在下一节中,我们将探讨如何开发一个面部表情识别(FER)应用。为了构建此应用,我们需要不同类型的数据集,因此我们将从理解一个用于 FER 的数据集开始。

理解面部表情识别数据集

为了开发一个面部表情识别(FER)应用,我们正在考虑使用FER2013数据集。您可以从www.kaggle.com/c/challenges-in-representation-learning-facial-expression-recognition-challenge/data下载此数据集。我们需要了解关于此数据集的基本细节。数据集归功于 Pierre-Luc Carrier 和 Aaron Courville,作为持续研究项目的一部分。

此数据集包含 48x48 像素灰度人脸图像。任务是根据图像中显示的表情将每个面部分类。以下为七个类别,每个类别都有一个表示情绪类别的数字标签:

  • 0 = 生气

  • 1 = 厌恶

  • 2 = 恐惧

  • 3 = 快乐

  • 4 = 悲伤

  • 5 = 惊讶

  • 6 = 中立

此数据集包含fer2013.csv文件。此 csv 文件将用作我们的训练数据集。现在让我们看看文件的属性。文件中有三列,如下所示:

  • 情绪: 这列包含面部表情的数字标签。对于恐惧,此列包含值为2;对于悲伤,此列包含值为4,依此类推。

  • 像素: 这列包含单个图像的像素值。它代表图像的像素值矩阵。

  • 用法:此列包含有关特定数据记录是否用于训练目的或测试目的的一般标签。此列有三个标签,分别是Training(训练)、PublicTest(公共测试)和PrivateTest(私有测试)。用于训练目的的数据样本有 28,709 个。公共测试集包含 3,589 个数据样本,私有测试集也包含 3,589 个数据样本。

现在,让我们了解有助于我们开发 FER 应用的概念。

理解面部情感识别的概念

我们使用卷积神经网络(CNN)来开发 FER 应用。之前,我们看到了 CNN 的基本架构。为了开发 FER 应用,我们将使用以下 CNN 架构和优化器。我们正在构建一个两层深的 CNN。我们将使用两个全连接层和 SoftMax 函数来分类面部情感。

我们将使用由卷积层构成的多层结构,随后是 ReLU(修正线性单元)层,再之后是最大池化层。参考以下图表,它将帮助您理解 CNN 层的排列。让我们看看 CNN 的工作原理。我们将涵盖以下层:

  • 卷积层

  • ReLU 层

  • 池化层

  • 全连接层

  • SoftMax 层

理解卷积层

在这个层中,我们将以像素值的形式输入我们的图像。我们使用一个 3 x 3 维度的滑动窗口,它在整个图像上滑动。滑动窗口选中的区域被称为感受野。它是图像的一部分。滑动窗口只是一个 3 x 3 维度的矩阵,它可以扫描整个图像。通过使用滑动窗口,我们使用 3 x 3 维度的矩阵扫描图像的九个像素值。这个感受野或图像的一部分是卷积网络的输入。

参考以下图表:

理解卷积层

图 10.19:滑动窗口和感受野

这个感受野以输入图像的像素值形式携带值。这些像素值被称为特征图、特征、滤波器、权重矩阵或核。我们已经有了一个 3 x 3 的矩阵,被称为特征图。特征图的大小是一个超参数。我们可以取 n x n 矩阵,其中 n >= 1。在这里,我们考虑了一个 3 x 3 的矩阵来理解操作。现在进行简单的数学运算,步骤如下:

第一步:获取特征图。在这里,特征图指的是以感受野形式生成的图像块。

第二步:我们需要在特征图和整个图像之间执行点积。再次,我们使用滑动窗口扫描整个图像,并生成点积的值。

第 3 步:我们需要将我们从点积中获得的所有值加起来。

第 4 步:我们需要将点积求和的值除以特征中的总像素数。在这个解释中,我们总共有九个像素,所以我们将总和除以 9。作为输出,我们得到被称为特征图像的图像。

参考以下图表:

理解卷积层

图 10.20:卷积层的数学运算

我们将重复这个操作,几乎针对图像的所有可能位置,并尝试所有可能的组合,这就是为什么这个操作被称为卷积操作。现在让我们看看 ReLU 层。

理解 ReLU 层

这个层基本上为卷积网络引入了非线性。在这里,我们需要使用激活函数。对于这个应用,我们选择了修正线性单元作为激活函数。这个层对我们的特征图进行某种归一化。让我们看看它对特征图做了什么:

第 1 步:这个层以卷积层生成的特征图作为输入。

第 2 步:这个层只是将负值转换为零。

参考以下图表:

理解 ReLU 层

图 10.21:ReLU 层的操作

现在是时候看看池化层了。

理解池化层

使用这个层,我们缩小图像。我们将在这里使用最大池化操作。我们需要执行以下步骤:

第 1 步:我们需要将特征图作为输入,这次,ReLU 层的输出被作为这个层的输入。

第 2 步:我们需要选择窗口大小。通常,我们选择 2 x 2 像素或 3 x 3 像素的大小。我们将 2 x 2 作为我们的窗口大小。

第 3 步:我们将根据这个窗口大小扫描整个图像,并从四个像素值中取最大值。

你可以通过参考以下图表来理解这个操作:

理解池化层

图 10.22:最大池化层的操作

我们可以将这些层堆叠得尽可能深。你可以重复卷积、ReLU 和池化层 n 次,以使 CNN 变深。

理解全连接层

所有层的输出都传递到全连接层。这一层有一个投票机制。所有图像块都被考虑在内。2 x 2 矩阵的图像块以水平方式排列。投票取决于一个值预测面部表情的强度。如果这一层的某些值很高,这意味着它们接近 1,如果某些值很低,这意味着它们接近 0。对于每个类别,某些单元格的值接近 1,而其他值是 0,这样我们的网络就会预测类别。请参考以下图表:

理解全连接层

图 10.23:全连接层的直观理解

这里只执行了一个数学运算。我们正在取平均值。正如您在前面的图表中看到的,第一、第四、第五、第十和第十一行的全连接层正在预测一个类别,因此我们需要将这些单元格中所有现有值的总和,并找到它们的平均值。这个平均值告诉我们我们的网络在预测类别时的信心程度。我们可以堆叠尽可能多的全连接层。在这里,神经元的数量是超参数。

理解 SoftMax 层

我们还可以使用 SoftMax 层,它将特征值转换为概率值。这个方程如下:

理解 SoftMax 层

图 10.24:SoftMax 方程

这一层获取特征值,并使用前面的方程生成概率值。请参考以下图表:

理解 SoftMax 层

图 10.25:计算 SoftMax 函数的过程

基于反向传播更新权重

基于反向传播技术,CNN 网络的权重已经更新。我们将测量我们的预测答案与实际答案之间的差异。基于这个误差测量,我们计算损失函数的梯度,这告诉我们是否应该增加权重或减少它。如果预测答案和实际答案相同,则权重将不会发生变化。

我们已经理解了我们将用于开发面部情感识别模型的 CNN 的大部分核心概念。

构建面部情感识别模型

在本节中,我们将实现使用 CNN 的 FER 应用。为了编码目的,我们将使用TensorFlowTFLearnOpenCVNumpy库。您可以通过使用此 GitHub 链接找到代码:github.com/jalajthanaki/Facial_emotion_recognition_using_TensorFlow。这是我们需要遵循的步骤:

  1. 准备数据

  2. 加载数据

  3. 训练模型

准备数据

在本节中,我们将准备可用于我们应用程序的数据集。如您所知,我们的数据集是灰度的。我们有两种选择。一种是我们只需要使用黑白图像,如果我们使用黑白图像,那么将有两个通道。第二种选择是将灰度像素值转换为 RGB(红色、绿色和蓝色)图像,并使用三个通道构建 CNN。为了我们的开发目的,我们使用两个通道,因为我们的图像是灰度的。

首先,我们正在加载数据集并将其转换为numpy数组。转换后,我们将以.npy格式保存它,以便我们可以随时加载该数据集。我们将实际数据记录保存在一个文件中,而它们的标签则保存在另一个文件中。我们的输入数据文件名为fer2013.csv。包含数据的输出文件是data_set_fer2013.npy,标签位于data_labels_fer2013.npy文件中。执行此任务的脚本名称是csv_to_numpy.py。您可以通过此 GitHub 链接参考其代码:github.com/jalajthanaki/Facial_emotion_recognition_using_TensorFlow/tree/master/data

参考以下图中加载数据的代码片段:

准备数据

图 10.26:加载数据代码片段

helper函数的代码如下所示:

准备数据

图 10.27:helper函数代码片段

现在我们来看看如何加载我们已保存为.npy格式的数据。

加载数据

在本节中,我们将探讨如何使用我们已准备的数据集,以便我们可以用它进行训练。在这里,我们创建一个单独的脚本以帮助我们加载数据。在这个脚本中,我们定义了一个测试数据集,我们将在测试期间使用它。

这是一段简单直接代码。您可以在以下图中找到代码片段:

加载数据

图 10.28:数据加载脚本代码片段

当我们编写训练脚本时,将使用此类及其方法。您可以通过此 GitHub 链接查看此脚本的代码:github.com/jalajthanaki/Facial_emotion_recognition_using_TensorFlow/blob/master/dataset_loader.py

训练模型

在本节中,我们将探讨如何训练模型,以便它可以识别面部表情。以下是我们将执行的操作步骤。您可以通过参考此 GitHub 链接找到此训练步骤的代码:github.com/jalajthanaki/Facial_emotion_recognition_using_TensorFlow/blob/master/emotion_recognition.py

使用 dataset_loader 脚本加载数据

在这里,我们使用我们在上一节中编写的并理解的脚本加载数据集。您可以在下面的图中找到代码片段:

使用 dataset_loader 脚本加载数据

图 10.29:在模型训练过程中加载数据

现在,让我们构建实际用于训练的 CNN。

构建卷积神经网络

在这一步,我们将构建用于训练目的的 CNN。在这里,我们有三层卷积网络、ReLU 层和池化层。前两层有 64 个神经元,最后一层有 128 个神经元。我们添加了 dropout 层。对于一些神经元,dropout 层将值设置为 0。这个层选择那些长时间没有改变权重或长时间未激活的神经元。这将使我们的训练更加有效。我们有两个全连接层,以及一个使用 SoftMax 函数来推导面部情感类概率的全连接层。我们使用momentum函数来进行梯度下降。在这里,我们的损失函数是分类交叉熵。参考下面的图中代码片段:

构建卷积神经网络

图 10.30:构建 CNN 的代码片段

现在,让我们看看如何进行训练。

为 FER 应用进行训练

在这一步,我们需要开始训练,以便我们的模型可以学会预测面部情感。在这一步,我们将定义一些训练的超参数。参考下面的图中代码片段:

为 FER 应用进行训练

图 10.31:执行训练的代码片段

如前图所示,我们将 epoch 设置为100。训练批次大小为50。我们可以看到shuffle参数,它作为标志。此参数的值为true,表示我们在训练过程中正在打乱我们的数据集。

开始训练的命令是$ python emotion_recognition.py train

预测和保存训练模型

在这一步,我们正在定义predict方法。这个方法帮助我们生成预测。我们还定义了一个可以帮助我们保存训练模型的方法。我们需要保存模型,因为我们可以在需要时加载它进行测试。您可以在下面的图中找到代码片段:

预测和保存训练模型

图 10.32:预测类别的代码片段

参考下面的图中代码片段:

预测和保存训练模型

图 10.33:保存训练模型的代码片段

现在是查看测试矩阵的时候了。之后,我们需要测试我们的训练模型。所以,在我们测试模型之前,我们应该理解测试矩阵。

理解测试矩阵

在本节中,我们将查看面部情绪应用的测试矩阵。测试的概念非常简单。我们需要开始观察训练步骤。我们正在跟踪损失和准确率的值。基于这些,我们可以决定我们模型的准确率。这听起来简单吗?我们已经训练了模型 30 个 epoch。这么多的训练需要超过三个小时。我们达到了 63.88%的训练准确率。请参考以下图中的代码片段:

理解测试矩阵

图 10.34:训练进度,以了解训练准确率

这是训练准确率。如果我们想检查验证数据集上的准确率,那么它也会在训练步骤中给出。我们已经定义了验证集。借助这个验证数据集,训练好的模型生成其预测。我们比较预测类别和实际类别标签。之后,我们生成您在前面的图表中可以看到的验证准确率。这里,val_acc为 66.37%,这很棒。到目前为止,这个应用程序已经能够达到 65%到 70%的准确率。

测试模型

现在我们需要加载训练好的模型并对其进行测试。在这里,我们将使用视频流。FER 应用程序将根据我的面部表情检测情绪。您可以参考以下 GitHub 链接中的代码:github.com/jalajthanaki/Facial_emotion_recognition_using_TensorFlow/blob/master/emotion_recognition.py

您可以在以下图中找到此代码片段:

测试模型

图 10.35:加载训练好的模型并进行测试的代码片段

为了开始测试,我们需要执行以下命令:

$ python emotion_recognition.py poc

这次测试将使用您的网络摄像头。我有一些演示文件想要在这里分享。请参考以下图:

测试模型

图 10.36:用于识别厌恶情绪的 FER 应用程序的代码片段

也请参考以下图:

测试模型

图 10.37:识别快乐情绪的 FER 应用程序

请参考以下图中的代码片段:

测试模型

图 10.38:用于识别中性情绪的 FER 应用程序的代码片段

请参考以下图中的代码片段:

测试模型

图 10.39:用于识别愤怒情绪的 FER 应用程序的代码片段

现在我们来看看如何改进这种方法。

现有方法存在的问题

在本节中,我们将列出所有造成问题的点。我们应该尝试改进它们。以下是我认为我们可以改进的地方:

  • 如果你发现你的案例中类采样不合适,那么你可以采用采样方法

  • 我们可以给我们的神经网络添加更多层

我们可以尝试不同的梯度下降技术。

在这种方法中,训练需要花费大量时间,这意味着训练计算成本很高。当我们训练模型时,我们使用了 GPU,尽管 GPU 训练需要很长时间。我们可以使用多个 GPU,但这很昂贵,带有多个 GPU 的云实例也不划算。因此,如果我们能在这种应用中使用迁移学习或使用预训练模型,那么我们将获得更好的结果。

如何优化现有方法

正如你在上一节中看到的,由于计算硬件的缺乏,我们已经达到了 66%的准确率。为了进一步提高准确率,我们可以使用预训练模型,这将更加方便。

理解优化过程

在前几节中,我描述了一些问题。我们可以给我们的 CNN 添加更多层,但这将使计算成本更高,所以我们不会这么做。我们已经很好地采样了我们的数据集,所以我们不需要担心这一点。

作为优化过程的一部分,我们将使用使用keras库训练的预训练模型。这个模型使用了多个 CNN 层。它将在多个 GPU 上训练。因此,我们将使用这个预训练模型,并检查结果如何。

在下一节中,我们将实现可以使用预训练模型的代码。

最佳方法

我们已经实现了大约 66%的准确率;对于一个 FER 应用,最佳准确率大约是 69%。我们将通过使用预训练模型来实现这一点。所以,让我们看看实现方法,以及我们如何使用它来实现最佳结果。

实施最佳方法

在本节中,我们将实现适用于 FER 应用的最好方法。这个预训练模型是通过使用密集和深度卷积层构建的。由于六层深度 CNN,以及随机梯度下降(SGD)技术的帮助,我们可以构建预训练模型。每一层的神经元数量分别为 32、32、64、64、128、128、1,024 和 512。所有层都使用 ReLU 作为激活函数。3 x 3 的矩阵将用于生成初始特征图,2 x 2 的矩阵将用于生成最大池化。你可以从这个 GitHub 链接下载模型:github.com/jalajthanaki/Facial_emotion_recognition_using_Keras

你可以通过参考以下图表来查看代码:

实施最佳方法

图 10.40:使用预训练 FER 模型的代码片段

在前面的代码中,我们加载了预训练的keras模型。我们提供了两个方案。我们可以使用这个脚本从图像中检测面部表情,也可以通过提供视频流来实现。

如果你想测试任何图像中存在的面部表情,那么我们需要执行$ python image_test.py tes.jpg命令。我已经将此模型应用于tes.jpg图像。你可以看到以下输出图像:

实施最佳方法

图 10.41:图像中 FER 应用的输出

如果你想测试视频流的模型,那么你需要执行此命令:$python realtime_facial_expression.py

参考以下图中的输出:

实施最佳方法

图 10.42:视频流中 FER 应用的输出

你可以在以下图中找到输出文件:

实施最佳方法

图 10.43:视频流中 FER 应用的输出

此应用为我们提供了大约 67%的准确率,这非常不错。

摘要

在本章中,我们探讨了如何使用face_recognition库开发人脸检测应用,该库使用基于 HOG 的模型来识别图像中的人脸。我们还使用了预训练的卷积神经网络,它可以从给定图像中识别人脸。我们开发了实时人脸识别来检测人的名字。对于人脸识别,我们使用了预训练的模型和现成的库。在章节的第二部分,我们开发了人脸表情识别应用,它可以检测人脸所携带的七种主要情绪。我们使用了TensorFlowOpenCVTFLearnKeras来构建人脸表情识别模型。此模型在预测人脸情绪方面具有相当好的准确率。我们达到了最佳可能的准确率 67%。

目前,计算机视觉领域在研究方面发展迅速。你可以探索许多新鲜和酷的概念,例如 Facebook AI Research 团队提出的deepfakes和 3D 人体姿态估计(机器视觉)。你可以通过点击此处参考deepfakesGitHub 仓库:github.com/deepfakes/faceswap。你可以通过点击此链接参考 3D 人体姿态估计的论文:arxiv.org/pdf/1705.03098.pdf。这两个概念都是新颖的,所以你可以参考它们并制作一些有趣的应用。

下一章将是本书的最后一章。在这一章中,我们将尝试制作一个游戏机器人。这一章将大量使用强化学习技术。我们将开发一个可以自己玩 Atari 游戏的机器人。所以请继续阅读!

第十一章:构建游戏机器人

在前面的章节中,我们介绍了属于计算机视觉领域的一些应用。在本章中,我们将制作一个游戏机器人。我们将介绍构建游戏机器人的不同方法。这些游戏机器人可以用来玩各种 Atari 游戏。

让我们快速回顾一下过去两年。让我们从 2015 年开始。一家名为 DeepMind 的小型伦敦公司发布了一篇名为《用深度强化学习玩 Atari》的研究论文,可在arxiv.org/abs/1312.5602找到。在这篇论文中,他们展示了计算机如何学习和玩 Atari 2600 视频游戏。计算机只需观察屏幕像素就能玩游戏。我们的计算机游戏代理(即游戏玩家)在游戏得分增加时会获得奖励。这篇论文中展示的结果非常引人注目。这篇论文引起了很大的轰动,那是因为每个游戏都有不同的得分机制,而且这些游戏被设计成人类难以获得最高分。这篇研究论文的美丽之处在于,我们可以使用该概念和给定的模型架构,无需任何修改来学习不同的游戏。这个模型架构和算法应用于七款游戏,其中三款游戏中,算法的表现远远超过了人类!这在人工智能领域是一个巨大的飞跃,因为希望我们能够构建一个可以掌握许多任务的单一算法,并在未来几十年内的某个时刻构建一个通用人工智能或人工通用智能(AGI)系统。你可以在en.wikipedia.org/wiki/Artificial_general_intelligence上了解更多关于 AGI 的信息。我们都知道 DeepMind 很快就被谷歌收购了。

2017 年,谷歌 DeepMind 和 OpenAI 取得了重大里程碑,这让我们有理由相信通用人工智能(AGI)很快就会实现。让我们先从谷歌 DeepMind 开始;你一定听说过谷歌 DeepMind 的 AlphaGo 人工智能(一个游戏机器人)在一场三局比赛中击败了世界上最优秀的围棋选手。围棋是一项复杂的游戏,因为它对于每一个走法都有大量的排列组合。你可以通过点击这个 YouTube 视频观看这场比赛:www.youtube.com/watch?v=vFr3K2DORc8。现在让我们来谈谈 OpenAI。如果你第一次听说 OpenAI,这是一个简要的介绍。OpenAI 是一个非营利性的人工智能研究组织,由埃隆·马斯克共同创立,致力于构建既安全又能确保人工智能(AI)系统的利益尽可能广泛且均匀地分布的 AI。2017 年,OpenAI 的游戏机器人击败了世界上最优秀的 Dota 2 选手。你可以观看这个 YouTube 视频作为参考:www.youtube.com/watch?v=7U4-wvhgx0w。所有这些成就都是通过科技巨头创建的 AGI 系统环境实现的。创建 AGI 系统的目标是让一个系统能够执行各种复杂任务。理想的 AGI 系统可以帮助我们在医疗保健、农业、机器人技术等领域解决大量复杂任务,而无需对其算法进行任何更改。因此,如果我们能理解基本概念,以便开发 AGI 系统,那就更好了。

在本章中,我们首先尝试制作一个能够玩简单 Atari 游戏的机器人。我们将通过使用强化学习来实现这一点。

在本章中,我们将讨论以下主题:

  • 介绍问题陈述

  • 设置编码环境

  • 理解强化学习(RL)

  • 基本 Atari 游戏机器人用于路径搜索

    • 理解关键概念
  • 实现游戏机器人的基本版本

  • 构建 Space Invaders 游戏机器人

    • 理解关键概念
  • 实现 Space Invaders 游戏机器人

  • 构建 Pong 游戏机器人

    • 理解关键概念
  • 实现 Pong 游戏机器人

  • 仅为了乐趣 - 实现 Flappy Bird 游戏机器人

  • 总结

介绍问题陈述

我们知道我们正在尝试开发一个游戏机器人:一个能够玩简单 Atari 游戏的程序。如果我们提供足够的时间和计算资源,那么它就能超越那些在玩某些游戏方面专家级的人类。我将列出一些著名的 Atari 游戏,以便你能看到我所说的游戏类型。你肯定玩过这些游戏中的至少一个。一些著名的 Atari 游戏包括 Casino、Space Invaders、Pac-man、Space War、Pong(乒乓球)等等。简而言之,我们试图解决的问题是如何构建一个能够学习玩 Atari 游戏的机器人?

在本章中,我们将使用已经内置的游戏环境,利用gymdqn库。因此,我们不需要创建游戏视觉环境,我们可以专注于制作最佳游戏机器人的方法。首先,我们需要设置编码环境。

设置编码环境

在本节中,我们将介绍如何设置一个编码环境,以帮助我们实现我们的应用程序。我们需要安装 gym 库。以下是您可以遵循的步骤。我使用Ubuntu 16.04 LTS作为我的操作系统:

  • 第一步:通过执行以下命令从 GitHub 克隆 gym 仓库:$ sudo git clone https://github.com/openai/gym.git

  • 第二步:通过执行以下命令跳转到 gym 目录:$ cd gym

  • 第三步:执行以下命令安装gym所需的最小数量库:$ sudo pip install -e

  • 第四步:通过执行以下命令安装 Atari 游戏的游戏环境:$ sudo pip install gym[atari]

  • 第五步:这一步是可选的。如果您想安装所有游戏环境,则可以执行以下命令:

    • $ sudo apt-get install -y python-numpy python-dev cmake zlib1g-dev libjpeg-dev xvfb libav-tools xorg-dev python-opengl libboost-all-dev libsdl2-dev swig

    • $ sudo pip install gym[all]

这就是您安装gym机器学习库的方法,我们将使用它来开发游戏机器人。我们将使用dqn库的 TensorFlow 实现,因此不需要单独安装dqn,但您当然可以参考这个安装说明:github.com/deepmind/dqn

由于我们已经完成了环境设置,我们需要继续到下一个部分,这将帮助我们了解在开发游戏机器人时有用的技术。那么,让我们开始吧!

理解强化学习(RL)

在本章中,我们将借助强化学习技术制作游戏机器人。强化学习的动机很简单。RL 给机器或任何软件代理一个机会,根据该代理从环境中收到的反馈来学习其行为。这种行为可以一次性学习,或者您可以随着时间的推移不断适应。

让我们通过一个孩子学习说话的有趣例子来理解 RL。当孩子学习如何说话时,他们会采取以下步骤:

  • 第一步:首先,孩子开始观察您;您是如何说话的,以及您是如何与他或她互动的。孩子从您那里听基本单词和句子,并了解到他们也可以发出类似的声音。因此,孩子试图模仿您。

  • 第 2 步:孩子想要说出完整的句子或单词,但他们可能不理解,甚至在说话句子之前,他们需要学习简单的单词!这是他们在尝试说话时遇到的挑战。现在孩子试图发出声音,有些声音听起来很滑稽或奇怪,但他们仍然决心说出单词和句子。

  • 第 3 步:孩子面临的另一个挑战是他们需要理解并记住他们试图说出的单词背后的含义。但孩子设法克服了这个挑战,学会了说出他们最初的一些单词,这些单词非常简单,例如mama, papa, dadda, paa, maa等等。他们通过不断观察周围的环境来学习这项任务。

  • 第 4 步:真正的挑战始于如何使用特定的单词,何时使用哪个单词,以及记住他们第一次听到的所有单词。尝试向孩子提供所有单词的意义以及他们需要使用的情境。听起来像是一项挑战性的任务,不是吗?

对于一个孩子来说,这是一项困难的任务,但一旦开始理解语言并练习句子,它就会成为孩子生活的一部分。在 2-3 年内,孩子可能已经足够练习,可以轻松地开始互动。如果我们考虑我们自己说话,对于我们来说这是一项简单的任务,因为我们已经学会了足够关于如何在我们的环境中互动的知识。

现在,让我们尝试将这些点联系起来。借助前面的例子,我们将尝试理解强化学习(RL)的概念。给定例子的问题陈述是说话,其中孩子是代理,他们试图通过采取行动(在这里,行动是说话)来操纵环境(孩子首先说出哪个单词),并尝试说出一个单词或另一个单词。当孩子完成任务的子模块时,他们会得到奖励——比如说,一块巧克力——这意味着他们在一天内说出了一些单词,而如果他们无法说出任何东西,则不会得到巧克力。这是强化学习的简化描述。您可以参考以下图表:

理解强化学习(RL)

图 11.1:强化学习基本概念的形象表示

所以基本上,强化学习(RL)允许机器和软件代理在特定任务或特定情境中自动确定理想和最佳的行为,以最大化软件代理的性能。代理需要简单的奖励反馈来学习其行为,这被称为强化信号。每次软件代理尝试采取导致其获得最大奖励的行动时,它都会尝试。最终,它学会了所有导致代理达到任务最优解的行动或移动,从而成为该领域的专家。强化学习的算法学会对环境做出反应。

为了构建游戏机器人,强化学习算法是完美的选择,这背后有原因。假设有许多带有随机回报的老虎机,你想要赢得最大金额。你该如何赢得最大金额?一个简单的方法是只选择一台机器,整天拉动它的杠杆,它可能会给你一些回报。如果你足够幸运,那么你可能会中大奖。为了尝试这种方法,你可能会损失一些钱。这种方法被称为纯利用方法。这不是一个最优的方法。

让我们采取另一种方法。在这个方法中,我们将拉动每台老虎机的杠杆,并祈祷至少有一台能够中大奖。这同样是一个简单的方法。在这个方法中,我们需要整天拉动杠杆。这种方法被称为纯探索方法。这种方法也不是最优的,因此我们需要在这两种方法之间找到一个适当的平衡,以获得最大的奖励。这被称为强化学习中的探索与利用的困境。现在我们需要解决这个问题。好吧,为了做到这一点,我们需要一个数学框架,可以帮助我们实现最优解,而这个数学方法就是马尔可夫决策过程(MDP)。让我们来探讨一下。

马尔可夫决策过程(MDP)

马尔可夫决策过程使用以下参数:

  • 状态集合,S

  • 行动集合,A

  • 奖励函数,R

  • 策略,π

  • 价值,V

为了将一个状态转换到最终状态*(S),我们必须采取一个动作(A)或一系列动作。我们将为每个采取的动作获得奖励(R)。我们的行动可以提供正奖励或负奖励。我们采取的行动集合定义了我们的策略(π)。执行每个行动后获得的奖励定义了我们的价值(V)*。我们的目标是通过对策略的正确选择来最大化奖励。我们可以通过执行最佳可能的行动来实现这一点。从数学上讲,我们可以像以下截图所示那样表达这一点:

马尔可夫决策过程 (MDP)

图 11.2:马尔可夫决策过程的数学表示

我们将应用前面的方程式来计算时间 t 时所有可能的状态值。我们有一组状态和动作。我们需要考虑这些状态、动作以及将智能体从一个状态转换到另一个状态的规则。当我们执行改变游戏智能体状态的行动时,智能体将因该行动而获得奖励。这个状态、行动和获得奖励的整个过程构成了马尔可夫决策过程(MDP)。一场游戏的一轮被认为是 MDP 的一个回合。这个过程包括有限的状态、动作和奖励序列。请看以下方程式,以表示这个过程::

S0, a0, r1, s1, a1, r2, s2, a2, r3 ,…,sn-1, an-1, rn, sn

在这里,s[i] 代表状态,ai 是动作,r[i+1] 是执行动作后我们将获得的奖励。sn 表示一个特定的场景以终端状态结束,这发生在“游戏结束”屏幕出现时。马尔可夫决策过程基于马尔可夫假设,下一个状态 s[i+1] 的概率取决于当前状态 s[i] 和执行的动作 ai,而不取决于前面的状态或动作。

折现未来奖励

从长远来看,如果我们想让我们的游戏代理做得好,那么我们需要考虑即时奖励,但我们也需要考虑我们的代理将获得的未来奖励。我们应该如何处理这种情况呢?嗯,答案在于折现未来奖励的概念。

给定一个 MDP 的运行,我们可以通过以下方程计算一个场景的总奖励:

R = r1 + r2 + r3 + … + rn

基于前面的方程,我们可以计算出从时间戳 t 开始的总未来奖励,这可以用给定的方程表示:

Rt = rt + rt+1 + rt+2 + rt+3 + … + rn

在这里,我们处理的是一个随机的游戏环境,我们无法确定我们是否会在执行相同的动作来玩特定游戏时获得相同的奖励。你考虑未来得越多,它就会越偏离。因此,我们最好使用折现未来奖励而不是总奖励:

Rt = rt+ γrt+1 + γ2rt+2+ … + γn-1 rn

在这里,γ 是折现系数。它的值在 0 到 1 之间。很容易理解,在特定的时间步 t 的折现未来奖励可以用当前状态的奖励加上时间步 t+1 的奖励来表示:

Rt = rt + γ (rt+1 + γ (rt+2 + …)) = rt + γRt+1

现在让我告诉你调整这个折现系数的实际意义:如果我们把折现系数 γ 的值设为 0,那么我们的游戏策略将是短视的,我们只是基于即时奖励来做游戏决策。我们需要在即时奖励和未来奖励之间找到一个平衡,所以我们应该把折现系数的值设为大于 0.7。

例如,我们可以将值设为 γ = 0.9。如果我们知道我们的游戏环境是确定性的,并且相同的动作总是带给我们相同的奖励,那么我们可以将折现系数 γ 的值设为 1。对于游戏代理来说,一个好的策略是始终选择一个最大化折现未来奖励的动作

我们已经涵盖了强化学习的基础。从现在开始,我们将开始实现我们的游戏机器人。所以,让我们为一些乐趣做好准备!

基本 Atari 游戏机器人

在本章中,我们尝试了一种动手的方式来构建一些基本的游戏机器人。我们选择了几乎每个人在生命中某个时刻都玩过的著名雅达利游戏。我们选择雅达利游戏是因为我们知道如何玩它们,这使得我们的生活变得简单,因为我们能够理解我们的机器人应该执行什么样的动作,以便在一段时间内变得更好。

在本节中,我们将构建自己的游戏。这个游戏很简单,因此我们可以看看我们如何应用 Q-Learning 算法。在这里,我们将自己设计游戏世界。让我们开始吧!

理解关键概念

在本节中,我们将探讨许多重要的方面,这些方面将帮助我们进行编码,因此在这里,我们将涵盖以下主题:

  • 游戏规则

  • 理解 Q-Learning 算法

游戏规则

在我们开始基本概念或算法之前,我们需要了解我们正在构建的游戏规则。这个游戏很简单,很容易玩。这个游戏的规则如下:

  • 游戏规则: 游戏代理意味着一个黄色方块必须达到一个目标以结束游戏:它可以是绿色单元格或红色单元格。这意味着黄色方块应该到达绿色单元格或红色单元格。

  • 奖励: 每一步都会给我们一个负奖励 -0.04。如果我们的游戏代理到达红色单元格,那么红色单元格会给我们一个负奖励 -1。如果我们的游戏代理到达绿色单元格,那么绿色单元格会给我们一个正奖励 +1。

  • 状态: 每个单元格都是代理的一个状态,它需要找到其目标。

  • 动作: 这个游戏只有四个动作:向上方向、向下方向、向右方向、向左方向。

我们需要 tkinter 库来实现这个方法。我已经在这个 GitHub 链接中提供了如何安装它的描述:github.com/jalajthanaki/Q_learning_for_simple_atari_game/blob/master/README.md

现在,让我们看看在本章构建游戏机器人时我们将使用的 Q 学习算法。

理解 Q-Learning 算法

这个算法最初由 DeepMind 在两篇论文中发表。第一篇论文在 2013 年 NIPS 上以 Playing Atari with Deep Reinforcement Learning 为标题发表。论文的链接是 arxiv.org/pdf/1312.5602.pdf。第二篇论文在 2015 年《自然》杂志上以 Human-level control through deep reinforcement Learning 为标题发表。这篇论文的链接是 www.davidqiu.com:8888/research/nature14236.pdf。你绝对应该阅读这些论文。我已经为你简化了这些论文的主要概念。

在 Q 学习中,我们需要定义一个Q(s, a)函数,它表示我们在状态s执行动作a时的折扣因子奖励,并从该点开始进行最优操作。你可以在下面的截图中看到帮助我们选择最大奖励的方程:

理解 Q 学习算法

图 11.3:Q 函数的方程

我们可以将 Q(s, a)函数视为在特定状态s执行动作a后,在游戏结束时给我们提供最佳可能得分的函数。这个函数是 Q 函数,因为它表明了在特定状态下某个动作的质量。

让我来简化一下。假设你处于状态s,正在考虑是否应该执行动作ab。你真的想以高分赢得游戏。所以,为了实现你的目标,你想要选择在游戏结束时给你带来最高得分的动作。如果你有这个 Q 函数,那么动作的选择就变得相当简单,因为你只需要选择具有最高 Q 值的动作。你可以在下面的截图中看到你可以使用的方程来获得最高的 Q 值:

理解 Q 学习算法

图 11.4:使用 Q 函数选择最大奖励的方程

在这里,π代表策略。策略表明游戏的规则和动作。借助策略,我们可以选择在每个状态下可用的典型动作。我们的下一步是获取这个 Q 函数。为此,我们需要专注于仅一个转换。这个转换由四个状态组成:< s, a, r, s' >。记住折扣因子奖励,其中我们可以用下一个状态s'的 Q 值来表示当前状态s和当前动作a*的 Q 值。计算奖励的方程式在下面的截图提供:

理解 Q 学习算法

图 11.5:计算奖励的 Bellman 方程

上述方程被称为 Bellman 方程,它是 Q 学习算法背后的主要思想。这个方程相当合理,它表明这个状态和动作的最大未来奖励是即时奖励和下一个状态的最大未来奖励的总和。

主要的直觉是,通过跟随迭代次数和近似步骤,我们可以生成 Q 函数的值。我们将通过使用Bellman 方程来实现这一点。在最简单的情况下,Q 函数以表格的形式实现,其中状态是其行,动作是其列。这个 Q 学习算法的伪步骤很简单。你可以看看它们,如下所示:

  • 第 1 步:任意初始化 Q [状态数量,动作数量]

  • 第 2 步:观察初始状态

  • 第 3 步:重复

    Select and perform an action a
    Observe two things: reward r and new state s'
    Q [s, a] = Q [s, a] + α (r + γmaxa' Q [s', a'] - Q [s, a])
    s = s'
    
  • 直到终止

我们需要遵循以下步骤,其中α是学习率。学习率验证了先前 Q 值和新提出的 Q 值之间的差异。这个差异值被考虑在内,以便我们可以检查我们的模型何时会收敛。借助学习率,我们可以调节训练速度,使我们的模型不会变得太慢以至于无法收敛,或者太快以至于无法学习任何东西。我们将使用*maxa'Q [s', a']来更新Q [s, a]*以最大化奖励。这是我们唯一需要执行的操作。这个估计操作将给我们提供更新的 Q 值。在训练的早期阶段,当我们的智能体在学习时,可能会出现我们的估计完全错误的情况,但随着每一次迭代的进行,估计和更新的 Q 值会越来越准确。如果我们进行这个过程足够多次,那么 Q 函数将收敛。它代表了真实和优化的 Q 值。为了更好地理解,我们将实现前面的算法。请参考以下截图中的代码片段:

理解 Q 学习算法

图 11.6:构建和更新 Q 表的代码片段

你可以在以下截图中以 Q 表的形式看到输出:

理解 Q 学习算法

图 11.7:Q 表值

你可以通过参考以下 GitHub 链接来查看前面算法的实现:github.com/jalajthanaki/Q_learning_for_simple_atari_game/blob/master/Demo_Q_table.ipynb

现在让我们开始实现游戏。

实现游戏机器人的基本版本

在本节中,我们将实现一个简单的游戏。我已经定义了这个游戏的规则。为了快速提醒你,我们的智能体,黄色方块试图到达红色方块或绿色方块。如果智能体到达绿色方块,我们将获得+1 作为奖励。如果它到达红色方块,我们得到-1。智能体每走一步都将被视为-0.04 的奖励。如果你想查看游戏的规则部分,可以翻回前面的页面。你可以通过参考这个 GitHub 链接来查看这个基本版本的游戏机器人代码:github.com/jalajthanaki/Q_learning_for_simple_atari_game

对于这个游戏,游戏世界或游戏环境已经构建好了,所以我们不需要担心它。我们只需要通过导入语句来包含这个游戏世界。我们正在运行的脚本主程序是Lerner.py。该代码的代码片段如下所示:

实现游戏机器人的基本版本

图 11.8:游戏机器人基本版本的代码片段 - I

如前述代码所示,我们通过循环中给出的代码帮助跟踪智能体的状态和动作。之后,我们将定义这个游戏的四种可能动作,并基于此计算奖励值。我们还定义了max_Q函数,它为我们计算最大的 Q 值。您也可以参考以下截图:

实现游戏机器人的基本版本

图 11.9:游戏机器人基本版本代码片段 - II

如前述代码片段所示,辅助函数使用inc_Q方法来更新 Q 值。通过使用run函数,我们可以更新 Q 值,使我们的机器人学会如何找到最佳解决方案。您可以通过执行以下命令来运行此脚本:

$ python Learner.py

当您运行脚本时,您可以看到以下输出窗口,在 1-2 分钟内,这个机器人将找到最佳解决方案。您可以在以下截图中找到机器人的初始状态和最终状态输出:

实现游戏机器人的基本版本

图 11.10:游戏机器人基本版本的输出

您可以通过使用奖励分数来跟踪机器人的进度。您可以参考以下截图:

实现游戏机器人的基本版本

图 11.11:跟踪游戏机器人的进度

如您所见,在初始迭代中,游戏机器人在经过几次迭代后表现不佳,但随后开始根据其获得的经验学习如何采取行动。我们在奖励分数没有显著提高时停止了代码。这是因为我们的游戏机器人能够找到最佳解决方案。

现在,让我们构建一个更复杂的游戏机器人;我们将使用深度 Q 网络进行训练。那么,让我们开始吧。

构建太空侵略者游戏机器人

我们将构建一个能够玩太空侵略者的游戏机器人。你们大多数人可能都玩过这个游戏,或者至少听说过它。如果您没有玩过,或者现在想不起来,那么请看看以下截图:

构建太空侵略者游戏机器人

图 11.12:太空侵略者游戏片段

希望您现在还记得这个游戏以及它是如何玩的。首先,我们将看看我们将用于构建这个版本游戏机器人的概念。让我们开始吧!

理解关键概念

在这个版本的游戏机器人中,我们将使用深度 Q 网络并训练我们的机器人。因此,在实现此算法之前,我们需要理解这些概念。看看以下概念:

  • 理解深度 Q 网络(DQN)

  • 理解经验回放

理解深度 Q 网络(DQN)

深度 Q 网络算法基本上是两个概念的组合。它使用深度神经网络中的 Q 学习逻辑。这就是为什么它被称为深度 Q 网络(DQN)。

每个游戏世界都有不同的环境。例如,超级马里奥与太空侵略者看起来不同。我们不能每次都为单个游戏提供整个游戏环境,因此首先我们需要决定所有游戏的通用表示,以便我们将它们作为 DQN 算法的输入。屏幕像素是明显的输入选择,因为它们显然包含了关于游戏世界及其状况的所有相关信息。没有屏幕像素的帮助,我们无法捕捉游戏代理的速度和方向。

如果我们将与 DeepMind 论文中提到的相同的前处理步骤应用于游戏屏幕,那么我们需要遵循以下步骤:

第 1 步:我们需要考虑游戏的最后四张屏幕图像作为输入。

第 2 步:我们需要将它们调整到 84 x 84 的大小,并将它们转换为 256 级灰度。这意味着我们将有 256^(84x84x4),大约是 10⁶⁷⁹⁷⁰种可能的游戏状态。这意味着在我们的想象中的 Q 表中将有 10⁶⁷⁹⁷⁰行,这是一个很大的数字。你可以争论说,许多像素组合或状态从未发生,所以我们可能将其表示为稀疏矩阵。这个稀疏矩阵只包含已访问的状态。然而,大多数状态很少被访问。因此,Q 表的收敛需要很长时间。坦白说,我们也希望为代理从未见过的状态提供一个好的 Q 值猜测,这样我们就可以为游戏代理生成一个合理的动作。这就是深度学习介入的地方。

第 3 步:神经网络非常适合为高度结构化的数据生成良好的特征。借助神经网络,我们可以表示我们的 Q 函数。这个神经网络将状态(即四个游戏屏幕和动作)作为输入,并生成相应的 Q 值作为输出。或者,我们也可以只将游戏屏幕作为输入,并为每个可能的动作生成 Q 值作为输出。 这种方法有一个很大的优点。让我解释一下。我们在这里做了两件主要的事情。首先,我们需要获得更新的 Q 值。其次,我们需要选择具有最高 Q 值的动作。

因此,如果我们有所有可能动作的 Q 值,那么我们可以很容易地更新 Q 值。我们也可以很轻松地选择具有最高 Q 值的动作。有趣的部分是,我们可以通过执行网络的前向传递来生成所有动作的 Q 值。经过单次前向传递,我们可以拥有所有可能动作的 Q 值列表。这次前向传递将节省大量时间,并为游戏代理提供良好的奖励。

DQN 架构

你可以在以下图表中找到深度 Q 网络的理想架构:

DQN 架构

图 11.13:DQN 架构

上述架构已在 DeepMind 论文中使用并发表。神经网络架构在下面的屏幕截图中显示:

DQN 架构

图 12.14:DQN 架构

提供的架构使用了一个经典的卷积神经网络(CNN)。它包含三个卷积层,随后是两个全连接层,这与我们在对象检测和面部识别 CNN 架构中看到的带有池化层的 CNN 架构相同。在这里,没有使用池化层。这是因为使用池化层的主要目的是使神经网络对位置不敏感。这意味着如果我们使用池化层,那么图像中对象的放置就不会被神经网络考虑。对于分类任务来说,这种位置不敏感性是有意义的,但对于游戏来说,游戏环境中对象的放置位置很重要。它们帮助我们确定动作以及潜在的奖励,我们不想丢弃这些信息。因此,我们在这里没有使用池化层。

DQN 算法的步骤

让我们看看 DQN 算法的步骤:

网络输入: 四个 84 x 84 的灰度游戏屏幕像素。

网络输出: 作为输出,我们将为每个可能的行为生成 Q 值。Q 值可以是任何实数,这意味着它可以是你能想象到的任何实数,这使得它成为一个回归任务。我们知道我们可以通过简单的平方误差损失来优化回归函数。误差损失的方程式在下面的屏幕截图中显示:

DQN 算法的步骤

图 11.15:误差损失函数的方程式

Q 表更新步骤: 存在着过渡 < s, a, r, s' >,但这次,更新 Q 表的规则与 Q-learning 不同。有一些变化。因此,更新 Q 表的步骤如下:

  • 第 1 步:我们需要对当前状态 s 执行前向传播,以获得所有动作的预测 Q 值。

  • 第 2 步:对下一个状态 s' 执行前向传播,并计算所有网络输出 maxa'Q(s', a').

  • 第 3 步:为动作 a 设置 Q 值目标为 r + γmaxa'Q(s', a')。在这里,我们可以使用我们在第 2 步中已经计算出的 maxa'Q(s', a') 值。对于所有其他动作,设置从第 1 步继承的 Q 值,使得那些输出的误差为零。

  • 第 4 步:我们需要使用反向传播来更新神经网络的权重。

现在我们来看一下经验回放的概念。

理解经验回放

我们正在使用两个概念来估计每个状态的未来奖励。我们使用 Q-learning,并使用卷积神经网络来近似 Q 函数。在这里,Q 值的近似是通过一个非线性函数完成的,而这个函数对于模型收敛来说并不非常稳定。因此,我们需要尝试各种超参数。这需要很长时间:在单个 GPU 上几乎需要一周的时间来训练游戏机器人。

我们将使用一个称为经验回放的概念。在训练过程中,所有经验 < s, a, r, s' > 都存储在回放记忆中。当我们进行训练时,网络将使用回放记忆中的随机样本而不是最近的转换。这样,训练时间会更短,而且还有一个优点。借助经验回放,我们的训练任务将更类似于常规的监督学习。现在我们可以轻松地对算法进行调试和测试操作。借助回放记忆,我们可以存储所有我们的游戏人类经验,然后根据这个数据集来训练模型。

因此,最终在 DQN 中使用的 Q-learning 算法的步骤如下。此算法来自原始的 DQN 论文,该论文可在arxiv.org/pdf/1312.5602.pdf找到:

  • 第 1 步:我们需要初始化回放记忆 D

  • 第 2 步:我们需要用随机权重初始化动作值函数 Q

  • 第 3 步:观察初始状态值

  • 第 4 步:重复

    Choose an action a       
    with probability ε we need to select a random action       
    otherwise we need to select a = argmaxa'Q(s,a')   
    Perform action a   
    Check reward r and new state s'   
    store the gameplay experience <s, a, r, s'> in replay memory D   
    sample random transitions <ss, aa, rr, ss'> from replay memory D   
    calculate target for each minibatch transition       
    if ss' is terminal state then tt = rr       
    otherwise tt = rr + γmaxa'Q(ss', aa')   
    We need to train the Q network using (tt - Q(ss, aa))² as loss   
    s = s'
    until terminated
    

我们正在使用 Q-learning 和 DQN 来实现空间入侵者游戏机器人。所以,让我们开始编码。

实现空间入侵者游戏机器人

在本节中,我们将使用 DQN 和 Q-learning 来编写空间入侵者游戏。对于编码,我们将使用gymTensorFlowvirtualenv库。你可以通过使用这个 GitHub 链接查看整个代码:github.com/jalajthanaki/SpaceInvaders_gamingbot

我们正在使用卷积神经网络(CNN)。在这里,我们已经在单独的文件中定义了 CNN。这个文件的名称是convnet.py。请看下面的截图:以下图:

实现空间入侵者游戏机器人

图 11.16:convnet.py的代码片段

你可以通过这个 GitHub 链接查看代码:github.com/jalajthanaki/SpaceInvaders_gamingbot/blob/master/convnet.py

我们在dqn.py脚本中定义了 DQN 算法。你可以参考以下截图中的代码片段:

实现空间入侵者游戏机器人

图 11.17:dqn.py的代码片段

对于训练,我们已经在train.py中定义了我们的训练逻辑。你可以参考以下截图中的代码片段:

实现空间入侵者游戏机器人

图 11.18:train.py 的代码片段

最后,我们将所有这些独立的脚本导入到主atari.py脚本中,在该脚本中,我们定义了所有参数值。您可以参考以下截图中的代码片段:

实现空间入侵者游戏机器人

图 11.19:atari.py 的代码片段

您可以通过执行以下命令开始训练:

$ python atari.py --game SpaceInvaders-v0 --display true

训练这个机器人以达到人类水平的表现至少需要 3-4 天的训练。我没有提供那么多的训练,但您肯定可以做到。您可以查看以下截图中的训练输出:以下图:

实现空间入侵者游戏机器人

图 11.20:训练步骤的输出片段

您可以通过参考以下截图来查看游戏环境初始得分的代码片段:

实现空间入侵者游戏机器人

图 11.21:游戏机器人前几场比赛得分的代码片段

要停止训练,将有两个参数:要么当我们的损失函数值在几次迭代中变为常数时结束训练,要么完成所有训练步骤。在这里,我们定义了 50,000 个训练步骤。您可以参考以下截图中的训练输出代码片段:

实现空间入侵者游戏机器人

图 11.22:训练日志的代码片段

您可以通过查看以下截图来查看游戏机器人经过 1,000 次迭代后的得分:

实现空间入侵者游戏机器人

图 11.23:经过 1,000 次迭代的游戏机器人的代码片段

我已经为您上传了预训练模型。您可以通过使用此 GitHub 链接下载它:github.com/jalajthanaki/SpaceInvaders_gamingbot/tree/master/model

现在是时候构建乒乓球游戏的机器人了。如果您使用单个 GPU 训练这个机器人一周,它可以击败游戏制造商团队编写的 AI 规则。因此,我们的智能体肯定会比计算机智能体表现得更好。

构建乒乓球游戏机器人

在本节中,我们将探讨如何构建一个能够学习乒乓球游戏的游戏机器人。在我们开始之前,我们将查看构建乒乓球游戏机器人所使用的方法和概念。

理解关键概念

在本节中,我们将介绍构建乒乓球游戏机器人的一些方面,具体如下:

  • 游戏机器人的架构

  • 游戏机器人的方法

游戏机器人的架构

为了开发乒乓球游戏机器人,我们选择了一个基于神经网络的方案。我们神经网络的架构至关重要。让我们一步一步地查看架构组件:

  1. 我们将游戏屏幕作为输入,并按照 DQN 算法对其进行预处理。

  2. 我们将预处理后的屏幕传递给一个神经网络 (NN)。

  3. 我们使用梯度下降来更新 NN 的权重。

  4. 权重 [1]: 此矩阵持有传递到隐藏层的像素权重。维度将是 [200 x 80 x 80] – [200 x 6400]。

  5. 权重 [2]: 此矩阵持有传递到输出的隐藏层权重。维度将是 [1 x 200]。

您可以参考以下图表:

游戏机器人架构

图 11.24:Pong 游戏机器人的神经网络架构

当我们看到这个游戏机器人的详细方法时,NN 的每个组件的任务更有意义。

游戏机器人的方法

为了构建 Pong 游戏机器人,我们将采用以下方法:

  • 对于实现,我们使用预处理后的图像向量,它是一个 [6400 x 1] 维度数组。

  • 在神经网络 (NN) 的帮助下,我们可以计算向上移动的概率。

  • 在那个概率分布的帮助下,我们将决定智能体是否向上移动。

  • 如果游戏回合结束,这意味着游戏智能体以及对手都错过了球。在这种情况下,我们需要找出我们的游戏智能体是赢了还是输了。

  • 当剧集结束时,这意味着如果任何一名玩家得分达到 21 分,我们需要传递结果。借助损失函数,我们可以找到错误值。我们应用梯度下降算法来找出我们的神经网络权重应该更新的方向。基于反向传播算法,我们将错误传播回网络,以便我们的网络可以更新权重。

  • 一旦完成 10 个剧集,我们需要汇总梯度,然后,我们沿着梯度的方向更新权重。

  • 重复此过程,直到我们的网络权重调整完毕,我们可以击败电脑。

现在让我们来介绍编码步骤。

实现 Pong 游戏机器人

这些是我们需要遵循的实现步骤:

  • 参数初始化

  • 以矩阵形式存储的权重

  • 更新权重

  • 如何移动智能体

  • 使用神经网络理解过程

您可以通过使用此 GitHub 链接来参考整个代码:github.com/jalajthanaki/Atari_Pong_gaming_bot

参数初始化

首先,我们定义并初始化我们的参数:

  • batch_size: 此参数表示在更新我们网络的权重之前应该玩多少轮游戏。

  • gamma: 这是折扣因子。我们使用它来折扣游戏旧动作对最终结果的影响。

  • decay_rate: 此参数用于更新权重。

  • num_hidden_layer_neurons: 此参数表示我们应该在隐藏层中放置多少个神经元。

  • 学习率: 这是我们游戏代理从结果中学习的速度,以便我们可以计算新的权重。学习率越高,我们对外部结果的反应越强烈,而学习率越低,我们对外部结果的反应就越不强烈。

您可以参考以下截图所示的代码片段:

参数初始化

图 11.25:参数初始化

以矩阵形式存储的权重

神经网络的权重以矩阵的形式存储。NN 的第一层是一个 200 x 6400 的矩阵,表示隐藏层的权重。如果我们使用符号w1_ij,那么这意味着我们正在表示第i^(th)个神经元在层 1 中对输入像素j的权重。第二层是一个 200 x 1 的矩阵,表示权重。这些权重是隐藏层的输出。对于层 2,元素w2_i表示放置在隐藏层第*i^(th)*个神经元激活上的权重。

您可以参考以下截图给出的代码片段:

以矩阵形式存储的权重

图 11.26:权重矩阵

更新权重

为了更新权重,我们将使用 RMSprop。您可以参考以下论文以了解有关此函数的更多详细信息:

sebastianruder.com/optimizing-gradient-descent/index.html#rmsprop。请参考以下图示。

更新权重

图 11.27:RMSprop 方程

代码显示在以下截图:

更新权重

图 11.28:更新权重的代码片段

如何移动代理

在预处理输入的帮助下,我们将权重矩阵传递给神经网络。我们需要生成告诉代理向上移动的概率。您可以参考以下截图所示的代码片段:

如何移动代理

图 11.29:移动代理的代码片段

我们已经完成了所有主要的辅助函数。我们需要将这些逻辑应用到神经网络中,以便它能够根据观察结果生成游戏代理向上移动的概率。

使用神经网络理解过程

这些步骤可以帮助我们生成代理向上移动的概率,以便它们可以决定何时向上移动

  • 我们需要通过将权重[1]和observation_matrix之间的点积来计算隐藏层值。权重[1]是一个 200 x 6400 的矩阵,而observation_matrix是一个 6400 x 1 的矩阵。输出矩阵的维度是 200 x 1。在这里,我们使用了 200 个神经元。Q 函数的每一行代表一个神经元的输出。

  • 我们将对隐藏层值应用非线性函数 ReLU。

  • 我们正在使用隐藏层的激活值来计算输出层的值。再次,我们在hidden_layer_values [200 x 1]和权重[2] [1 x 200]之间执行点积。这个点积给我们一个单一值[1 x 1]。

  • 最后,我们将 sigmoid 函数应用于输出值。这将给出一个关于概率的答案。输出值介于 0 和 1 之间。

你可以参考以下屏幕截图中的代码片段:

使用神经网络理解过程

图 11.30:使用神经网络的过程代码片段

要运行此代码,你需要执行以下命令:

$ python me_Pong.py

如果你想要构建一个能够打败电脑的机器人,那么你至少需要在单个 GPU 上训练三天到四天。你可以在以下屏幕截图中参考机器人的输出:

使用神经网络理解过程

图 11.31:Pong 游戏机器人的输出

你可以在以下屏幕截图中看到训练日志:

使用神经网络理解过程

图 11.32:Pong 游戏机器人的训练日志

现在让我们仅作娱乐地构建一个游戏机器人。这个机器人使用 Flappy Bird 游戏环境。

仅作娱乐 - 实现 Flappy Bird 游戏机器人

在本节中,我们将构建 Flappy Bird 游戏机器人。这个游戏机器人是使用 DQN 构建的。你可以在以下 GitHub 链接中找到完整的代码:github.com/jalajthanaki/DQN_FlappyBird

这个机器人有一个预训练的模型,所以你可以使用预训练的模型来测试它。为了运行这个机器人,你需要执行以下命令:

$ python deep_q_network.py

你可以在以下屏幕截图中看到输出:

仅作娱乐 - 实现 Flappy Bird 游戏机器人

图 11.33:Flappy Bird 游戏机器人的输出

你可以在这次实现中看到我们迄今为止所研究的所有概念的组合,所以请确保你探索这段代码。这将是本章的练习。

摘要

恭喜读者们;你们已经到达了本章的结尾!我们在这章中介绍了与强化学习相关的基本概念。你学习了构建游戏机器人的各种概念和算法。你还了解了深度 Q 学习算法是如何工作的。使用gym库,我们加载了游戏世界。通过使用dqn库,我们将能够训练模型。训练一个能够击败人类级别专家的游戏机器人需要花费大量时间。所以,我只训练了几个小时。如果你想训练更长的时间,你绝对可以做到。我们尝试构建了各种简单的 Atari 游戏,例如简单的路径寻找游戏机器人、太空侵略者、乒乓球和 Flappy Bird。你可以将这种基本方法扩展到更大的游戏环境中。如果你想更新自己并做出贡献,那么你可以查看 OpenAI 的 GitHub 仓库:github.com/openai。深度学习的新闻和博客部分可以在以下链接找到:deepmind.com/blog/

在接下来的部分,你将找到一个附录,可以帮助你获得一些额外信息。这些额外信息将帮助你在构建机器学习(ML)应用或参加黑客马拉松或其他竞赛时。我还提供了一些可以帮助你在构建 ML 应用时的速查表。