TensorFlow Lite,ML Kit 和 Flutter 移动深度学习(三)
原文:Mobile Deep Learning with TensorFlow Lite, ML Kit and Flutter
八、基于强化神经网络的国际象棋引擎
在几个在线应用商店以及几乎每个软件商店中,游戏都提供了自己的完整版块。 游戏的重要性和热情不容忽视,这就是为什么全世界的开发人员都在不断尝试开发出更好,更吸引人的游戏的原因。
在流行的棋盘游戏世界中,国际象棋是全世界最有竞争力和最复杂的游戏之一。 已经尝试了一些强大的自动化程序来下棋和与人类竞争。 本章将讨论 DeepMind 的开发人员所使用的方法,他们创建了 Alpha Zero,这是一种自学算法,可以自学下棋,从而能够以一个单打击败市场上当时最好的国际象棋 AI,Stockfish 8。 在短短 24 小时的训练中得分较高。
在本章中,我们将介绍您需要理解的概念,以便构建这种深度强化学习算法,然后构建示例项目。 请注意,该项目将要求您具有 Python 和机器学习的丰富知识。
我们将在本章介绍以下主题:
- 强化学习导论
- 手机游戏中的强化学习
- 探索 Google 的 DeepMind
- 适用于 Connect 4 的 Alpha 类零 AI
- 基础项目架构
- 为国际象棋引擎开发 GCP 托管的 REST API
- 在 Android 上创建简单的国际象棋 UI
- 将国际象棋引擎 API 与 UI 集成
让我们从讨论增强学习智能体在手机游戏中的用法和普及程度开始。
强化学习导论
在过去的几年中,强化学习已成为机器学习研究人员中一个重要的研究领域。 人们越来越多地使用它来构建能够在任何给定环境中表现更好的智能体,以寻求对他们所执行行为的更好回报。 简而言之,这为我们提供了强化学习的定义–在人工智能领域,这是一种算法,旨在创建虚拟的智能体,它可在任何给定条件下,在环境中执行动作,在执行一系列动作后,取得最佳的奖励。
让我们尝试通过定义与通用强化学习算法关联的变量来赋予此定义更多的结构:
- 智能体:执行动作的虚拟实体。 是替换游戏/软件的指定用户的实体。
- 操作(
a):智能体可以执行的可能操作。 - 环境(
e):在软件/游戏中可用的一组场景。 - 状态(
S):所有方案的集合,以及其中可用的配置。 - 奖励(
R):对于智能体执行的任何操作返回的值,然后智能体尝试将其最大化。 - 策略(
π):智能体用来确定接下来必须执行哪些操作的策略。 - 值(
V):R是短期每动作奖励,而值是在一组动作结束时预期的总奖励。V[π](s)通过遵循状态S下的策略π来定义预期的总回报。
下图显示了该算法的流程:
尽管我们在前面的定义列表中没有提到观察者,但必须有观察者或评估者才能产生奖励。 有时,观察者本身可能是一个复杂的软件,但是通常,这是一个简单的评估函数或指标。
要获得关于强化学习的更详细的想法,您可以阅读这个页面上的 Wikipedia 文章。 有关正在使用的强化学习智能体的快速样本,请阅读以下 DataCamp 文章。
在下一部分中,我们将学习强化学习在手机游戏中的地位。
手机游戏中的强化学习
出于各种原因而希望构建具有游戏性的 AI 的开发人员中,强化学习已变得越来越流行-只需检查 AI 的功能,建立可以帮助专业人士改善游戏水平的训练智能体等等。 从研究人员的角度来看,游戏为强化学习智能体提供了最佳的测试环境,可以根据经验做出决策并学习在任何给定环境中的生存/成就。 这是因为可以使用简单而精确的规则设计游戏,从而可以准确预测环境对特定动作的反应。 这使得更容易评估强化学习智能体的表现,从而为 AI 提供良好的训练基础。 考虑到在玩游戏的 AI 方面的突破,也有人表示,我们向通用 AI 的发展速度比预期的要快。 但是强化学习概念如何映射到游戏?
让我们考虑一个简单的游戏,例如井字棋。 另外,如果您觉得古怪,只需使用 Google 搜索井字棋,您就会在搜索结果中看到一个游戏!
考虑您正在用计算机玩井字棋。 这里的计算机是智能体。 在这种情况下,环境是什么? 您猜对了–井字棋板以及在环境中管理游戏的一组规则。 井字棋盘上已经放置的标记可以确定环境所在的状态。座席可以在棋盘上放置的X或O是他们可以执行的动作,即输掉,赢得比赛或平局。 或朝着损失,胜利或平局前进是他们执行任何行动后回馈给智能体的奖励。 智能体赢得比赛所遵循的策略是遵循的策略。
因此,从该示例可以得出结论,强化学习智能体非常适合构建学习玩任何游戏的 AI。 这导致许多开发人员想出了象围棋,跳棋,反恐精英等国际象棋以外的几种流行游戏的游戏 AI。 甚至 Chrome Dino 之类的游戏也发现开发人员试图使用 AI 进行游戏。
在下一部分中,我们将简要概述 Google 的 DeepMind,它是游戏 AI 制造商领域中最受欢迎的公司之一。
探索 Google 的 DeepMind
当您谈论自学习人工智能的发展时,DeepMind 可能是最著名的名称之一,这是由于它们在该领域的开创性研究和成就。 自 2015 年 Google 重组以来,DeepMind 在 2014 年被 Google 收购,目前是 Alphabet 的全资子公司。DeepMind 最著名的作品包括 AlphaGo 及其继任者 Alpha Zero。 让我们更深入地讨论这些项目,并尝试了解是什么使它们在当今如此重要。
AlphaGo
2015 年,AlphaGo 成为第一个在19x19棋盘上击败职业围棋选手 Lee Sedol 的计算机软件。 突破被记录下来并作为纪录片发行。 击败李·塞多尔的影响如此之大,以至于韩国 Baduk 协会授予了荣誉 9 丹证书,这实际上意味着围棋选手的游戏技能与神性息息相关。 这是围棋历史上第一次提供 9 荣誉荣誉证书,因此提供给 AlphaGo 的证书编号为 001。ELO 等级为 3,739。
AlphaGo Master 的继任者 AlphaGo Master 在三场比赛中击败了当时统治世界的游戏冠军 Ke Jie。 为了表彰这一壮举,它获得了中国围棋协会颁发的 9 丹证书。 该软件当时的 ELO 等级为 4,858。
但是,这两款软件都被其继任者 AlphaGo Zero 压倒了,后者在 3 天的自学式学习中,能够在 21 分之后以 100:0 的游戏得分击败 AlphaGo,在 89:11 的游戏得分下击败 AlphaGo Master。 天的训练。 40 天后,它的 ELO 评分达到了 5,185,超过了以前所有 Go AI 的技能。
AlphaGo 基于蒙特卡洛树搜索算法,并采用了对生成的和人类玩家游戏日志进行的深度学习。 该模型的初始训练是通过人类游戏进行的。 然后,计算机将与自己对战并尝试改善其游戏性。 树搜索将被设置为一定的深度,以避免巨大的计算开销,在这种开销下,计算机将尝试达到所有可能的动作,然后再进行任何动作。
总而言之,遵循以下过程:
- 最初,该模型将在人类游戏日志上进行训练。
- 一旦在基线上进行了训练,计算机将使用在先前步骤中训练过的模型与自己竞争,并使用有上限的蒙特卡洛树搜索来确保进行移动而不会长时间停滞该软件。 这些游戏的日志已生成。
- 然后对生成的游戏进行了训练,从而改善了整体模型。
现在,让我们讨论 Alpha Zero。
Alpha Zero
Alpha Zero 是 AlphaGo Zero 的后继产品,它是对算法进行泛化的尝试,以便也可以用于其他棋盘游戏。 Alpha Zero 经过训练可以下棋,将棋(类似于棋的日式游戏)和围棋,其表现与相应游戏的现有 AI 相当。 经过 34 小时的训练,Alpha Zero for Go 击败了经过 3 天训练的 AlphaGo Zero,得分为 60:40。 这导致 ELO 等级为 4,430。
经过约 9 个小时的训练,Alpha Zero 击败了 TCEC 竞赛 2016 年冠军的 Stockfish 8。 因此,它仍然是迄今为止最强大的国际象棋 AI,尽管有人声称最新版本的 Stockfish 将能够击败它。
AlphaGo Zero 和 Alpha Zero 变体之间的主要区别如下:
- 出现平局的可能性:在围棋中,保证有一名选手获胜,而对于象棋则不是这样。 因此,对 Alpha Zero 进行了修改,以允许并列游戏。
- 对称性:AlphaGo Zero 利用了电路板的对称性。 但是,由于国际象棋不是非对称游戏,因此必须对 Alpha Zero 进行修改以使其工作。
- 硬编码的超参数搜索:Alpha Zero 具有用于超参数搜索的硬编码规则。
- 在 Alpha Zero 的情况下,神经网络会不断更新。
此时,您可能会想,“什么是蒙特卡罗树搜索?”。 让我们尝试回答这个问题!
蒙特卡洛树搜索
当我们谈论象棋,围棋或井字棋等基于当前场景的战略游戏时,我们所谈论的是大量可能的场景和可以在任何情况下在其中的给定点执行的动作。 尽管对于井字棋等较小的游戏,可能的状态和动作的数量在现代计算机可以计算的范围内,但对于游戏可以生成的状态数量,更复杂的游戏(如国际象棋和围棋)呈指数增长。
蒙特卡洛树搜索尝试找到在给定环境下赢得任何游戏或获得更好奖励所需要的正确动作序列。 之所以将其称为树搜索是因为它创建了游戏中所有可能状态的树,并通过创建每个状态的分支来实现其中的所有可能动作。 表示为树中的节点。
让我们考虑以下简单的游戏示例。 假设您正在玩一个游戏,要求您猜一个三位数的数字,每个猜中都有一个相关的奖励。 可能的数字范围是 1 到 5,您可以猜测的次数是 3。 如果您做出准确的猜测,即正确猜测任意给定位置的数字,则将获得 5 分。但是,如果您做出错误的猜测,将得到正确数字两边的线性差值的分数。
例如,如果要猜测的数字是 2,则可能获得以下奖励分数:
- 如果您猜 1,则得分为 4
- 如果您猜 2,则得分为 5
- 如果您猜 3,则得分为 4
- 如果您猜 4,则得分为 3
- 如果您猜 5,则得分为 2
因此,游戏中的最佳总得分为 15,即每个正确的猜测为 5 分。 鉴于此,您可以在每个步骤中的五个选项中进行选择,游戏中可能的状态总数为5 * 5 * 5 = 125,只有一个状态会给出最佳分数。
让我们尝试在树上描绘前面的游戏。 假设您要猜测的数字是 413。在第一步中,您将具有以下树:
做出选择后,您将获得奖励,再次有五个选项可供选择-换句话说,每个节点中有五个分支可以遍历。 在最佳游戏玩法中,将获得以下树:
现在,让我们考虑以下事实:围棋游戏共有3^361个可能状态。 在 AI 采取行动之前尝试计算每种可能性变得不切实际。 这是蒙特卡罗树搜索与上限可信度算法相结合的地方,它比其他方法更具优势,因为它可以终止到任何搜索深度,并且可以产生趋向于最佳分数的结果。 因此,算法不需要遍历树的每个分支。 一旦树形搜索算法意识到任何特定分支的表现不佳,就可以停止沿该路径前进,而专注于表现更好的路径。 而且,它可以尽早终止任何路径并在该点返回预期的回报,从而可以调整 AI 采取任何行动所需的时间。
更确切地说,蒙特卡罗树搜索遵循以下步骤:
-
选择:从树的当前节点中选择最佳回报分支。 例如,在前面的游戏树中,选择除 4 以外的任何分支将产生较低的分数,因此选择了 4。
-
扩展:一旦选择了最佳回报节点,该节点下的树将进一步扩展,从而创建具有该节点可用的所有可能选项(分支)的节点。 这可以理解为从游戏的任何位置布局 AI 的未来动作。
-
模拟:现在,由于事先不知道在扩展阶段创建的哪个未来选项最有回报,因此我们使用强化学习逐个模拟游戏的每个选项。 请注意,与上限可信度上限算法结合使用时,直到结束游戏才算重要。 计算任何
n个步骤的奖励也是一种不错的方法。 -
更新:最后,更新节点和父节点的奖励分数。 尽管不可能回到游戏中,并且由于任何节点的值都已减小,但如果在以后的游戏中的那个阶段找到了更好的替代方案,那么 AI 将不会遵循这条路径,从而通过多次迭代来改善其游戏玩法。
接下来,我们将构建一个系统,该系统的工作原理类似于 Alpha Zero,并尝试学习玩 Connect 4 游戏,该游戏比 Tic-Tac-Toe 游戏要复杂得多,但对我们来说足够大,来解释如何构建类似的国际象棋引擎。
适用于 Connect 4 的类似 Alpha Zero 的 AI
在开始研究可玩 Connect4 的 AI 之前,让我们简要了解一下游戏及其动态。 Connect 4,有时也称为连续四人,连续四人,四人以上,等等,是全世界儿童中最受欢迎的棋盘游戏之一。 我们也可以将它理解为井字棋的更高级版本,在其中您必须水平,垂直或对角放置三个相同类型的标记。 棋盘通常是一个6x7的网格,两个玩家各自玩一个标记。
Connect 4 的规则可能会有所不同,因此让我们为 AI 将学习的规则版本制定一些具体规则:
- 该游戏被模拟为在具有七个空心列和六行的垂直板上玩。 每列在板的顶部都有一个开口,可以在其中插入片段。可以查看已放入板的片段。
- 两位玩家都有 21 个形状像不同颜色硬币的硬币。
- 将硬币放在板上构成一个动作。
- 碎片从顶部的开口下降到最后一行,或者堆积在该列的最后一块。
- 第一个以任意方向连接其任意四枚硬币的玩家,因此彼此之间不会存在任何间隙或其他玩家的硬币获胜。
现在,让我们分解将 Connect 4 播放式自学 AI 分解为子问题的问题:
- 首先,我们需要创建棋盘的虚拟表示。
- 接下来,我们必须创建允许根据游戏规则移动的函数。
- 然后,为了保存游戏状态,我们需要一个状态管理系统。
- 接下来,我们将简化游戏玩法,其中将提示用户进行移动并宣布游戏终止。
- 之后,我们必须创建一个脚本,该脚本可以生成示例游戏玩法,供系统学习。
- 然后,我们必须创建训练函数来训练系统。
- 接下来,我们需要蒙特卡洛树搜索(MCTS)实现。
- 最后,我们需要一个神经网络的实现。
- 除了前面的具体步骤之外,我们还需要为系统创建许多驱动脚本以使其更加可用。
让我们依次移至前面的要点,一次覆盖系统的每个部分。 但是,首先,我们将快速浏览该项目中存在的目录结构和文件,这在本书的 GitHub 存储库中也可以找到。 让我们来看看:
command/:__init__.py:此文件使我们可以将此文件夹用作模块。arena.py:此文件获取并解析用于运行游戏的命令。generate.py:此文件接受并分析自玩招式生成系统的命令。newmodel.py:此文件用于为智能体创建新的空白模型。train.py:此文件用于训练基于增强学习的神经网络如何玩游戏。util/:__init__.py:此文件使我们可以将此文件夹用作模块。arena.py:此文件创建并维护玩家之间进行的比赛的记录,并允许我们在轮到谁之间切换。compat.py:此文件是用于使程序与 Python 2 和 Python 3 兼容的便捷工具。如果您确定正在开发的版本并希望在其上运行,则可以跳过此文件。generate.py:此文件播放一些随机移动的游戏,再加上 MCTS 移动,以生成可用于训练目的的游戏日志。 该文件存储每个游戏的获胜者以及玩家做出的动作。internal.py:此文件创建棋盘的虚拟表示并定义与棋盘相关的函数,例如将棋子放置在棋盘上,寻找获胜者或只是创建新棋盘。keras_model.py:此文件定义充当智能体大脑的模型。 在本项目的后面,我们将更深入地讨论该文件。mcts.py:此文件提供 MCTS 类,该类实质上是蒙特卡罗树搜索的实现。nn.py:此文件提供 NN 类,它是神经网络的实现,以及与神经网络相关的函数,例如拟合,预测,保存等。player.py:此文件为两种类型的播放器提供了类-MCTS 播放器和人工播放器。 MCTS 玩家是我们将训练的智能体,以玩游戏。state.py:这是internal.py文件的包装,提供了用于访问电路板和与电路板相关的函数的类。trainer.py:这使我们可以训练模型。 这与nn.py中提供的内容不同,因为它更专注于涵盖游戏的训练过程,而nn.py中的内容主要是围绕此功能的包装。
接下来,我们将继续探索这些文件中每个文件的一些重要部分,同时遵循我们先前为构建 AI 制定的步骤。
创建棋盘的虚拟表示
您将如何代表 Connect 4 棋盘? 代表 Connect 4 棋盘的两种常用方法以及游戏状态。 让我们来看看:
- 人类可读的长格式:在这种形式中,木板的行和列分别显示在 x 和 y 轴上,并且两个玩家的标记都显示为
x和o, 分别(或任何其他合适的字符)。 可能如下所示:
|1 2 3 4 5 6 7
--+--------------
1|. . . . . . .
2|. . . . . . .
3|. . . . . . .
4|. . . . o x .
5|x o x . o o .
6|o x x o x x o
但是,这种形式有点冗长并且在计算上不是很友好。
- 计算有效的形式:在此形式中,我们将板存储为 2D NumPy 数组:
array([[1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0]], dtype=int8)
以这种方式创建该数组,当将其展平为一维数组时,板位置按顺序排列,就好像该数组实际上是一维数组一样。 前两个位置分别编号为 0 和 1,而第 5 个位置位于第 5 行和第 5 列,编号为 32。通过将前一个代码块中的矩阵与给定的表进行映射,可以轻松理解此条件。 在下图中:
这种形式适合于进行计算,但不适合玩家在游戏过程中观看,因为对于玩家而言很难解密。
- 一旦决定了如何表示电路板及其部件,就可以开始在
util/internal.py文件中编写代码,如下所示:
BOARD_SIZE_W = 7
BOARD_SIZE_H = 6
KEY_SIZE = BOARD_SIZE_W * BOARD_SIZE_H
前几行设置了板子的常数,在这种情况下,是板子上的行数和列数。 我们还通过将它们相乘来计算板上的按键或位置的数量。
- 现在,让我们准备在板上生成获胜位置的代码,如下所示:
LIST4 = []
LIST4 += [[(y, x), (y + 1, x + 1), (y + 2, x + 2), (y + 3, x + 3)] for y in range(BOARD_SIZE_H - 3) for x in range(BOARD_SIZE_W - 3)]
LIST4 += [[(y, x + 3), (y + 1, x + 2), (y + 2, x + 1), (y + 3, x)] for y in range(BOARD_SIZE_H - 3) for x in range(BOARD_SIZE_W - 3)]
LIST4 += [[(y, x), (y, x + 1), (y, x + 2), (y, x + 3)] for y in range(BOARD_SIZE_H) for x in range(BOARD_SIZE_W - 3)]
NO_HORIZONTAL = len(LIST4)
LIST4 += [[(y, x), (y + 1, x), (y + 2, x), (y + 3, x)] for y in range(BOARD_SIZE_H - 3) for x in range(BOARD_SIZE_W)]
LIST4变量存储任何玩家赢得比赛时可以实现的可能组合。
我们不会在此文件中讨论整个代码。 但是,重要的是要了解以下函数及其作用:
get_start_board():此函数以 NumPy 数组的形式返回电路板的空白 2D 数组表示形式。clone_board(board):此函数用于按板级克隆整个 NumPy 数组。get_action(board):此函数返回播放器已修改的数组中的位置。action_to_string(action):此函数将玩家执行的动作的内部数字表示形式转换为可以以易于理解的形式显示给用户的字符串。 例如place_at(board, pos,。player):执行为任何给定玩家在板上放置一块棋子的动作。 它还会更新板。def get_winner(board):此函数确定棋盘当前状态下的游戏是否有赢家。 如果是,则返回获胜玩家的标识符,该标识符将为 1 或 -1。def to_string(board):此函数将板的 NumPy 数组表示形式转换为字符串,该字符串为人类可读的格式。
接下来,我们将研究如何对 AI 进行编程,使其根据游戏规则进行并仅接受有效的动作。
允许根据游戏规则移动
为了确定玩家(无论是人还是机器)做出的动作的有效性,我们需要建立一种机制,在机器的情况下,该机制连续不断地只生成有效的动作,或者不断验证任何人类玩家的输入。 让我们开始吧:
- 可以在
util/generator.py文件的_selfplay(self, state, args)函数中找到一个这样的实例,如以下代码所示:
turn = 0
hard_random_turn = args['hard_random'] if 'hard_random' in args else 0
soft_random_turn = (args['soft_random'] if 'soft_random' in args else 30) + hard_random_turn
history = []
首先,我们将移动切换设置为0,指示游戏开始时尚未进行任何移动。 我们还考虑了用户在其 AI 自行生成的游戏中想要的硬性和软性随机回合的数量。 然后,我们将移动的历史记录设置为空白。
- 现在,我们可以开始为 AI 生成动作,如下所示:
while state.getWinner() == None:
if turn < hard_random_turn:
# random action
action_list = state.getAction()
index = np.random.choice(len(action_list))
(action, key) = action_list[index]
前面的代码说,直到没有游戏的获胜者,都必须生成招式。 在前面的案例中,我们可以看到,只要进行一次随机随机转弯的可能性为真,AI 就会选择一个完全随机的位置来放置其棋子。
- 通过在前面的
if语句中添加else块,我们告诉 AI,只要它需要进行柔和转弯,它就可以检查是否有任何随机位置将其放置在其中,但只能在 MCTS 算法所建议的移动范围内,如下所示:
else:
action_list = self.mcts.getActionInfo(state, args['simulation'])
if turn < soft_random_turn:
# random action by visited count
visited = [1.0 * a.visited for a in action_list]
sum_visited = sum(visited)
assert(sum_visited > 0)
p = [v / sum_visited for v in visited]
index = np.random.choice(len(action_list), p = p)
else:
# select most visited count
index = np.argmax([a.visited for a in action_list])
请注意,如果既不进行硬转弯也不进行软转弯,则坐席会在游戏的那一刻进行最常用的动作,这有望使它朝着胜利迈进。
因此,在非人类玩家的情况下,智能体只能在任何给定阶段在一组填充的有效动作之间进行选择。 对于人类玩家而言,情况并非如此,根据他们的创造力,他有可能尝试做出无效的举动。 因此,当人类玩家做出动作时,需要对其进行验证。
- 可以在
util/player.py文件的getNextAction(self, state)函数中找到验证人类玩家移动的方法,如下所示:
action = state.getAction()
available_x = []
for i in range(len(action)):
a, k = action[i]
x = a % util.BOARD_SIZE_W + 1
y = a // util.BOARD_SIZE_W + 1
print('{} - {},{}'.format(x, x, y))
available_x.append(x)
- 首先,我们现在计算人类玩家可能采取的合法行动,并将其显示给用户。 然后,我们提示用户输入一个动作,直到他们做出有效的动作为止,如下所示:
while True:
try:
x = int(compat_input('enter x: '))
if x in available_x:
for i in range(len(action)):
if available_x[i] == x:
select = i
break
break
except ValueError:
pass
因此,我们根据填充的一组有效动作来验证用户所做的动作。 我们还可以选择向用户显示错误。
接下来,我们将研究程序的状态管理系统,您肯定已经注意到,到目前为止,我们一直在看该代码。
状态管理系统
游戏的状态管理系统是整个程序中最重要的部分之一,因为它控制着所有的游戏玩法,并在 AI 的自学习过程中促进了游戏玩法。 这样可以确保向玩家展示棋盘,并在进行有效的移动。 它还存储了几个与状态有关的变量,这些变量对于游戏进行很有用。 让我们来看看:
- 让我们讨论
util/state.py文件中提供的State类中最重要的特性和函数:
import .internal as util
此类使用util/internal.py文件中定义的名称为util的变量和函数。
__init__(self, prototype = None):此类在启动时,会继承现有状态或创建新状态。 该函数的定义如下:
def __init__(self, prototype = None):
if prototype == None:
self.board = util.get_start_board()
self.currentPlayer = 1
self.winner = None
else:
self.board = util.clone_board(prototype.board)
self.currentPlayer = prototype.currentPlayer
self.winner = prototype.winner
在这里,您可以看到该类可以使用游戏的现有状态启动,并作为参数传递给该类的构造器; 否则,该类将创建一个新的游戏状态。
getRepresentativeString(self):此函数返回可以由人类玩家读取的游戏状态的格式正确的字符串表示形式。 其定义如下:
def getRepresentativeString(self):
return ('x|' if self.currentPlayer > 0 else 'o|') + util.to_oneline(self.board)
状态类中的许多其他重要方法如下:
getCurrentPlayer(self):此方法返回游戏的当前玩家; 也就是说,应该采取行动的玩家。getWinner(self):如果游戏结束,则此方法返回游戏获胜者的标识符。getAction(self):此方法检查游戏是否结束。 如果没有,它将在任何给定状态下返回一组下一个可能的动作。getNextState(self, action):此方法返回游戏的下一个状态; 也就是说,在将当前正在移动的棋子放在棋盘上并评估游戏是否结束之后,它将执行从一种状态到另一种状态的切换。getNnInput(self):此方法返回玩家到目前为止在游戏中执行的动作,并为每个玩家的动作使用不同的标记。
现在,让我们看一下如何改善程序的游戏玩法。
实现游戏玩法
负责控制程序中游戏玩法的文件是util/arena.py文件。
它在Arena类中定义了以下两种方法:
def fight(self, state, p1, p2, count):
stats = [0, 0, 0]
for i in range(count):
print('==== EPS #{} ===='.format(i + 1))
winner = self._fight(state, p1, p2)
stats[winner + 1] += 1
print('stats', stats[::-1])
winner = self._fight(state, p2, p1)
stats[winner * -1 + 1] += 1
print('stats', stats[::-1])
前面的fight()函数管理玩家的胜利/损失或平局的状态。 它确保在每个回合中进行两场比赛,其中每位玩家只能先玩一次。
此类中定义的另一个_fight()函数如下:
def _fight(self, state, p1, p2):
while state.getWinner() == None:
print(state)
if state.getCurrentPlayer() > 0:
action = p1.getNextAction(state)
else:
action = p2.getNextAction(state)
state = state.getNextState(action)
print(state)
return state.getWinner()
此函数负责切换棋盘上的玩家,直到找到赢家为止。
现在,让我们看一下如何生成随机的游戏玩法以使智能体自学。
生成示例游戏
到目前为止,我们已经讨论了util/gameplay.py文件,以演示该文件中与移动规则相关的代码-特别是该文件的自播放函数。 现在,我们来看看这些自玩游戏如何在迭代中运行以生成完整的游戏玩法日志。 让我们开始吧:
- 请考虑此文件提供的
Generator类的generate()方法的代码:
def generate(self, state, nn, cb, args):
self.mcts = MCTS(nn)
iterator = range(args['selfplay'])
if args['progress']:
from tqdm import tqdm
iterator = tqdm(iterator, ncols = 50)
# self play
for pi in iterator:
result = self._selfplay(state, args)
if cb != None:
cb(result)
本质上,此函数负责运行该类的_selfplay()函数,并确定一旦完成自播放后必须执行的操作。 在大多数情况下,您会将输出保存到文件中,然后将其用于训练。
- 这已在
command/generate.py文件中定义。 该脚本可以作为具有以下签名的命令运行:
usage: run.py generate [-h]
[--model, default='latest.h5', help='model filename']
[--number, default=1000000, help='number of generated states']
[--simulation, default=100, help='number of simulations per move']
[--hard, default=0, help='number of random moves']
[--soft, default=1000, help='number of random moves that depends on visited node count']
[--progress, help='show progress bar']
[--gpu, help='gpu memory fraction']
[--file, help='save to a file']
[--network, help='save to remote server']
- 该命令的示例调用如下:
python run.py generate --model model.h5 --simulation 100 -n 5000 --file selfplay.txt --progress
现在,让我们看一下一旦生成自播放日志就可以训练模型的函数。
系统训练
要训练智能体,我们需要创建util/trainer.py文件,该文件提供train()函数。 让我们来看看:
- 签名如下:
train(state, nn, filename, args = {})
该函数接受State类,神经网络类和其他参数。 它还接受文件名,该文件名是包含生成的游戏玩法的文件的路径。 训练后,我们可以选择将输出保存到另一个模型文件中,如command/train.py文件的train()函数所提供的。
- 此命令具有以下签名:
usage: run.py train [-h]
[--progress, help='show progress bar']
[--epoch EPOCH, help='training epochs']
[--batch BATCH, help='batch size']
[--block BLOCK, help='block size']
[--gpu GPU, help='gpu memory fraction']
history, help='history file'
input, help='input model file name'
output, help='output model file name'
历史参数是存储生成的游戏玩法的文件。 输入文件是当前保存的模型文件,而输出文件是将新训练的模型保存到的文件。
- 该命令的示例调用如下:
python run.py train selfplay.txt model.h5 newmodel.h5 --epoch 3 --progress
现在我们已经有了一个训练系统,我们需要创建 MCTS 和神经网络实现。
实现蒙特卡罗树搜索
util/mcts.py文件中提供了完整的 MCTS 算法实现。 该文件提供了 MCTS 类,该类具有以下重要函数:
getMostVisitedAction:此函数返回将状态传递给访问次数最多的操作。getActionInfo:执行任何操作后,此函数返回状态信息。_simulation:此函数执行单个游戏模拟,并返回有关在模拟过程中玩过的游戏的信息。
最后,我们需要创建一个神经网络实现。
实现神经网络
在最后一节中,我们将了解为智能体进行训练而创建的神经网络。 我们将探索util/nn.py文件,该文件提供NN类以及以下重要方法:
__init__(self, filename):如果磁盘上不存在此函数,则使用util/keras_model.py函数创建新模型。 否则,它将模型文件加载到程序中。util/keras_model.py文件中定义的模型是残差 CNN,它与 MCTS 和 UCT 结合使用,表现得像深度强化学习神经网络。 形成的模型具有以下配置:
input_dim: (2, util.BOARD_SIZE_H, util.BOARD_SIZE_W),
policy_dim: util.KEY_SIZE,
res_layer_num: 5,
cnn_filter_num: 64,
cnn_filter_size: 5,
l2_reg: 1e-4,
learning_rate: 0.003,
momentum: 0.9
默认情况下,模型具有五个残差卷积层块。 我们先前在util/internal.py文件中定义了BOARD_SIZE_H,BOARD_SIZE_W和KEY_SIZE常量:
save(self, filename):此函数将模型保存到提供的文件名中。predict(self, x):提供了板状态以及已经进行的移动,此函数输出可以下一步进行的单个移动。fit(self, x, policy, value, batch_size = 256, epochs = 1):此函数用于将新样本拟合到模型并更新权重。
除了上述脚本之外,我们还需要一些驱动脚本。 您可以在该项目的存储库中查找它们,以了解它们的用法。
要运行已完成的项目,您需要执行以下步骤:
- 使用以下命令创建新模型:
python run.py newmodel model.h5
这将创建一个新模型并打印出其摘要。
- 生成示例游戏日志:
python run.py generate --model model.h5 --simulation 100 -n 5000 --file selfplay.txt --progress
在仿真过程中,上一行为 MCTS 生成了 5,000 个示例游戏,深度为 100。
- 训练模型:
python run.py train selfplay.txt model.h5 newmodel.h5 --epoch 3 --progress
前面的命令在游戏文件上训练模型三个时间,并将训练后的模型另存为newmodel.h5。
- 与 AI 对抗:
python run.py arena human mcts,newmodel.h5,100
前面的命令开始与 AI 进行游戏。 在这里,您将在终端中看到一个面板和游戏选项,如下所示:
现在,我们已经成功创建了一个基于 Alpha Zero 的程序来学习玩棋盘游戏,现在我们可以将其推论到国际象棋 AI 上了。 但是,在这样做之前,我们将简要地介绍项目架构。
基础项目架构
为了创建国际象棋引擎,将其作为 REST API 托管在 GCP 上,我们将遵循常规项目架构:
虽然上图提供了该项目的非常简化的概述,但它可以用于更复杂的系统,这些系统可以产生更好的自学习象棋引擎。
GCP 上托管的模型将放置在 EC2 VM 实例中,并将包装在基于 Flask 的 REST API 中。
为国际象棋引擎开发 GCP 托管的 REST API
现在我们已经看到了如何继续进行此项目,我们还需要讨论如何将 Connect 4 的游戏映射到国际象棋,以及如何将国际象棋 RL 引擎部署为 API。
您可以在这个页面上找到我们为该象棋引擎创建的文件。 在将这些文件与 Connect 4 项目中的文件映射之前,让我们快速了解一些最重要的文件:
src/chess_zero/agent/:player_chess.py:此文件描述ChessPlayer类,该类保存有关在任何时间点玩游戏的玩家的信息。 它为与使用蒙特卡洛树搜索来搜索新动作,更改玩家状态以及每个用户在玩游戏期间所需的其他功能的相关方法提供了包装。model_chess.py:此文件描述了此系统中使用的剩余 CNN。src/chess_zero/config/:mini.py:此文件定义国际象棋引擎学习或玩的配置。 您将需要在此处有时调整这些参数,以降低在低端计算机上进行训练期间的批量大小或虚拟 RAM 消耗。src/chess_zero/env/:chess_env.py:此文件描述棋盘的设置,游戏规则以及执行游戏操作所需的函数。 它还包含检查游戏状态和验证移动的方法。src/chess_zero/worker/:evaluate.py:此文件负责与当前最佳模型和下一代模型玩游戏。 如果下一代模型的表现优于 100 款游戏,则它将替代以前的模型。optimize.py:此文件加载当前最佳模型,并在其上执行更多监督的基于学习的训练。self.py:引擎与自己对战并学习新的游戏玩法。sl.py:监督学习的缩写,此文件将来自其他玩家的游戏的 PGN 文件作为输入,并对其进行监督学习。src/chess_zero/play_game/:uci.py:此文件提供了通用国际象棋界面(UCI)标准环境,可以与引擎进行交互。flask_server.py:该文件创建一个 Flask 服务器,该服务器使用国际象棋游戏的 UCI 表示法与引擎进行通信。
现在我们知道每个文件的作用,让我们建立这些文件与 Connect 4 游戏中文件的映射。
还记得我们在讨论 Connect 4 AI 时制定的步骤吗? 让我们看看国际象棋项目是否也遵循相同的步骤:
- 创建棋盘的虚拟代表。 这是在
src/chess_zero/env/chess_env.py文件中完成的。 - 创建允许根据游戏规则进行移动的函数。 这也可以在
src/chess_zero/env/chess_env.py文件中完成。 - 原地的状态管理系统:此功能在许多文件上维护,例如
src/chess_zero/agent/player_chess.py和src/chess_zero/env/chess_env.py。 - 简化游戏:这是通过
src/chess_zero/play_game/uci.py文件完成的。 - 创建一个可以生成示例游戏玩法的脚本,以供系统学习。 尽管此系统未将生成的游戏玩法明确地存储为磁盘上的文件,但该任务由
src/chess_zero/worker/self_play.py执行。 - 创建训练函数来训练系统。 这些训练函数位于
src/chess_zero/worker/sl.py和src/chess_zero/worker/self.py处。 - 现在,我们需要一个 MCTS 实现。 可以在
src/chess_zero/agent/player_chess.py的文件的移动搜索方法中找到该项目的 MCTS 实现。 - 神经网络的实现:
src/chess_zero/agent/model_chess.py中定义了项目的神经网络。
除了前面的映射之外,我们还需要讨论 Universal Chess Interface 和 Flask 服务器脚本,这两个都是游戏性和 API 部署所必需的。
了解通用国际象棋界面
/src/chess_zero/play_game/uci.py上的文件为引擎创建了通用国际象棋界面。 但是,UCI 到底是什么?
UCI 是 Rudolf Huber 和 Stefan Meyer-Kahlen 引入的一种通信标准,它允许在任何控制台环境中使用国际象棋引擎进行游戏。 该标准使用一小组命令来调用国际象棋引擎,以搜索并输出板子任何给定位置的最佳动作。
通过 UCI 进行的通信与标准输入/输出发生,并且与平台无关。 在我们程序的 UCI 脚本中可用的命令如下:
uci:打印正在运行的引擎的详细信息。isready:这查询引擎是否准备好进行对抗。ucinewgame:这将启动带有引擎的新游戏。position [fen | startpos] moves:此设置板的位置。 如果用户从非起始位置开始,则用户需要提供 FEN 字符串来设置板。go:这要求引擎进行搜索并提出最佳建议。quit:这将结束游戏并退出界面。
以下代码显示了带有 UCI 引擎的示例游戏玩法:
> uci
id name ChessZero
id author ChessZero
uciok
> isready
readyok
> ucinewgame
> position startpos moves e2e4
> go
bestmove e7e5
> position rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 1 moves g1f3
> go
bestmove b8c6
> quit
要快速生成任何板位置的 FEN 字符串,可以使用板编辑器。
现在,让我们讨论一下 Flask 服务器脚本以及如何在 GCP 实例上部署它。
在 GCP 上部署
该国际象棋引擎程序需要存在 GPU。 因此,我们必须遵循其他步骤,才能在 GCP 实例上部署脚本。
大致的工作流程如下:
- 请求增加帐户可用的 GPU 实例的配额。
- 创建基于 GPU 的计算引擎实例。
- 部署脚本。
我们将在以下各节中详细介绍这些步骤。
请求增加 GPU 实例的配额
第一步将是请求增加 GPU 实例的配额。 默认情况下,您的 GCP 帐户上可拥有的 GPU 实例数为 0。此限制由您的帐户的配额配置设置,您需要请求增加。 这样做,请按照下列步骤操作:
- 通过这里打开 Google Cloud Platform 控制台。
- 在左侧菜单上,单击“IAM&Admin | 配额”,如以下屏幕截图所示:
- 单击
Metrics过滤器,然后键入 GPU 以找到读取 GPU(所有区域)的条目,如以下屏幕截图所示:
- 选择条目,然后单击“编辑配额”。
- 系统将要求您提供身份证明,包括您的电话号码。 填写详细信息,然后单击“下一步”。
- 输入您想要将 GPU 配额设置为的限制(最好是
1,以避免滥用)。 另外,请提供您提出要求的理由,例如学术研究,机器学习探索或任何适合您的东西! - 单击“提交”。
提出要求后,大约需要 10 到 15 分钟才能将您的配额增加/设置为您指定的数量。 您将收到一封电子邮件,通知您有关此更新。 现在,您准备创建一个 GPU 实例。
创建一个 GPU 实例
下一步是创建 GPU 实例。 创建 GPU 实例的过程与创建非 GPU 实例的过程非常相似,但是需要额外的步骤。 让我们快速完成所有这些步骤:
- 在您的 Google Cloud Platform 仪表板上,单击左侧导航菜单中的“Compute Engine | VM 实例”。
- 单击“创建实例”。
- 单击“计算机类型选择”部分正下方的 CPU 平台和 GPU,如以下屏幕截图所示:
- 单击“添加 GPU”(大加号(
+)按钮)。 选择要附加到此 VM 的 GPU 类型和 GPU 数量。 - 将启动盘操作系统更改为 Ubuntu 版本 10.10。
- 在“防火墙”部分中,检查 HTTP 和 HTTPS 通信权限,如以下屏幕截图所示:
- 单击表单底部的“创建”。
几秒钟后,您的实例将成功创建。 如果遇到任何错误,例如超出了区域资源限制,请尝试更改要在其中创建实例的区域/区域。这通常是一个临时问题。
现在,我们可以部署 Flask 服务器脚本。
部署脚本
现在,我们将部署 Flask 服务器脚本。 但是在我们这样做之前,让我们先看一下该脚本的作用:
- 脚本的前几行导入了必要的模块,脚本才能正常工作:
from flask import Flask, request, jsonify
import os
import sys
import multiprocessing as mp
from logging import getLogger
from chess_zero.agent.player_chess import ChessPlayer
from chess_zero.config import Config, PlayWithHumanConfig
from chess_zero.env.chess_env import ChessEnv
from chess_zero.agent.model_chess import ChessModel
from chess_zero.lib.model_helper import load_best_model_weight
logger = getLogger(__name__)
- 其余代码放入
start()函数中,该函数由config对象实例化:
def start(config: Config):
## rest of the code
- 以下几行创建了引擎和人类玩家的实例,并在脚本开始运行时重置了游戏环境:
def start(config: Config):
...
PlayWithHumanConfig().update_play_config(config.play)
me_player = None
env = ChessEnv().reset()
...
- 将创建模型,并使用以下代码将模型的最佳权重加载到其中:
def start(config: Config):
...
model = ChessModel(config)
if not load_best_model_weight(model):
raise RuntimeError("Best model not found!")
player = ChessPlayer(config, model.get_pipes(config.play.search_threads))
...
- 前面代码中的最后一行创建具有指定配置和模型知识的国际象棋引擎玩家实例:
def start(config: Config):
...
app = Flask(__name__)
@app.route('/play', methods=["GET", "POST"])
def play():
data = request.get_json()
print(data["position"])
env.update(data["position"])
env.step(data["moves"], False)
bestmove = player.action(env, False)
return jsonify(bestmove)
...
前面的代码创建了 Flask 服务器应用的实例。 定义/play路由,使其可以接受位置并移动参数,这与我们先前在 UCI 游戏中使用的命令相同。
- 游戏状态将更新,并且要求象棋引擎计算下一个最佳移动。 这以 JSON 格式返回给用户:
def start(config: Config):
...
app.run(host="0.0.0.0", port="8080")
脚本的最后一行在主机0.0.0.0处启动 Flask 服务器,这意味着脚本将监听其运行所在设备的所有打开的 IP。 指定的端口为8080。
-
最后,我们将脚本部署到我们创建的 VM 实例。 为此,请执行以下步骤:
-
打开 GCP 控制台的 VM 实例页面。
-
输入在上一节中创建的 VM 后,单击
SSH按钮。 -
SSH 会话激活后,通过运行以下命令来更新系统上的存储库:
sudo apt update
- 接下来,使用以下命令克隆存储库:
git clone https://github.com/PacktPublishing/Mobile-Deep-Learning-Projects.git
- 将当前工作目录更改为
chess文件夹,如下所示:
cd Mobile-Deep-Learning-Projects/Chapter8/chess
- 为 Python3 安装 PIP:
sudo apt install python3-pip
- 安装项目所需的所有模块:
pip3 install -r requirements.txt
- 为最初的监督学习提供训练 PGN。 您可以从这里下载示例 PGN。
ficsgamesdb2017.pgn文件包含 5,000 个已存储的游戏。 您需要将此文件上传到data/play_data/文件夹。 - 运行监督学习命令:
python3 src/chess_zero/run.py sl
- 运行自学习命令:
python3 src/chess_zero/run.py self
当您对程序可以自行播放的时间感到满意时,请使用Ctrl + C/Z停止脚本。
- 运行以下命令以启动服务器:
python3 src/chess_zero/run.py server
现在,您应该能够将职位和移动发送到服务器并获得响应。 让我们快速测试一下。 使用 Postman 或其他任何用于 API 测试的工具,我们将使用 FEN 字符串向 API 发出请求,以设置位置和正在进行的移动。
假设您的 VM 实例正在公共 IP 地址上运行(在 VM 实例仪表板的实例条目上可见)1.2.3.4。 在这里,我们发送以下POST请求:
endpoint: http://1.2.3.4:8080/play
Content-type: JSON
Request body:
{
"position": "r1bqk2r/ppp2ppp/2np1n2/2b1p3/2B1P3/2N2N2/PPPPQPPP/R1B1K2R w KQkq - 0 1",
"moves": "f3g5"
}
先前代码的输出为"h7h6"。 让我们直观地了解这种交互。 FEN 中定义的板看起来如下:
我们告诉服务器这是怀特的举动,而怀特玩家的举动是f3g5,这意味着将怀特骑士移动到板上的 G5 位置。 我们传递给 API 的棋盘 FEN 字符串中的'w'表示白人玩家将进行下一回合。
引擎通过将 H7 处的棋子移动到 H6 进行响应,威胁到马的前进,如以下屏幕快照所示:
现在,我们可以将此 API 与 Flutter 应用集成!
在 Android 上创建简单的国际象棋 UI
现在,我们了解了强化学习以及如何使用它来开发可部署到 GCP 的国际象棋引擎,让我们为游戏创建 Flutter 应用。 该应用将具有两个播放器–用户和服务器。 用户是玩游戏的人,而服务器是我们在 GCP 上托管的国际象棋引擎。 首先,用户采取行动。 记录此移动并将其以 POST 请求的形式发送到国际象棋引擎。 然后,国际象棋引擎以自己的动作进行响应,然后在屏幕上进行更新。
我们将创建一个简单的单屏应用,将棋盘和棋子放置在中间。 该应用将显示如下:
该应用的小部件树如下所示:
让我们开始编写应用代码。
将依赖项添加到pubspec.yaml
首先,将chess_vectors_flutter包添加到pubspec.yaml文件中,以便在将要构建的棋盘上显示实际的棋子。 将以下行添加到pubspec.yaml的依赖项部分:
chess_vectors_flutter: ">=1.0.6 <2.0.0"
运行flutter pub get安装包。
将棋子放置在正确的位置可能会有些棘手。 让我们了解将所有片段放置在正确位置的约定。
了解映射结构
我们将首先创建一个名为chess_game.dart的新 dart 文件。 这将包含所有游戏逻辑。 在文件内部,我们声明一个名为ChessGame的有状态小部件:
- 要将棋子映射到棋盘的正方形,我们将使用与构建模型时相同的符号,以便每个正方形均由字母和数字表示。 我们将在
ChessGameState内创建一个列表squareList,以便我们可以存储所有索引的正方形,如下所示:
var squareList = [
["a8","b8","c8","d8","e8","f8","g8","h8"],
["a7","b7","c7","d7","e7","f7","g7","h7"],
["a6","b6","c6","d6","e6","f6","g6","h6"],
["a5","b5","c5","d5","e5","f5","g5","h5"],
["a4","b4","c4","d4","e4","f4","g4","h4"],
["a3","b3","c3","d3","e3","f3","g3","h3"],
["a2","b2","c2","d2","e2","f2","g2","h2"],
["a1","b1","c1","d1","e1","f1","g1","h1"],
];
- 为了将正确的棋子存储在正确的正方形中并根据玩家的移动来更新它们,我们将创建一个名为
board的HashMap:
HashMap board = new HashMap<String, String>();
HashMap的键将包含正方形的索引,而值将是正方形将保留的片段。 我们将使用一个字符串来表示一块特定的作品,该字符串将根据作品的名称包含一个字母。 例如,K代表王,B代表相。 我们通过使用大写和小写字母来区分白色和黑色部分。 大写字母代表白色,小写字母代表黑色。 例如,K代表白王,b代表黑相。 board['e7'] = "P"表示索引为'e7'的盒子当前有一个白色棋子。
- 现在,让我们将它们放置在初始位置。 为此,我们需要定义
initializeBoard()方法,如下所示:
void initializeBoard() {
setState(() {
for(int i = 8; i >= 1; i--) {
for(int j = 97; j <= 104; j++) {
String ch = String.fromCharCode(j)+'$i';
board[ch] = " ";
}
}
//Placing White Pieces
board['a1'] = board['h1']= "R";
board['b1'] = board['g1'] = "N";
board['c1'] = board['f1'] = "B";
board['d1'] = "Q";
board['e1'] = "K";
board['a2'] = board['b2'] = board['c2'] = board['d2'] =
board['e2'] = board['f2'] = board['g2'] = board['h2'] = "P";
//Placing Black Pieces
board['a8'] = board['h8']= "r";
board['b8'] = board['g8'] = "n";
board['c8'] = board['f8'] = "b";
board['d8'] = "q";
board['e8'] = "k";
board['a7'] = board['b7'] = board['c7'] = board['d7'] =
board['e7'] = board['f7'] = board['g7'] = board['h7'] = "p";
});
}
在前面的方法中,我们使用一个简单的嵌套循环通过从a到h的所有行以及从 1 到 8 的所有列进行遍历,使用空白字符串初始化哈希映射板的所有索引。 如“步骤 2”中所述,将其放置在其初始位置上。 为了确保在初始化棋盘时重新绘制 UI,我们将整个分配放在setState()中。
- 屏幕启动后,板将被初始化。 为了确保这一点,我们需要覆盖
initState()并从那里调用initializeBoard():
@override
void initState() {
super.initState();
initializeBoard();
}
现在我们对映射棋子有了更好的了解,让我们开始在屏幕上放置棋子的实际图像。
放置实际片段的图像
将片段映射到其初始位置后,我们可以开始放置实际的图像向量:
- 我们首先定义一个名为
mapImages()的函数,该函数采用正方形的索引(即哈希图板的键值)并返回图像:
Widget mapImages(String squareName) {
board.putIfAbsent(squareName, () => " ");
String p = board[squareName];
var size = 6.0;
Widget imageToDisplay = Container();
switch (p) {
case "P":
imageToDisplay = WhitePawn(size: size);
break;
case "R":
imageToDisplay = WhiteRook(size: size);
break;
case "N":
imageToDisplay = WhiteKnight(size: size);
break;
case "B":
imageToDisplay = WhiteBishop(size: size);
break;
case "Q":
imageToDisplay = WhiteQueen(size: size);
break;
case "K":
imageToDisplay = WhiteKing(size: size);
break;
case "p":
imageToDisplay = BlackPawn(size: size);
break;
case "r":
imageToDisplay = BlackRook(size: size);
break;
case "n":
imageToDisplay = BlackKnight(size: size);
break;
case "b":
imageToDisplay = BlackBishop(size: size);
break;
case "q":
imageToDisplay = BlackQueen(size: size);
break;
case "k":
imageToDisplay = BlackKing(size: size);
break;
case "p":
imageToDisplay = BlackPawn(size: size);
break;
}
return imageToDisplay;
}
在前面的函数中,我们构建一个与矩形中所含件名相对应的开关盒块。 我们使用哈希图在特定的正方形中找到片段,然后返回相应的图像。 例如,如果将a1的值传递到squareName中,并且哈希图板具有与键值a1对应的值P,则白兵的图像将存储在变量imageToDisplay中。
请注意,在 64 个棋盘格正方形中,只有 32 个包含棋子。 其余将为空白。 因此,在哈希表board中,将存在没有值的键。 如果squareName没有片段,则将其传递给imageToDisplay变量,该变量将只有一个空容器。
-
在上一步中,我们构建了对应于棋盘上每个正方形的小部件(图像或空容器)。 现在,让我们将所有小部件排列成行和列。
squareName中的特定元素(例如[a1,b1,....,g1])包含应并排放置的正方形。 因此,我们将它们包装成一行并将这些行中的每一个包装成列。 -
让我们从定义
buildRow()方法开始,该方法包含一个列表。 这本质上是sqaureName中的元素列表,并构建完整的行。 该方法如下所示:
Widget buildRow(List<String> children) {
return Expanded(
flex: 1,
child: Row(
children: children.map((squareName) => getImage(squareName)).toList()
),
);
}
在前面的代码片段中,我们迭代使用map()方法传递的列表的每个元素。 这会调用getImage()以获取对应于正方形的适当图像。 然后,我们将所有这些返回的图像添加为一行的子级。 该行将一个子代添加到展开的窗口小部件并返回。
getImage()方法定义如下:
Widget getImage(String squareName) {
return Expanded(
child: mapImages(squareName),
);
}
只需输入squareName的值,然后返回一个扩展的小部件,其中将包含我们先前定义的mapImages返回的图像。 我们稍后将修改此方法,以确保玩家可以拖动每个图像,以便它们可以在棋盘上移动。
- 现在,我们需要构建将包含已构建行的列。 为此,我们需要定义
buildChessBoard()方法,如下所示:
Widget buildChessBoard() {
return Container(
height: 350,
child: Column(
children: widget.squareList.map((row) {
return buildRow(row,);
}).toList()
)
);
}
在前面的代码中,我们迭代了squareList内部的每一行,这些行表示为一个列表。 我们通过调用buildRow()来构建行,并将它们作为子级添加到列中。 此列作为子级添加到容器中并返回。
- 现在,让我们将所有片段以及实际的棋盘图像放到屏幕上。 我们将覆盖
build()方法,以构建由棋盘图像及其碎片组成的小部件栈:
@override
Widget build(BuildContext context) {
return Container(
child: Stack(
children: <Widget>[
Container(
child: new Center(child: Image.asset("assets/chess_board.png", fit: BoxFit.cover,)),
),
Center(
child: Container(
child: buildChessBoard(),
),
)
],
)
);
}
前面的方法使用容器来构建栈,该容器添加存储在assets文件夹中的棋盘图像。 栈的下一个子项是居中对齐的容器,其中所有片段图像都通过对buildChessBoard()的调用以小部件的形式添加为行和列包装。 整个栈作为子级添加到容器中并返回,以便出现在屏幕上。
此时,应用显示棋盘,以及所有放置在其初始位置的棋子。 如下所示:
现在,让我们使这些棋子变得可移动,以便我们可以玩一个真实的游戏。
使片段移动
在本节中,我们将用可拖动的工具包装每块棋子,以便用户能够将棋子拖动到所需位置。 让我们详细看一下实现:
- 回想一下,我们声明了一个哈希图来存储片段的位置。 移动将包括从一个盒子中移出一块并将其放在另一个盒子中。 假设我们有两个变量
'from'和'to',它们存储用于移动片段的盒子的索引。 进行移动后,我们拿起'from'处的片段并将其放入'to'中。 因此,'from'的框变为空。 按照相同的逻辑,我们将定义refreshBoard()方法,该方法在每次移动时都会调用:
void refreshBoard(String from, String to) {
setState(() {
board[to] = board[from];
board[from] = " ";
});
}
from和to变量存储源和目标正方形的索引。 这些值在board HasMhap 中用作键。 进行移动时,from处的棋子会移至to.。此后,from处的方块应该变空。 它包含在setState()中,以确保每次移动后都更新 UI。
- 现在,让我们将其拖曳。 为此,我们将拖动项附加到
getPieceImage()方法返回的木板的每个图像小部件上。 我们通过修改方法来做到这一点:
Widget getImage(String squareName) {
return Expanded(
child: DragTarget<List>(builder: (context, accepted, rejected) {
return Draggable<List>(
child: mapImages(squareName),
feedback: mapImages(squareName),
onDragCompleted: () {},
data: [
squareName,
],
);
}, onWillAccept: (willAccept) {
return true;
}, onAccept: (List moveInfo) {
String from = moveInfo[0];
String to = squareName;
refreshBoard(from, to);
})
);
}
在前面的函数中,我们首先将特定正方形的图像包装在Draggable中。 此类用于感测和跟随屏幕上的拖动手势。 child属性用于指定要拖动的窗口小部件,而反馈内部的窗口小部件用于跟踪手指在屏幕上的移动。 当拖动完成并且用户抬起手指时,目标将有机会接受所携带的数据。 由于我们正在源和目标之间移动,因此我们将添加Draggable作为DragTarget的子代,以便可以在源和目标之间移动小部件。 onWillAccept设置为true,以便可以进行所有移动。
可以修改此属性,以使其具有可以区分合法象棋动作并且不允许拖动非法动作的功能。 放下片段并完成拖动后,将调用onAccept。 moveInfo列表保存有关拖动源的信息。 在这里,我们调用refreshBoard(),并传入from和to的值,以便屏幕可以反映运动。 至此,我们完成了向用户显示初始棋盘的操作,并使棋子可以在盒子之间移动。
在下一节中,我们将通过对托管的国际象棋服务器进行 API 调用来增加应用的交互性。 这些将使游戏栩栩如生。
将国际象棋引擎 API 与 UI 集成
托管的棋牌服务器将作为对手玩家添加到应用中。 用户将是白色的一面,而服务器将是黑色的一面。 这里要实现的游戏逻辑非常简单。 第一步是提供给应用用户。 用户进行移动时,他们将棋盘的状态从状态 X 更改为状态 Y。棋盘的状态由 FEN 字符串表示。 同样,他们将一块from移到一个特定的正方形to移到一个特定的正方形,这有助于他们的移动。 当用户完成移动时,状态 X 的 FEN 字符串及其当前移动(通过将from和to正方形连接在一起而获得)以POST请求的形式发送到服务器。 作为回报,服务器从其侧面进行下一步移动,然后将其反映在 UI 上。
让我们看一下此逻辑的代码:
- 首先,我们定义一个名为
getPositionString()的方法来为应用的特定状态生成 FEN 字符串:
String getPositionString(String move) {
String s = "";
for(int i = 8; i >= 1; i--) {
int count = 0;
for(int j = 97; j <= 104; j++) {
String ch = String.fromCharCode(j)+'$i';
if(board[ch] == " ") {
count += 1;
if(j == 104)
s = s + "$count";
} else {
if(count > 0)
s = s + "$count";
s = s + board[ch];count = 0;
}
}
s = s + "/";
}
String position = s.substring(0, s.length-1) + " w KQkq - 0 1";
var json = jsonEncode({"position": position, "moves": move});
}
在前面的方法中,我们将move作为参数,它是from和to变量的连接。 接下来,我们为棋盘的当前状态创建 FEN 字符串。 创建 FEN 字符串背后的逻辑是,我们遍历电路板的每一行并为该行创建一个字符串。 然后将生成的字符串连接到最终字符串。
让我们借助示例更好地理解这一点。 考虑一个rnbqkbnr/pp1ppppp/8/1p6/8/3P4/PPP1PPPP/RNBQKBNR w KQkq - 0 1的 FEN 字符串。 在此,每行可以用八个或更少的字符表示。 特定行的状态通过使用分隔符“/”与另一行分开。 对于特定的行,每件作品均以其指定的符号表示,其中P表示白兵,b表示黑相。 每个占用的正方形均由件符号明确表示。 例如,PpkB指示板上的前四个正方形被白色棋子,黑色棋子,黑色国王和白色主教占据。 对于空盒子,使用整数,该数字表示可传染的空盒子的数量。 注意示例 FEN 字符串中的8。 这表示该行的所有 8 个正方形均为空。 3P4表示前三个正方形为空,第四个方框被白色棋子占据,并且四个正方形为空。
在getPositionString()方法中,我们迭代从 8 到 1 的每一行,并为每行生成一个状态字符串。 对于每个非空框,我们只需在's'变量中添加一个表示该块的字符。 对于每个空框,当找到非空框或到达行末时,我们将count的值增加 1 并将其连接到's'字符串。 遍历每一行后,我们添加“/”以分隔两行。 最后,我们通过将生成的's'字符串与w KQkq - 0 1连接来生成位置字符串。 然后,我们通过将jsonEncode()与键值对结合使用来生成所需的 JSON 对象
- 我们使用“步骤 1”的“步骤 1”中的
from和to变量来保存用户的当前移动。 我们可以通过在refreshBoard()方法中添加两行来实现:
void refreshBoard(String from, String to) {
String move= from + to;
getPositionString(move);
.....
}
在前面的代码片段中,我们将from和to的值连接起来,并将它们存储在名为move的字符串变量中。 然后,我们调用getPositionString(),并将move的值传递给参数。
- 接下来,我们使用在上一步中
makePOSTRequest()方法中生成的JSON向服务器发出POST请求:
void makePOSTRequest(var json) async{
var url = 'http://35.200.253.0:8080/play';
var response = await http.post(url, headers: {"Content-Type": "application/json"} ,body: json);
String rsp = response.body;
String from = rsp.substring(0,3);
String to = rsp.substring(3);
}
首先,将国际象棋服务器的 IP 地址存储在url变量中。 然后,我们使用http.post()发出HTTP POST请求,并为 URL,标头和正文传递正确的值。 POST 请求的响应包含服务器端的下一个动作,并存储在变量响应中。 我们解析响应的主体并将其存储在名为rsp的字符串变量中。 响应基本上是一个字符串,是服务器端的源方和目标方的连接。 例如,响应字符串f4a3表示国际象棋引擎希望将棋子以f4正方形移动到a3正方形。 我们使用substring()分隔源和目标,并将值存储在from和to变量中。
- 现在,通过将调用添加到
makePOSTrequest()来从getPositionString()发出 POST 请求:
String getPositionString(String move) {
.....
makePOSTRequest(json);
}
在 FEN 字符串生成板的给定状态之后,对makePOSTrequest()的调用添加在函数的最后。
- 最后,我们使用
refreshBoardFromServer()方法刷新板以反映服务器在板上的移动:
void refreshBoardFromServer(String from, String to) {
setState(() {
board[to] = board[from];
board[from] = " ";
});
}
前述方法中的逻辑非常简单。 首先,我们将映射到from索引正方形的片段移动到to索引正方形,然后清空from索引正方形。
- 最后,我们调用适当的方法以用最新的动作更新 UI:
void makePOSTRequest(var json) async{
......
refreshBoardFromServer(from, to);
buildChessBoard();
}
发布请求成功完成后,我们收到了服务器的响应,我们将调用refreshBoardFromServer()以更新板上的映射。 最后,我们调用buildChessBoard()以在应用屏幕上反映国际象棋引擎所做的最新动作。
以下屏幕快照显示了国际象棋引擎进行移动后的更新的用户界面:
请注意,黑色的块在白色的块之后移动。 这就是代码的工作方式。 首先,用户采取行动。 它以板的初始状态发送到服务器。 然后,服务器以其移动进行响应,更新 UI。 作为练习,您可以尝试实现一些逻辑以区分有效动作和无效动作。
可以在这个页面中找到此代码。
现在,让我们通过创建材质应用来包装应用。
创建材质应用
现在,我们将在main.dart中创建最终的材质应用。 让我们从以下步骤开始:
- 首先,我们创建无状态窗口小部件
MyApp,并覆盖其build()方法,如下所示:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Chess',
theme: ThemeData(primarySwatch: Colors.blue,),
home: MyHomePage(title: 'Chess'),
);
}
}
- 我们创建一个单独的
StatefulWidget,称为MyHomePage,以便将 UI 放置在屏幕中央。MyHomePage的build()方法如下所示:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Chess'),),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ChessGame()
],
),
),
);
}
- 最后,我们通过在
main.dart中添加以下行来执行整个代码:
void main() => runApp(MyApp());
而已! 现在,我们有一个交互式的国际象棋游戏应用,您可以与聪明的对手一起玩。 希望你赢!
整个文件的代码可以在这个页面中找到。
总结
在此项目中,我们介绍了强化学习的概念以及为什么强化学习在创建游戏性 AI 的开发人员中很受欢迎。 我们讨论了 Google DeepMind 的 AlphaGo 及其兄弟项目,并深入研究了它们的工作算法。 接下来,我们创建了一个类似的程序来玩 Connect 4,然后下棋。 我们将基于 AI 的国际象棋引擎作为 API 部署到 GPU 实例的 GCP 上,并将其与基于 Flutter 的应用集成。 我们还了解了如何使用 UCI 促进国际象棋的无状态游戏。 完成此项目后,您将对如何将游戏转换为强化学习环境,如何以编程方式定义游戏规则以及如何创建用于玩这些游戏的自学智能体有很好的了解。
在下一章中,我们将创建一个应用,该应用可以使低分辨率图像变成非常高分辨率的图像。 我们将在 AI 的帮助下进行此操作。
九、构建图像超分辨率应用
还记得上次和亲人一起旅行并拍了一些漂亮的照片作为记忆,但是当您回到家并刷过它们时,您发现它们非常模糊且质量低下吗? 现在,您剩下的所有美好时光就是您自己的心理记忆和那些模糊的照片。 如果可以使您的照片清晰透明并且可以看到其中的每个细节,那不是很好吗?
超分辨率是基于像素信息的近似将低分辨率图像转换为高分辨率图像的过程。 虽然今天可能还不完全是神奇的,但当技术发展到足以成为通用 AI 应用时,它肯定会在将来挽救生命。
在此项目中,我们将构建一个应用,该应用使用托管在 DigitalOcean Droplet 上的深度学习模型,该模型可以同时比较低分辨率和高分辨率图像,从而使我们更好地了解今天的技术。 我们将使用生成对抗网络(GAN)生成超分辨率图像。
在本章中,我们将介绍以下主题:
- 基本项目架构
- 了解 GAN
- 了解图像超分辨率的工作原理
- 创建 TensorFlow 模型以实现超分辨率
- 构建应用的 UI
- 从设备的本地存储中获取图片
- 在 DigitalOcean 上托管 TensorFlow 模型
- 在 Flutter 上集成托管的自定义模型
- 创建材质应用
让我们从了解项目的架构开始。
基本项目架构
让我们从了解项目的架构开始。
我们将在本章中构建的项目主要分为两个部分:
- Jupyter 笔记本,它创建执行超分辨率的模型。
- 使用该模型的 Flutter 应用,在 Jupyter 笔记本上接受训练后,将托管在 DigitalOcean 中的 Droplet 中。
从鸟瞰图可以用下图描述该项目:
将低分辨率图像放入模型中,该模型是从 Firebase 上托管的 ML Kit 实例中获取的,并放入 Flutter 应用中。 生成输出并将其作为高分辨率图像显示给用户。 该模型缓存在设备上,并且仅在开发人员更新模型时才更新,因此可以通过减少网络延迟来加快预测速度。
现在,让我们尝试更深入地了解 GAN。
了解 GAN
Ian Goodfellow,Yoshua Bengio 和其他人在 NeurIPS 2014 中引入的 GAN 席卷全球。 可以应用于各种领域的 GAN 会根据模型对实际数据样本的学习近似,生成新的内容或序列。 GAN 已被大量用于生成音乐和艺术的新样本,例如下图所示的面孔,而训练数据集中不存在这些面孔:
经过 60 个周期的训练后,GAN 生成的面孔。 该图像取自这里。
前面面孔中呈现的大量真实感证明了 GAN 的力量–在为他们提供良好的训练样本量之后,他们几乎可以学习生成任何类型的模式。
GAN 的核心概念围绕两个玩家玩游戏的想法。 在这个游戏中,一个人说出一个随机句子,另一个人仅仅考虑第一人称使用的单词就指出它是事实还是假。 第二个人唯一可以使用的知识是假句子和实句中常用的单词(以及如何使用)。 这可以描述为由 minimax 算法玩的两人游戏,其中每个玩家都试图以其最大能力抵消另一位玩家所做的移动。 在 GAN 中,第一个玩家是生成器(G),第二个玩家是判别器(D)。 G和D都是常规 GAN 中的神经网络。 生成器从训练数据集中给出的样本中学习,并基于其认为当观察者查看时可以作为真实样本传播的样本来生成新样本。
判别器从训练样本(正样本)和生成器生成的样本(负样本)中学习,并尝试对哪些图像存在于数据集中以及哪些图像进行分类。 它从G获取生成的图像,并尝试将其分类为真实图像(存在于训练样本中)或生成图像(不存在于数据库中)。
通过反向传播,GAN 尝试不断减少判别器能够对生成器正确生成的图像进行分类的次数。 一段时间后,我们希望达到识别器在识别生成的图像时开始表现不佳的阶段。 这是 GAN 停止学习的地方,然后可以使用生成器生成所需数量的新样本。 因此,训练 GAN 意味着训练生成器以从随机输入产生输出,从而使判别器无法将其识别为生成的图像。
判别器将传递给它的所有图像分为两类:
- 真实图像:数据集中存在的图像或使用相机拍摄的图像
- 伪图像:使用某软件生成的图像
生成器欺骗判别器的能力越好,当向其提供任何随机输入序列时,生成的输出将越真实。
让我们以图表形式总结前面关于 GAN 进行的讨论:
GAN 具有许多不同的变体,所有变体都取决于它们正在执行的任务。 其中一些如下:
- 渐进式 GAN:在 ICLR 2018 上的一篇论文中介绍,渐进式 GAN 的生成器和判别器均以低分辨率图像开始,并随着图像层的增加而逐渐受到训练,从而使系统能够生成高分辨率图像。 例如,在第一次迭代中生成的图像为
10x10像素,在第二代中它变为20x20,依此类推,直到获得非常高分辨率的图像为止。 生成器和判别器都在深度上一起增长。 - 条件 GAN:假设您有一个 GAN 可以生成 10 个不同类别的样本,但是在某个时候,您希望它在给定类别或一组类别内生成样本。 这是有条件 GAN 起作用的时候。有条件 GAN 使我们可以生成 GAN 中经过训练可以生成的所有标签中任何给定标签的样本。 在图像到图像的翻译领域中,已经完成了条件 GAN 的一种非常流行的应用,其中将一个图像生成为相似或相同域的另一个更逼真的图像。 您可以通过这个页面上的演示来尝试涂鸦一些猫,并获得涂鸦的真实感版本。
- 栈式 GAN:栈式 GAN 的最流行的应用是基于文本描述生成图像。 在第一阶段,GAN 生成描述项的概述,在第二阶段,根据描述添加颜色。 然后,后续层中的 GAN 将更多细节添加到图像中,以生成图像的真实感版本,如描述中所述。 通过观察堆叠 GAN 的第一次迭代中的图像已经处于将要生成最终输出的尺寸,可以将栈式 GAN 与渐进式 GAN 区别开来。但是,与渐进式 GAN 相似,在第一次迭代中, 图像是最小的,并且需要进一步的层才能将其馈送到判别器。
在此项目中,我们将讨论 GAN 的另一种形式,称为超分辨率 GAN(SRGAN)。 我们将在下一部分中了解有关此变体的更多信息。
了解图像超分辨率的工作原理
几十年来,人们一直在追求并希望能够使低分辨率图像更加精细,以及使高分辨率图像化。 超分辨率是用于将低分辨率图像转换为超高分辨率图像的技术的集合,是图像处理工程师和研究人员最激动人心的工作领域之一。 已经建立了几种方法和方法来实现图像的超分辨率,并且它们都朝着自己的目标取得了不同程度的成功。 然而,近来,随着 SRGAN 的发展,关于使用任何低分辨率图像可以实现的超分辨率的量有了显着的改进。
但是在讨论 SRGAN 之前,让我们了解一些与图像超分辨率有关的概念。
了解图像分辨率
用质量术语来说,图像的分辨率取决于其清晰度。 分辨率可以归类为以下之一:
- 像素分辨率
- 空间分辨率
- 时间分辨率
- 光谱分辨率
- 辐射分辨率
让我们来看看每个。
像素分辨率
指定分辨率的最流行格式之一,像素分辨率最通常是指形成图像时涉及的像素数量。 单个像素是可以在任何给定查看设备上显示的最小单个单元。 可以将几个像素组合在一起以形成图像。 在本书的前面,我们讨论了图像处理,并将像素称为存储在矩阵中的颜色信息的单个单元,它代表图像。 像素分辨率定义了形成数字图像所需的像素元素总数,该总数可能与图像上可见的有效像素数不同。
标记图像像素分辨率的一种非常常见的表示法是以百万像素表示。 给定NxM像素分辨率的图像,其分辨率可以写为(NxM / 1000000)百万像素。 因此,尺寸为2,000x3,000的图像将具有 6,000,000 像素,其分辨率可以表示为 6 兆像素。
空间分辨率
这是观察图像的人可以分辨图像中紧密排列的线条的程度的度量。 在这里,严格说来,图像的像素越多,清晰度越好。 这是由于具有较高像素数量的图像的空间分辨率较低。 因此,需要良好的空间分辨率以及具有良好的像素分辨率以使图像以良好的质量呈现。
它也可以定义为像素一侧所代表的距离量。
时间分辨率
分辨率也可能取决于时间。 例如,卫星或使用无人飞行器(UAV)无人机拍摄的同一区域的图像可能会随时间变化。 重新捕获相同区域的图像所需的时间称为时间分辨率。
时间分辨率主要取决于捕获图像的设备。 如在图像捕捉的情况下,这可以是变型,例如当在路边的速度陷阱照相机中触发特定传感器时执行图像捕捉。 它也可以是常数。 例如,在配置为每x间隔拍照的相机中。
光谱分辨率
光谱分辨率是指图像捕获设备可以记录的波段数。 也可以将其定义为波段的宽度或每个波段的波长范围。 在数字成像方面,光谱分辨率类似于图像中的通道数。 理解光谱分辨率的另一种方法是在任何给定图像或频带记录中可区分的频带数。
黑白图像中的波段数为 1,而彩色(RGB)图像中的波段数为 3。可以捕获数百个波段的图像,其中其他波段可提供有关图像的不同种类的信息。 图片。
辐射分辨率
辐射分辨率是捕获设备表示在任何频带/通道上接收到的强度的能力。 辐射分辨率越高,设备可以更准确地捕获其通道上的强度,并且图像越真实。
辐射分辨率类似于图像每个像素的位数。 虽然 8 位图像像素可以表示 256 个不同的强度,但是 256 位图像像素可以表示2 ^ 256个不同的强度。 黑白图像的辐射分辨率为 1 位,这意味着每个像素只能有两个不同的值,即 0 和 1。
现在,让我们尝试了解 SRGAN。
了解 SRGAN
SRGAN 是一类 GAN,主要致力于从低分辨率图像创建超分辨率图像。
SRGAN 算法的功能描述如下:该算法从数据集中选取高分辨率图像,然后将其采样为低分辨率图像。 然后,生成器神经网络尝试从低分辨率图像生成高分辨率图像。 从现在开始,我们将其称为超分辨率图像。 将超分辨率图像发送到鉴别神经网络,该神经网络已经在高分辨率图像和一些基本的超分辨率图像的样本上进行了训练,以便可以对它们进行分类。
判别器将由生成器发送给它的超分辨率图像分类为有效的高分辨率图像,伪高分辨率图像或超分辨率图像。 如果将图像分类为超分辨率图像,则 GAN 损失会通过生成器网络反向传播,以便下次产生更好的伪造图像。 随着时间的流逝,生成器将学习如何创建更好的伪造品,并且判别器开始无法正确识别超分辨率图像。 GAN 在这里停止学习,被列为受过训练的人。
可以用下图来总结:
现在,让我们开始创建用于超分辨率的 SRGAN 模型。
创建 TensorFlow 模型来实现超分辨率
现在,我们将开始构建在图像上执行超分辨率的 GAN 模型。 在深入研究代码之前,我们需要了解如何组织项目目录。
项目目录结构
本章中包含以下文件和文件夹:
api/:model /:__init __.py:此文件指示此文件的父文件夹可以像模块一样导入。common.py:包含任何 GAN 模型所需的常用函数。srgan.py:其中包含开发 SRGAN 模型所需的函数。weights/:gan_generator.h5:模型的预训练权重文件。 随意使用它来快速运行并查看项目的工作方式。data.py:用于在 DIV2K 数据集中下载,提取和加载图像的工具函数。flask_app.py:我们将使用此文件来创建将在 DigitalOcean 上部署的服务器。train.py:模型训练文件。 我们将在本节中更深入地讨论该文件。
您可以在这个页面中找到项目此部分的源代码。
多样 2K(DIV2K)数据集由图像恢复和增强的新趋势(NTIRE)2017 单张图像超分辨率挑战赛引入,也用于挑战赛的 2018 版本中。
在下一节中,我们将构建 SRGAN 模型脚本。
创建用于超分辨率的 SRGAN 模型
首先,我们将从处理train.py文件开始:
- 让我们从将必要的模块导入项目开始:
import os
from data import DIV2K
from model.srgan import generator, discriminator
from train import SrganTrainer, SrganGeneratorTrainer
前面的导入引入了一些现成的类,例如SrganTrainer,SrganGeneratorTrainer等。 在完成此文件的工作后,我们将详细讨论它们。
- 现在,让我们为权重创建一个目录。 我们还将使用此目录来存储中间模型:
weights_dir = 'weights'
weights_file = lambda filename: os.path.join(weights_dir, filename)
os.makedirs(weights_dir, exist_ok=True)
- 接下来,我们将从 DIV2K 数据集中下载并加载图像。 我们将分别下载训练和验证图像。 对于这两组图像,可以分为两对:高分辨率和低分辨率。 但是,这些是单独下载的:
div2k_train = DIV2K(scale=4, subset='train', downgrade='bicubic')
div2k_valid = DIV2K(scale=4, subset='valid', downgrade='bicubic')
- 将数据集下载并加载到变量后,我们需要将训练图像和验证图像都转换为 TensorFlow 数据集对象。 此步骤还将两个数据集中的高分辨率和低分辨率图像结合在一起:
train_ds = div2k_train.dataset(batch_size=16, random_transform=True)
valid_ds = div2k_valid.dataset(batch_size=16, random_transform=True, repeat_count=1)
- 现在,回想一下我们在“了解 GAN”部分中提供的 GAN 的定义。 为了使生成器开始产生判别器可以评估的伪造品,它需要学习创建基本的伪造品。 为此,我们将快速训练神经网络,以便它可以生成基本的超分辨率图像。 我们将其命名为预训练器。 然后,我们将预训练器的权重迁移到实际的 SRGAN,以便它可以通过使用判别器来学习更多。 让我们构建并运行预训练器:
pre_trainer = SrganGeneratorTrainer(model=generator(), checkpoint_dir=f'.ckpt/pre_generator')
pre_trainer.train(train_ds,
valid_ds.take(10),
steps=1000000,
evaluate_every=1000,
save_best_only=False)
pre_trainer.model.save_weights(weights_file('pre_generator.h5'))
现在,我们已经训练了一个基本模型并保存了权重。 我们可以随时更改 SRGAN 并通过加载其权重从基础训练中重新开始。
- 现在,让我们将预训练器权重加载到 SRGAN 对象中,并执行训练迭代:
gan_generator = generator()
gan_generator.load_weights(weights_file('pre_generator.h5'))
gan_trainer = SrganTrainer(generator=gan_generator, discriminator=discriminator())
gan_trainer.train(train_ds, steps=200000)
请注意,在具有 8 GB RAM 和 Intel i7 处理器的普通计算机上,上述代码中的训练操作可能会花费大量时间。 建议在具有图形处理器(GPU)的基于云的虚拟机中执行此训练。
- 现在,让我们保存 GAN 生成器和判别器的权重:
gan_trainer.generator.save_weights(weights_file('gan_generator.h5'))
gan_trainer.discriminator.save_weights(weights_file('gan_discriminator.h5'))
现在,我们准备继续进行下一部分,在该部分中将构建将使用此模型的 Flutter 应用的 UI。
构建应用的 UI
现在,我们了解了图像超分辨率模型的基本功能并为其创建了一个模型,让我们深入研究构建 Flutter 应用。 在本节中,我们将构建应用的 UI。
该应用的用户界面非常简单:它将包含两个图像小部件和按钮小部件。 当用户单击按钮小部件时,他们将能够从设备的库中选择图像。 相同的图像将作为输入发送到托管模型的服务器。 服务器将返回增强的图像。 屏幕上将放置的两个图像小部件将用于显示服务器的输入和服务器的输出。
下图说明了应用的基本结构和最终流程:
该应用的三个主要小部件可以简单地排列在一列中。 该应用的小部件树如下所示:
现在,让我们编写代码以构建主屏幕。 以下步骤讨论了该应用小部件的创建和放置:
- 首先,我们创建一个名为
image_super_resolution.dart的新文件。 这将包含一个名为ImageSuperResolution的无状态窗口小部件。 该小部件将包含应用主屏幕的代码。 - 接下来,我们将定义一个名为
buildImageInput()的函数,该函数返回一个小部件,该小部件负责显示用户选择的图像:
Widget buildImage1() {
return Expanded(
child: Container(
width: 200,
height: 200,
child: img1
)
);
}
此函数返回带有Container作为其child.的Expanded小部件。Container的width和height为200。 Container的子元素最初是存储在资产文件夹中的占位符图像,可以通过img1变量进行访问,如下所示:
var img1 = Image.asset('assets/place_holder_image.png');
我们还将在pubspec.yaml文件中添加图像的路径,如下所示:
flutter:
assets:
- assets/place_holder_image.png
- 现在,我们将创建另一个函数
buildImageOutput(),该函数返回一个小部件,该小部件负责显示模型返回的增强图像:
Widget buildImageOutput() {
return Expanded(
child: Container(
width: 200,
height: 200,
child: imageOutput
)
);
}
此函数返回一个以其Container作为其子元素的Expanded小部件。 Container的宽度和高度设置为200。 Container的子级是名为imageOutput的小部件。 最初,imageOutput还将包含一个占位符图像,如下所示:
Widget imageOutput = Image.asset('assets/place_holder_image.png');
将模型集成到应用中后,我们将更新imageOutput。
- 现在,我们将定义第三个函数
buildPickImageButton(),该函数返回一个Widget,我们可以使用它从设备的图库中选择图像:
Widget buildPickImageButton() {
return Container(
margin: EdgeInsets.all(8),
child: FloatingActionButton(
elevation: 8,
child: Icon(Icons.camera_alt),
onPressed: () => {},
)
);
}
此函数返回以FloatingActionButton作为其子元素的Container。 按钮的elevation属性控制其下方阴影的大小,并设置为8。 为了反映该按钮用于选择图像,通过Icon类为它提供了摄像机的图标。 当前,我们已经将按钮的onPressed属性设置为空白。 我们将在下一部分中定义一个函数,使用户可以在按下按钮时从设备的图库中选择图像。
- 最后,我们将覆盖
build方法以返回应用的Scaffold:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Image Super Resolution')),
body: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
buildImageInput(),
buildImageOutput(),
buildPickImageButton()
]
)
)
);
}
Scaffold包含一个appBar,其标题设置为“图像超分辨率”。 Scaffold的主体为Container,其子代为Column。 该列的子级是我们在先前步骤中构建的三个小部件。 另外,我们将Column的crossAxisAlignment属性设置为CrossAxisAlignment.center,以确保该列位于屏幕的中央。
至此,我们已经成功构建了应用的初始状态。 以下屏幕截图显示了该应用现在的外观:
尽管屏幕看起来很完美,但目前无法正常工作。 接下来,我们将向应用添加功能。 我们将添加让用户从图库中选择图像的功能。
从设备的本地存储中获取图片
在本节中,我们将添加FloatingActionButton的功能,以使用户可以从设备的图库中选择图像。 这最终将被发送到服务器,以便我们能够收到响应。
以下步骤描述了如何启动图库并让用户选择图像:
- 为了允许用户从设备的图库中选择图像,我们将使用
image_picker库。 这将启动图库并存储用户选择的图像文件。 我们将从在pubspec.yaml文件中添加依赖项开始:
image_picker: 0.4.12+1
另外,我们通过在终端上运行flutter pub get来获取库。
- 接下来,我们将库导入
image_super_resolution.dart文件中:
import 'package:image_picker/image_picker.dart';
- 现在,让我们定义
pickImage()函数,该函数使用户可以从图库中选择图像:
void pickImage() async {
File pickedImg = await ImagePicker.pickImage(source: ImageSource.gallery);
}
- 从函数内部,我们只需调用
ImagePicker.pickImage()并将source指定为ImageSource.gallery即可。 该库本身处理启动设备图库的复杂性。 用户选择的图像文件最终由该函数返回。 我们将函数返回的文件存储在File类型的pickedImg变量中。 - 接下来,我们定义
loadImage()函数,以便在屏幕上显示用户选择的图像:
void loadImage(File file) {
setState(() {
img1 = Image.file(file);
});
}
此函数将用户选择的图像文件作为输入。 在函数内部,我们将先前声明的img1变量的值设置为Image.file(file),这将返回从'file'构建的Image小部件。 回想一下,最初,img1被设置为占位符图像。 为了重新渲染屏幕并显示用户选择的图像,我们将img1的新分配放在setState()中。
- 现在,将
pickImage()添加到builtPickImageButton()内的FloatingActionButton的onPressed属性中:
Widget buildPickImageButton() {
return Container(
....
child: FloatingActionButton(
....
onPressed: () => pickImage(),
)
);
}
前面的补充内容确保单击按钮时,会启动图库,以便可以选择图像。
- 最后,我们将从
pickImage()向loadImage()添加一个调用:
void pickImage() async {
....
loadImage(pickedImg);
}
在loadImage()内部,我们传入用户选择的图像,该图像存储在pickedImage变量中,以便可以在应用的屏幕上查看该图像。
完成上述所有步骤后,该应用将如下所示:
至此,我们已经构建了应用的用户界面。 我们还添加了一些功能,使用户可以从设备的图库中选择图像并将其显示在屏幕上。
在下一部分中,我们将学习如何托管在“为超分辨率创建 TensorFlow 模型”中创建的模型作为 API,以便我们可以使用它执行超分辨率。
在 DigitalOcean 上托管 TensorFlow 模型
DigitalOcean 是一个了不起的低成本云解决方案平台,非常易于上手,并提供了应用开发人员为立即可用的应用后端提供动力所需的几乎所有功能。 该界面非常易于使用,并且 DigitalOcean 拥有一些最广泛的文档,这些文档围绕着如何在云上设置不同类型的应用服务器提供入门。
在这个项目中,我们将使用 DigitalOcean 的 Droplet 部署我们的超分辨率 API。 DigitalOcean 中的 Droplet 只是通常在共享硬件空间上运行的虚拟机。
首先,我们将在项目目录中创建flask_app.py文件,并添加服务器工作所需的代码。
创建一个 Flask 服务器脚本
在本节中,我们将处理flask_app.py文件,该文件将作为服务器在云虚拟机上运行。 让我们开始吧:
- 首先,我们将对文件进行必要的导入:
from flask import Flask, request, jsonify, send_file
import os
import time
from matplotlib.image import imsave
from model.srgan import generator
from model import resolve_single
- 现在,我们将定义
weights目录并将生成器权重加载到文件中:
weights_dir = 'weights'
weights_file = lambda filename: os.path.join(weights_dir, filename)
gan_generator = generator()
gan_generator.load_weights(weights_file('gan_generator.h5'))
- 接下来,我们将使用以下代码行实例化
Flask应用:
app = Flask(__name__)
- 现在,我们准备构建服务器将监听的路由。 首先,我们将创建
/generate路由,该路由将图像作为输入,生成其超分辨率版本,并将所生成的高分辨率图像的文件名返回给用户:
@app.route('/generate', methods=["GET", "POST"])
def generate():
global gan_generator
imgData = request.get_data()
with open("input.png", 'wb') as output:
output.write(imgData)
lr = load_image("input.png")
gan_sr = resolve_single(gan_generator, lr)
epoch_time = int(time.time())
outputfile = 'output_%s.png' % (epoch_time)
imsave(outputfile, gan_sr.numpy())
response = {'result': outputfile}
return jsonify(response)
让我们尝试了解前面的代码块中发生的情况。 /generate路由已设置为仅监听 HTTP 请求的 GET 和 POST 方法。 首先,该方法获取 API 请求中提供给它的图像,将其转换为 NumPy 数组,然后将其提供给 SRGAN 模型。 SRGAN 模型返回超分辨率图像,然后为其分配一个唯一的名称并存储在服务器上。 用户显示文件名,他们可以使用该文件名调用另一个端点来下载文件。 让我们现在构建此端点。
- 为了创建端点以便下载生成的文件,我们可以使用以下代码:
@app.route('/download/<fname>', methods=['GET'])
def download(fname):
return send_file(fname)
在这里,我们创建了一个名为/download的端点,该端点附加了文件名后,将其提取并发送回给用户。
- 最后,我们可以编写执行该脚本并设置服务器的代码:
app.run(host="0.0.0.0", port="8080")
保存此文件。 确保此时将您的存储库推送到 GitHub/GitLab 存储库。
现在,我们准备将该脚本部署到DigitalOcean Droplet。
将 Flask 脚本部署到 DigitalOcean Droplet
要将 Flask 脚本部署到 DigitalOcean Droplet,您必须创建一个 DigitalOcean 帐户并创建一个 Droplet。 请按照以下步骤操作:
- 在您喜欢的 Web 浏览器中转到 digitalocean.com 。
如果您希望在添加帐单详细信息时获得 100 美元的赠金,也可以转到这里。 我们稍后再做。
-
在 DigitalOcean 的注册表格中填写您的详细信息,然后提交表格继续进行下一步。
-
系统将要求您验证电子邮件并为 DigitalOcean 帐户添加结算方式。
-
在下一步中,系统将提示您创建第一个项目。 输入所需的详细信息并提交表单以创建您的项目:
- 创建项目后,您将被带到 DigitalOcean 仪表板。 您将能够看到创建 Droplet 的提示,如以下屏幕截图所示:
-
单击“提示”以弹出 Droplet 创建表单。 选择下表中描述的选项:
字段 说明 要使用的值 选择一张图片 Droplet 将在其上运行的操作系统。 Ubuntu 18.04(或最新可用版本) 选择一个计划 选择 Droplet 的配置。 4 GB RAM 或更高 添加块存储 Droplet 的其他持久性,可拆卸存储容量。 保留默认值 选择数据中心区域 投放 Droplet 的区域。 根据您的喜好选择任何一个 选择其他选项 选择将与您的 Droplet 一起使用的所有其他功能。 保留默认值 认证方式 选择虚拟机的认证方法。 一次性密码 完成并创建 Droplet 的一些其他设置和选项。 保留默认值 -
单击“创建 Droplet”,然后等待 DigitalOcean 设置您的 Droplet。
-
创建 Droplet 后,单击其名称以打开 Droplet 管理控制台,该控制台应如下所示:
-
现在,我们可以使用上一幅截图所示的 Droplet 控制台左侧导航菜单上的 Access 选项卡登录到 Droplet。 单击“访问”,然后启动控制台。
-
将打开一个新的浏览器窗口,显示您的 Droplet 的 VNC 视图。 系统将要求您输入 Droplet 的用户名和密码。 您必须在此处使用的用户名是
root。 可以在您已注册的电子邮件收件箱中找到该密码。 -
首次登录时,系统会要求您更改 Droplet 密码。 确保您选择一个强密码。
-
登录 Droplet 后,将在 VNC 终端上看到一些 Ubuntu 欢迎文本,如以下屏幕截图所示:
- 现在,按照本书的“附录”中的说明,执行在云 VM 上设置深度学习环境的步骤。
- 接下来,将项目存储库克隆到您的 Droplet,并使用以下命令将工作目录更改为存储库的
api文件夹:
git clone https://github.com/yourusername/yourrepo.git
cd yourrepo/api
- 使用以下命令运行服务器:
python3 flask_app.py
除了来自 TensorFlow 的一些警告消息之外,在终端输出的末尾,您还应该看到以下几行指示服务器已成功启动:
现在,如 Droplet 控制台所示,您的服务器已启动并在 Droplet 的 IP 上运行。
在下一部分中,我们将学习如何使用 Flutter 应用向服务器发出 POST 请求,并在屏幕上显示服务器的响应。
在 Flutter 上集成托管的自定义模型
在本节中,我们将向托管模型发出 POST 请求,并将其传递给用户选择的图像。 服务器将以 PNG 格式响应NetworkImage。 然后,我们将更新之前添加的图像小部件,以显示模型返回的增强图像。
让我们开始将托管模型集成到应用中:
- 首先,我们将需要两个以上的外部库来发出成功的 POST 请求。 因此,我们将以下库作为依赖项添加到
pubspec.yaml文件:
dependencies:
flutter:
http: 0.12.0+4
mime: 0.9.6+3
http依赖项包含一组类和函数,这些类和函数使使用 HTTP 资源非常方便。 mime依赖性用于处理 MIME 多部分媒体类型的流。
现在,我们需要运行flutter pub get以确保所有依赖项均已正确安装到我们的项目中。
- 接下来,我们将所有新添加的依赖项导入
image_super_resolution.dart文件:
import 'package:http/http.dart' as http;
import 'package:mime/mime.dart';
- 现在,我们需要定义
fetchResponse(),它接受所选的图像文件并向服务器创建 POST 请求:
void fetchResponse(File image) async {
final mimeTypeData =
lookupMimeType(image.path, headerBytes: [0xFF, 0xD8]).split('/');
final imageUploadRequest = http.MultipartRequest('POST', Uri.parse("http://x.x.x.x:8080/generate"));
final file = await http.MultipartFile.fromPath('image', image.path,
contentType: MediaType(mimeTypeData[0], mimeTypeData[1]));
imageUploadRequest.fields['ext'] = mimeTypeData[1];
imageUploadRequest.files.add(file);
try {
final streamedResponse = await imageUploadRequest.send();
final response = await http.Response.fromStream(streamedResponse);
final Map<String, dynamic> responseData = json.decode(response.body);
String outputFile = responseData['result'];
} catch (e) {
print(e);
return null;
}
}
在前面的方法中,我们通过使用lookupMimeType函数并使用文件的路径及其头来查找所选文件的 MIME 类型。 然后,按照托管模型的服务器的预期,初始化一个多部分请求。 我们使用 HTTP 执行此操作。 我们使用MultipartFile.fromPath并将image的值设置为作为POST参数附加的路径。 由于image_picker存在一些错误,因此我们将图片的扩展名明确传递给请求主体。 因此,它将图像扩展名与文件名(例如filenamejpeg)混合在一起,这在管理或验证文件扩展名时在服务器端造成了问题。 然后,来自服务器的响应将存储在response变量中。 响应为 JSON 格式,因此我们需要使用json.decode()对其进行解码。 该函数接收响应的主体,可以使用response.body进行访问。 我们将解码后的 JSON 存储在responseData变量中。 最后,使用responseDate['result']访问服务器的输出并将其存储在outputFile变量中。
- 接下来,我们定义
displayResponseImage()函数,该函数接受服务器在outputFile参数内返回的 PNG 文件的名称:
void displayResponseImage(String outputFile) {
print("Updating Image");
outputFile = 'http://x.x.x.x:8080/download/' + outputFile;
setState(() {
imageOutput = Image(image: NetworkImage(outputFile));
});
}
根据服务器的自定义,我们需要在文件名之前附加一个字符串以将其显示在屏幕上。 该字符串应包含服务器正在运行的端口地址,后跟'/download/<outputFile>'。 然后,我们将outputFile的最终值用作url值,将imageOutput小部件的值设置为NetworkImage。 另外,我们将其封装在[HTG5]中,以便在正确获取响应后可以刷新屏幕。
- 接下来,我们在
fetchResponse()的最后调用displayResponseImage(),并传入从托管模型收到的outputFile:
void fetchResponse(File image) async {
....
displayResponseImage(outputFile);
}
- 最后,通过传入用户最初选择的图像,将调用从
pickImage()添加到fetchResponse():
void pickImage() async {
....
fetchResponse(pickedImg);
}
在前面的步骤中,我们首先向托管模型的服务器发出 POST 请求。 然后,我们解码响应并添加代码以在屏幕上显示它。 在pickImage()末尾添加fetchResponse()可确保仅在用户选择图像后才发出 POST 请求。 另外,为了确保在成功解码来自服务器的输出之后已经尝试显示响应图像,在fetchResponse()的末尾调用displayImageResponse()。 以下屏幕快照显示了屏幕的最终预期状态:
因此,我们已经完成了应用的构建,以便可以显示模型的输出。 我们将两个图像保存在屏幕上,以便我们可以看到它们之间的差异。
可以在这个页面上访问image_super_resolution.dart文件的代码。
创建材质应用
现在,我们将添加main.dart以创建最终的 Material 应用。 我们将创建一个名为MyApp的无状态小部件,并覆盖build()方法:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ImageSuperResolution(),
);
}
}
最后,我们执行代码,如下所示:
void main() => runApp(MyApp());
至此,我们完成了一个应用的创建,该应用允许用户选择图像并修改其分辨率。
总结
在本章中,我们研究了超分辨率图像以及如何使用 SRGAN 应用它们。 我们还研究了其他类型的 GAN 以及 GAN 的总体工作方式。 然后,我们讨论了如何创建一个 Flutter 应用,该应用可以与 DigitalOcean Droplet 上托管的 API 集成在一起,以便当从图库中拾取图像时可以执行图像超分辨率。 接下来,我们介绍了如何使用 DigitalOcean Droplet,以及由于其低成本和易于使用的界面而成为托管应用后端的理想选择。
在下一章中,我们将讨论一些流行的应用,这些应用通过将深度学习集成到其功能中而获得了很大的改进。 我们还将探索手机深度学习中的一些热门研究领域,并简要讨论已在其上进行的最新工作。
十、前方的路
旅程中最重要的部分是知道结束后要去哪里。 到目前为止,在本系列项目中,我们已经介绍了一些与 Flutter 应用相关的独特且功能强大的深度学习(DL)应用,但重要的是,您必须知道在哪里可以找到更多这样的项目,灵感和知识来构建自己的出色项目。 在本章中,我们将简要介绍当今在移动应用上使用 DL 的最流行的应用,当前趋势以及将来在该领域中将会出现的情况。
在本章中,我们将介绍以下主题:
- 了解移动应用中 DL 的最新趋势
- 探索移动设备上 DL 的最新发展
- 探索移动应用中 DL 的当前研究领域
让我们开始研究 DL 移动应用世界中的一些趋势。
了解移动应用中 DL 的最新趋势
特别是 DL,随着最新技术和硬件的发展,人工智能(AI)变得越来越移动。 组织一直在使用智能算法来提供个性化的用户体验并提高应用参与度。 借助人脸检测,图像处理,文本识别,对象识别和语言翻译等技术,移动应用已不仅仅是提供静态信息的媒介。 它们能够适应用户的个人偏好和选择以及当前和过去的环境状况,以提供无缝的用户体验。
让我们看一下一些流行的应用及其部署的方法,以提供良好的用户体验,同时增加应用的参与度。
数学求解器
数学求解器应用由微软于 2020 年 1 月 16 日启动,可通过简单地单击智能手机上有问题的图片来帮助学生完成数学作业。 该应用为基本和高级数学问题提供支持,涵盖了广泛的主题,包括基本算术,二次方程,微积分和统计。 以下屏幕截图显示了该应用的工作方式:
用户可以在其智能手机上单击手写或打印问题的图片,或直接在设备上涂鸦或键入图片。 该应用利用 AI 来识别问题并准确解决。 此外,它还可以提供分步说明,并提供其他学习资料,例如与问题有关的工作表和视频教程。
Netflix
Netflix 的推荐系统是在移动应用上使用 DL 的最大成功案例之一。 Netflix 利用多种算法来了解用户的偏好,并提供了他们可能感兴趣的推荐列表。所有内容都标记有标签,这些标签提供了可以从中学习算法的初始数据集。 此外,该系统监视着超过 1 亿个用户个人资料,以分析人们观看的内容,以后可能观看的内容,以前观看的内容,一年前观看的内容,等等。 将收集的所有数据汇总在一起,以了解用户可能感兴趣的内容类型。
然后,将使用标签和用户行为收集的数据汇总在一起,并输入到复杂的 ML 算法中。 这些数据有助于解释可能最重要的因素-例如,如果用户一年前观看的电影与上周观看的系列相比应被计数两次。 该算法还可以从用户行为中学习,例如用户喜欢还是不喜欢特定的内容,或者用户在 2 个晚上观看和观看的节目。 将所有因素汇总在一起并进行仔细分析,从而得出用户可能最感兴趣的建议列表。
谷歌地图
Google Maps 已帮助通勤者前往新地方,探索新城市并监控每日流量。 在 2019 年 6 月上旬,谷歌地图发布了一项新功能,使用户可以监控印度 10 个主要城市的巴士旅行时间,以及从印度铁路局获得实时更新。 该功能位于班加罗尔,钦奈,哥印拜陀,德里,海得拉巴,勒克瑙,孟买,浦那和苏拉特,它利用 Google 的实时交通数据和公交时刻表来计算准确的出行时间和延误。 支持该功能的算法可从总线位置随时间的顺序中学习。 该数据还与通勤时公交车上的汽车速度结合在一起。 数据还用于捕获特定街道的独特属性。 研究人员还模拟了围绕某个区域弹出查询的可能性,以使该模型更加健壮和准确。
Tinder
作为结识新朋友的全球最受欢迎的应用,Tinder 部署了许多学习模型,以增加喜欢特定个人资料的人数。 智能照片功能增加了用户找到正确匹配项的可能性。 该功能随机排序特定用户的图片并将其显示给其他人。 支持该功能的算法分析了向左或向右滑动图片的频率。 它使用该知识根据图片的受欢迎程度对其重新排序。 随着越来越多的数据收集,该算法的准确率一直在不断提高。
Snapchat
Snapchat 使用的过滤器是在图片和视频的顶部添加的设计叠加层,可以跟踪面部移动。 这些过滤器是通过计算机视觉实现的。 应用使用的算法的第一步是检测图像中存在的面部。 它输出包围检测到的面部的框。 然后,它为检测到的每个脸部标记面部标志(例如眼睛,鼻子和嘴唇)。 这里的输出通常是一个包含x-坐标和y-坐标的二维点。 正确检测到面部和面部特征后,它将使用图像处理功能在整个面部上正确放置或应用过滤器。 该算法使用 Active Shape Model 进一步分析了关键的面部特征。 在通过手动标记关键面部特征的边界进行训练后,该模型将创建与屏幕上出现的面部对齐的平均面部。 该模型将创建一个网格,以正确放置过滤器并跟踪其运动。
现在,我们来看看 DL 领域的研究领域。
探索移动设备上 DL 的最新发展
随着 DL 和 AI 的复杂性与移动应用的结合,正在不断进行软件和硬件优化,以在设备上高效运行模型。 让我们看看其中的一些。
谷歌的 MobileNet
Google 的 MobileNet 于 2017 年推出。它是基于 TensorFlow 的一组移动优先计算机视觉模型,经过精心优化以在受限的移动环境中高效运行。 它充当复杂神经网络结构的准确率与移动运行时性能约束之间的桥梁。 由于这些模型具有在设备本身上本地运行的能力,因此 MobileNet 具有安全性,隐私性和灵活的可访问性的优点。 MobileNet 的两个最重要的目标是在处理计算机视觉模型时减小尺寸并降低复杂性。 MobileNet 的第一个版本提供了低延迟模型,该模型能够在受限资源下正常工作。 它们可用于分类,检测,嵌入和分段,支持各种用例。
于 2018 年发布的 MobileNetV2 是对第一个版本的重大增强。 它可以用于语义分割,对象检测和分类。 作为 TensorFlow-Slim 图像分类库的一部分启动的 MobileNetV2,可以从 Colaboratory 直接访问。 也可以在本地下载,使用 Jupyter 进行浏览,也可以从 TF-Hub 和 GitHub 访问。 添加到架构中的两个最重要的功能是层之间的线性瓶颈和瓶颈之间的快捷连接。 瓶颈对中间的输入和输出进行编码,并且内层支持从较低级别的概念转换为较高级别的描述符的功能。 传统的剩余连接和快捷方式有助于减少训练时间并提高准确率。 与第一个版本相比,MobileNetV2 更快,更准确,并且所需的操作和参数更少。 它非常有效地用于对象检测和分割以提取特征。
阿里巴巴移动神经网络
阿里巴巴移动神经网络(MNN)是开源的轻量级 DL 推理引擎。 阿里巴巴工程副总裁贾阳清说:“与 TensorFlow 和 Caffe2 等通用框架相比,它既涵盖训练又包括推理,MNN 专注于推理的加速和优化,并解决了模型部署过程中的效率问题。 因此可以在移动端更高效地实现模型背后的服务,这实际上与 TensorRT 等服务器端推理引擎中的思想相符在大型机器学习应用中,推理的计算量通常是 10 倍以上,因此,进行推理的优化尤为重要。”
MNN 的主要关注领域是深度神经网络(DNN)模型的运行和推断。 它专注于模型的优化,转换和推断。 MNN 已被成功用于阿里巴巴公司的许多移动应用中,例如 Mobile Tmall,Mobile Taobao,Fliggy,UC,Qianuu 和 Juhuasuan。 它涵盖了搜索推荐,短视频捕获,直播,资产分配,安全风险控制,交互式营销,按图像搜索产品以及许多其他实际场景。 菜鸟呼叫机柜等物联网(IoT)设备也越来越多地使用技术。 MNN 具有很高的稳定性,每天可以运行超过 1 亿次。
MNN 具有高度的通用性,并为市场上大多数流行的框架提供支持,例如 TensorFlow,Caffe 和开放式神经网络交换(ONNX)。 它与卷积神经网络(CNN)和循环神经网络(RNN)等通用神经网络兼容。 MNN 轻巧且针对移动设备进行了高度优化,并且没有依赖关系。 它可以轻松部署到移动设备和各种嵌入式设备。 它还通过便携式操作系统接口(POSIX)支持主要的 Android 和 iOS 移动操作系统以及嵌入式设备。 MNN 不受任何外部库的影响,可提供非常高的性能。 它的核心操作通过大量的手写汇编代码来实现,以充分利用高级 RISC 机器(ARM)CPU 的优势。 借助高效的图像处理模块(IPM),无需 libyuv 或 OpenCV 即可加速仿射变换和色彩空间变换,MNN 易于使用。
在积极开发和研究这些产品的同时,现在让我们看一下将来有望变得越来越重要的一些领域。
探索移动应用中 DL 的当前研究领域
活跃的研究人员社区要投入时间和精力,对于任何研究领域的健康发展至关重要。 幸运的是,DL 在移动设备上的应用引起了全球开发人员和研究人员的强烈关注,许多手机制造商(例如三星,苹果,Realme 和 Xiaomi)将 DL 直接集成到了系统用户界面中 (UI)为所有设备生成。 这极大地提高了模型的运行速度,并且通过系统更新定期提高模型的准确率。
让我们看一下该领域中一些最受欢迎的研究领域,以及它们是如何发展的。
DeepFashion
在 2019 年,DeepFashion2 数据集由葛玉英,张瑞茂等提出。 该数据集是对 DeepFashion 数据集的改进,包括来自卖方和消费者的 491,000 张图像。 数据集可识别 801,000 件服装。 数据集中的每个项目都标有比例,遮挡,放大,视点,类别,样式,边界框,密集的界标和每个像素的蒙版。
数据集在训练集中有 391,000 张图像,在验证集中有 34,000 张图像,在测试集中有 67,000 张图像。 该数据集提供了提出更好的模型的可能性,该模型能够从图像中识别时装和不同的服装。 可以轻松想象此数据集可能会导致的应用范围-包括在线商店根据消费者经常穿的衣服推荐要购买的产品,以及首选品牌和产品的预期价格范围。 仅通过识别他们所穿的服装和品牌,也有可能识别任何人可能从事的职业及其财务,宗教和地理细节。
您可以在此处阅读有关 DeepFashion2 数据集的更多信息。
自我注意生成对抗网络
我们在“第 9 章”,“构建图像超分辨率应用”中讨论了生成对抗网络(GAN)的应用,其中我们从低分辨率图像中生成高分辨率图像。 GAN 在学习模仿艺术和图案方面做得相当不错。 但是,在需要记住更长的序列的情况下,以及在序列的多个部分对于生成生成的输出很重要的情况下,它们无法很好地执行。 因此,我们期待 Ian Goodfellow 及其团队推出的自我注意力 GAN(SAGAN),它们是对图像生成任务应用注意力驱动的远程依赖建模的 GAN 系统。 该系统在 ImageNet 数据集上具有更好的性能,并有望在将来被广泛采用。
Jason Antic 的 DeOldify 项目是使用 SAGANs 完成的工作的衍生产品。 该项目旨在将色彩带入旧的图像和视频中,从而使它们似乎从来没有缺少色彩。 以下屏幕快照显示了 DeOldify 项目的示例:
Dorothea Lange(1936)的《移民母亲》。 图像取自 DeOldify GitHub 存储库。 该项目可通过这里进行测试和演示。 您可以在这个页面上了解有关 SAGAN 的更多信息。
图片动画
Facebook 是一个流行的社交媒体平台,具有用于多个平台的专用应用,一直在致力于创建工具,使您可以使用普通的相机生成 3D 图像,否则这些相机只会生成 2D 图像。 图像动画是一项类似的技术,可让我们将动画带入静态图像。 可以想象这种技术非常令人兴奋的用法,人们拍摄自拍照,然后从运动库中进行选择以对其图像进行动画处理,就好像他们自己在进行这些运动一样。
图像动画虽然还处于起步阶段,但可以成为流行和有趣的应用,考虑到采用 Deepfake 技术的类似应用已成功地成为一项业务-例如,中国的 Zao 应用。
总结
在本章中,我们讨论了一些最流行的移动应用,这些应用因其在业务产品中最前沿地使用 DL 而著称,还讨论了 DL 影响其增长的方式。 我们还讨论了移动应用 DL 领域的最新发展。 最后,我们讨论了该领域的一些令人兴奋的研究领域,以及它们将来如何发展成潜在的流行应用。 我们相信,到目前为止,您将对如何在移动应用上部署 DL 以及如何使用 Flutter 来构建可在所有流行的移动平台上运行的跨平台移动应用有一个很好的了解。
我们在本章结束时希望,您将充分利用本项目系列中介绍的思想和知识,并构建出令人敬畏的东西,从而在此技术领域带来一场革命。
十一、附录
计算机科学领域令人兴奋的是,它允许多个软件组件组合在一起并致力于构建新的东西。 在这个简短的附录中,我们介绍了在移动设备上进行深度学习之前需要设置的工具,软件和在线服务。
在本章中,我们将介绍以下主题:
- 在 Cloud VM 上设置深度学习环境
- 安装 Dart SDK
- 安装 Flutter SDK
- 配置 Firebase
- 设置 Visual Studio(VS)代码
在 Cloud VM 上设置深度学习环境
在本节中,我们将提供有关如何在 Google Cloud Platform(GCP)计算引擎虚拟机(VM)实例以执行深度学习。 您也可以轻松地将此处描述的方法扩展到其他云平台。
我们将以快速指南开始,介绍如何创建您的 GCP 帐户并为其启用结算功能。
创建 GCP 帐户并启用结算
要创建 GCP 帐户,您需要一个 Google 帐户。 如果您有一个以@gmail.com结尾的电子邮件地址或 G Suite 上的帐户,则您已经有一个 Google 帐户。 否则,您可以通过访问这里创建一个 Google 帐户。 登录到 Google 帐户后,请执行以下步骤:
- 在浏览器上访问这里。
- 接受在弹出窗口中显示给您的所有条款。
- 您将能够查看 GCP 控制台信息中心。 您可以通过阅读这个页面上的支持文档来快速使用此仪表板。
- 在左侧导航菜单上,单击“计费”以打开计费管理仪表板。 系统将提示您添加一个计费帐户,如以下屏幕截图所示:
- 点击“添加结算帐户”。 如果有资格,您将被重定向到
GCP Free Trial注册页面。 您可以在这个页面上了解有关免费试用的更多信息。 您应该看到类似于以下屏幕截图的屏幕:
- 根据需要填写表格。 创建完帐单后,请返回 GCP 控制台信息中心。
您已成功创建 GCP 帐户并为其启用了结算功能。 接下来,您将能够在 GCP 控制台中创建一个项目并将资源分配给该项目。 我们将在接下来的部分中对此进行演示。
创建一个项目和 GCP Compute Engine 实例
在本部分中,您将在 GCP 帐户上创建一个项目。 GCP 中的所有资源都封装在项目下。 项目可能属于或不属于组织。 一个组织下可以有多个项目,而一个项目中可能有多个资源。 让我们开始创建项目,如以下步骤所示:
-
在屏幕的左上方,单击“选择项目”下拉菜单。
-
在出现的对话框中,单击对话框右上方的“新建项目”。
-
您将看到新的项目创建表单,如以下屏幕截图所示:
- 填写必要的详细信息后,单击
CREATE完成创建项目。 创建项目后,将带您到项目的仪表板。 在这里,您将能够查看与当前所选项目相关的一些基本日志记录和监视。 您可以在这个页面上了解有关 GCP 资源组织方式的更多信息。 - 在左侧导航窗格中,单击
Compute Engine。 系统将提示您创建一个 VM 实例。 - 点击“创建”以显示 Compute Engine 实例创建表单。 根据需要填写表格。 我们假设您在创建实例时选择了 Ubuntu 18.04 LTS 发行版。
- 确保在防火墙设置中启用对 VM 实例的 HTTP 和 HTTPS 连接的访问,如以下屏幕快照所示:
- 单击“创建”。 GCP 开始为您配置 VM 实例。 您将被带到 VM 实例管理页面。 您应该在此页面上看到您的 VM,如以下屏幕截图所示:
现在,您准备开始配置此 VM 实例以执行深度学习。 我们将在下一部分中对此进行介绍。
配置您的 VM 实例来执行深度学习
在本节中,我们将指导您如何安装包和模块,以在创建的 VM 实例上执行深度学习。 这些包和模块的安装说明在您选择的任何云服务提供商中都是相似的。
您还可以在本地系统上使用类似的命令,以设置本地深度学习环境。
首先调用 VM 的终端:
-
单击 VM 实例页面上的
SSH按钮,以启动到 VM 的终端会话。 -
您应该看到终端会话开始,其中包含一些与系统有关的常规信息以及上次登录的详细信息,如以下屏幕截图所示:
- 现在,让我们对该新创建的实例的包存储库执行更新:
sudo apt update
- 接下来,我们将在此 VM 上安装 Anaconda。 Anaconda 是一个受欢迎的包集合,用于使用 Python 执行深度学习和与数据科学相关的任务。 它带有
conda包管理器打包在一起,这使得管理系统上安装的 Python 包的不同版本非常容易。 要安装它,我们首先需要获取 Anaconda 安装程序下载链接。 前往这里。 您将转到一个页面,为您提供要安装的 Anaconda 版本的选择,如以下屏幕截图所示:
- 建议您选择 Python 3.7 版本。 右键单击“下载”按钮,然后在菜单中找到允许您复制链接地址的选项。
- 切换到您的 VM 实例的终端会话。 使用以下命令将占位符文本粘贴到命令中,从而将其替换为您复制的链接,如下所示:
curl -O <link_you_have_copied>
- 前面的命令会将 Anaconda 安装程序下载到当前用户的主目录中。 要对其进行验证,可以使用
ls命令。 现在,要将此文件设置为可执行文件,我们将使用以下命令:
chmod +x Anaconda*.sh
- 现在,安装程序文件可以由您的系统执行。 要开始执行,请使用以下命令:
./Anaconda*.sh
- 安装应开始。 应该显示一个提示,询问您是否接受 Anaconda 软件的许可协议,如下所示:
- 点击
Enter继续检查许可证。 您会看到许可证文件。 - 点击向下箭头键以阅读协议。 输入
yes接受许可证。 - 系统将要求您确认 Anaconda 安装的位置,如以下屏幕截图所示:
- 点击
Enter确认位置。 包提取和安装将开始。 完成此操作后,系统将询问您是否要初始化 Anaconda 环境。 在此处输入yes,如下所示:
- 现在,安装程序将完成其任务并退出。 要激活 Anaconda 环境,请使用以下命令:
source ~/.bashrc
- 您已经成功安装了 Anaconda 环境并激活了它。 要检查安装是否成功,请在终端中输入以下命令:
python3
如果以下命令的输出在第二行包含单词 Anaconda,Inc.,则表明安装成功。 您可以在以下屏幕截图中看到它:
现在,您可以在此环境上开始运行深度学习脚本。 但是,您将来可能希望向此环境添加更多工具库,例如 PyTorch 或 TensorFlow 或任何其他包。 由于本书假定读者熟悉 Python,因此我们不会详细讨论pip工具。
现在让我们看一下如何在 VM 上安装 TensorFlow。
在 VM 上安装 TensorFlow
TensorFlow 是执行深度学习的绝佳框架。
要安装它,可以使用以下命令:
# TensorFlow 1 with CPU only support
python3 -m pip install tensorflow==1.15
# TensorFlow 1 with GPU support
python3 -m pip install tensorflow-gpu==1.15
# TensorFlow 2 with CPU only support
python3 -m pip install tensorflow
# Tensorflow 2 with GPU support
python3 -m pip install tensorflow-gpu
Python 中另一个经常安装的流行库是自然语言工具包(NLTK)库。 我们将在接下来的部分中演示其安装过程。
在 VM 上安装 NLTK 并下载包
要在 VM 上安装 NLTK 并为其下载数据包,请执行以下步骤:
- 使用
pip安装 NLTK:
python3 -m pip install nltk
- NLTK 有几种不同的数据包。 在大多数情况下,您并不需要全部。 要列出 NLTK 的所有可用数据包,请使用以下命令:
python3 -m nltk.downloader
前面命令的输出将允许您交互式地查看所有可用的包,选择所需的包,然后下载它们。
- 但是,如果您只希望下载一个包,请使用以下命令:
python3 -m nltk.downloader stopwords
前面的命令将下载 NLTK 的stopwords数据包。 在极少数情况下,您可能会发现自己需要或使用 NLTK 中可用的所有数据包。
通过这种设置,您应该能够在云 VM 上运行大多数深度学习脚本。
在下一部分中,我们将研究如何在本地系统上安装 Dart。
安装 Dart SDK
Dart 是 Google 开发的一种面向对象的语言。 它用于移动和 Web 应用开发。 Flutter 是用 Dart 构建的。 Dart 具有即时(JIT)开发周期,该状态与有状态的热重载兼容,并且具有提前编译的功能,可以快速启动并提供可预测的性能,这使其成为了可能。 适用于 Flutter。
以下各节讨论如何在 Windows,macOS 和 Linux 上安装 Dart。
Windows
在 Windows 中安装 Dart 的最简单方法是使用 Chocolatey。 只需在终端中运行以下命令:
C:\> choco install dart-sdk
接下来,我们将研究如何在 Mac 系统上安装 Dart。
MacOS
要在 macOS 上安装 Dart,请执行以下步骤:
- 通过在终端中运行以下命令来安装 Homebrew:
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- 运行以下命令以安装 Dart:
$brew tap dart-lang/dart
$brew install dart
接下来,我们将研究如何在 Linux 系统上安装 Dart。
Linux
Dart SDK 可以如下安装在 Linux 中:
- 执行以下一次性设置:
$sudo apt-get update
$sudo apt-get install apt-transport-https
$sudo sh -c 'wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -'
$sudo sh -c 'wget -qO- https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list'
- 安装稳定版本:
$sudo apt-get update
$sudo apt-get install dart
接下来,我们将研究如何在本地计算机上安装 Flutter SDK。
安装 Flutter SDK
Flutter 是 Google 的一个工具包,用于使用单个代码库构建本地编译的 Android,iOS 和 Web 应用。 Flutter 具有热重载的快速开发,易于构建的表达性 UI 和本机性能等功能,这些都使 Flutter 成为应用开发人员的首选。
以下各节讨论如何在 Windows,macOS 和 Linux 上安装 Flutter SDK。
Windows
以下步骤详细概述了如何在 Windows 上安装 Flutter:
- 从这里下载最新的 Flutter SDK 稳定版本。
- 解压缩 ZIP 文件夹,并导航到要安装 Flutter SDK 的目录,以放置
flutter文件夹。
避免将flutter放在可能需要特殊特权的目录中,例如C:\Program Files\。
- 在“开始”搜索栏中输入
env,然后选择“编辑环境变量”。 - 使用
;作为分隔符,将flutter/bin的完整路径附加到用户变量下的路径。
如果缺少Path条目,只需创建一个新的Path变量并将path设置为flutter/bin作为其值。
- 在终端中运行
flutter doctor。
flutter doctor分析整个 Flutter 的安装,以检查是否需要更多工具才能在计算机上成功运行 Flutter。
接下来,我们将研究如何在 Mac 系统上安装 Flutter。
MacOS
Flutter 可以如下安装在 macOS 上:
- 从这里下载最新的稳定 SDK。
- 将下载的 ZIP 文件夹解压缩到合适的位置,如下所示:
$cd ~/
$unzip ~/Downloads/flutter_macos_v1.9.1+hotfix.6-stable.zip
- 将
flutter工具添加到路径变量:$ export PATH=pwd/flutter/bin:$PATH。 - 打开
bash_profile以永久更新PATH:
$cd ~
$nano .bash_profile
- 将以下行添加到
bash_profile:
$export PATH=$HOME/flutter/bin:$PATH
- 运行
flutter doctor。
Linux
以下步骤概述了如何在 Linux 上安装 Flutter:
- 从这里下载 SDK 的最新稳定版本。
- 将文件提取到合适的位置:
$cd ~/development
$tar xf ~/Downloads/flutter_linux_v1.9.1+hotfix.6-stable.tar.xz
- 将
flutter添加到path变量中:
$export PATH="$PATH:`pwd`/flutter/bin"
- 运行
flutter doctor。
接下来,我们将研究如何配置 Firebase 以提供 ML Kit 和自定义模型。
配置 Firebase
Firebase 提供了可促进应用开发并帮助支持大量用户的工具。 Firebase 可以轻松用于 Android,iOS 和 Web 应用。 Firebase 提供的产品(例如 Cloud Firestore,ML Kit,Cloud Functions,Authentication,Crashlytics,Performance Monitoring,Cloud Messaging 和 Dynamic Links)有助于构建应用,从而在不断发展的业务中提高应用质量。
要集成 Firebase 项目,您需要创建一个 Firebase 项目并将其集成到您的 Android 或 iOS 应用中。 以下各节讨论如何创建 Firebase 项目并将其集成到 Android 和 iOS 项目中。
创建 Firebase 项目
首先,我们需要创建一个 Firebase 项目并将其链接到我们的 Android 和 iOS 项目。 此链接有助于我们利用 Firebase 提供的功能。
要创建 Firebase 项目,请执行以下步骤:
- 通过这里访问 Firebase 控制台。
- 单击“添加项目”以添加新的 Firebase 项目:
- 为您的项目提供一个名称:
- 根据您的要求启用/禁用 Google Analytics(分析)。 通常建议您保持启用状态。
Google Analytics 是一种免费且不受限制的分析解决方案,可在 Firebase Crashlytics,Cloud Messaging,应用内消息传递,远程配置,A/B 测试,预测和 Cloud Functions 中实现目标定位,报告等功能。
- 如果您选择 Firebase Analytics,则还需要选择一个帐户:
在 Firebase 控制台上创建项目后,您将需要分别为 Android 和 iOS 平台进行配置。
配置 Android 项目
以下步骤讨论了如何配置 Android 项目以支持 Firebase:
- 导航到 Firebase 控制台上的应用。 在项目概述页面的中心,单击 Android 图标以启动工作流程设置:
- 添加包名称以在 Firebase 控制台上注册该应用。 此处填写的包名称应与您的应用的包名称匹配。 此处提供的包名称用作标识的唯一密钥:
此外,您可以提供昵称和调试签名证书 SHA-1。
- 下载
google-services.json文件并将其放在app文件夹中:
google-services.json文件存储开发人员凭据和配置设置,并充当 Firebase 项目和 Android 项目之间的桥梁。
- 用于 Gradle 的 Google 服务插件会加载您刚刚下载的
google-services.json文件。 项目级别的build.gradle(<project>/build.gradle)应该进行如下修改,以使用该插件:
buildscript {
repositories {
// Check that you have the following line (if not, add it):
google() // Google's Maven repository
}
dependencies {
...
// Add this line
classpath 'com.google.gms:google-services:4.3.3'
}
}
allprojects {
...
repositories {
// Check that you have the following line (if not, add it):
google() // Google's Maven repository
...
}
}
- 这是应用级别的
build.gradle(<project>/<app-module>build.gradle):
apply plugin: 'com.android.application'
// Add this line
apply plugin: 'com.google.gms.google-services'
dependencies {
// add SDKs for desired Firebase products
// https://firebase.google.com/docs/android/setup#available-libraries
}
现在,您都可以在 Android 项目中使用 Firebase。
配置 iOS 项目
以下步骤演示了如何配置 iOS 项目以支持 Firebase:
- 导航到 Firebase 控制台上的应用。 在项目概述页面的中心,单击 iOS 图标以启动工作流程设置:
- 添加 iOS 捆绑包 ID 名称,以在 Firebase 控制台上注册该应用。 您可以在“常规”选项卡中的捆绑包标识符中找到应用主要目标的 Xcode。 它用作标识的唯一密钥:
此外,您可以提供昵称和 App Store ID。
- 下载
GoogleService-Info.plist文件:
- 将刚刚下载的
GoogleService-Info.plist文件移到 Xcode 项目的根目录中,并将其添加到所有目标中。
Google 服务使用 CocoaPods 来安装和管理依赖项。
- 打开一个终端窗口,然后导航到您的应用的 Xcode 项目的位置。 如果没有,请在此文件夹中创建一个 Podfile:
pod init
- 打开您的 Podfile 并添加以下内容:
# add pods for desired Firebase products # https://firebase.google.com/docs/ios/setup#available-pods
- 保存文件并运行:
pod install
这将为您的应用创建一个.xcworkspace文件。 使用此文件进行应用的所有将来开发。
- 要在应用启动时连接到 Firebase,请将以下初始化代码添加到主
AppDelegate类中:
import UIKit
import Firebase
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
return true
}
}
现在,您都可以在 Android 项目中使用 Firebase。
设置 VS 代码
Visual Studio(VS)Code 是由 Microsoft 开发的轻型代码编辑器。 它的简单性和广泛的插件存储库使其成为开发人员的便捷工具。 凭借其 Dart 和 Flutter 插件,以及应用执行和调试支持,Flutter 应用非常易于开发。
在接下来的部分中,我们将演示如何设置 VS Code 以开发 Flutter 应用。 我们将从这里下载最新版本的 VS Code 开始。
安装 Flutter 和 Dart 插件
首先,我们需要在 VS Code 上安装 Flutter 和 Dart 插件。
可以按照以下步骤进行:
- 在计算机上加载 VS Code。
- 导航到“查看 | 命令面板”。
- 开始输入
install,然后选择扩展:安装扩展。 - 在扩展搜索字段中键入
flutter,从列表中选择 Flutter,然后单击安装。 这还将安装所需的 Dart 插件。 - 或者,您可以导航到侧栏来安装和搜索扩展:
成功安装 Flutter 和 Dart 扩展后,我们需要验证设置。 下一节将对此进行描述。
用 Flutter Doctor 验证设置
通常建议您验证设置以确保一切正常。
Flutter 安装可以通过以下方式验证:
- 导航到“查看 | 命令面板”。
- 输入
doctor,然后选择Flutter: Run Flutter Doctor。 - 查看“输出”窗格中的输出。 输出中列出了所有错误或缺少库。
- 另外,您可以在终端上运行
flutter doctor来检查一切是否正常:
上面的屏幕快照显示,尽管 Flutter 很好用,但其他一些相关的配置却丢失了。 在这种情况下,您可能需要安装所有支持软件并重新运行flutter doctor以分析设置。
在 VS Code 上成功设置 Flutter 之后,我们可以继续创建我们的第一个 Flutter 应用。
创建第一个 Flutter 应用
创建第一个 Flutter 应用非常简单。 执行以下步骤:
-
导航到“查看 | 命令面板”。
-
开始输入
flutter,然后选择Flutter: New Project。 -
输入项目名称,例如
my_sample_app。 -
点击
Enter。 -
创建或选择新项目文件夹的父目录。
-
等待项目创建完成,然后显示
main.dart文件。
有关更多详细信息,请参阅这个页面上的文档。
在下一节中,我们将讨论如何运行您的第一个 Flutter 应用。
运行应用
一个新的 Flutter 项目的创建带有一个模板代码,我们可以直接在移动设备上运行它。 创建第一个模板应用后,可以尝试如下运行它:
- 导航至“VS Code”状态栏(即窗口底部的蓝色栏):
- 从设备选择器区域中选择您喜欢的设备:
- 如果没有可用的设备,并且要使用设备模拟器,请单击“无设备”并启动模拟器:
- 您也可以尝试设置用于调试的真实设备。
- 单击设置按钮-位于右上角的齿轮图标齿轮(现已标记为红色或橙色指示器),位于
DEBUG文本框旁边,显示为No Configuration。 选择 Flutter,然后选择调试配置以创建仿真器(如果已关闭)或运行仿真器或已连接的设备。 - 导航到“调试 | 开始调试”或按
F5。 - 等待应用启动,进度会显示在
DEBUG CONSOLE视图中:
应用构建完成后,您应该在设备上看到已初始化的应用:
在下一节中,我们将介绍 Flutter 的热重载功能,该功能有助于快速开发。
尝试热重载
Flutter 提供的快速开发周期使其适合于时间优化的开发。 它支持有状态热重载,这意味着您可以重载正在运行的应用的代码,而不必重新启动或丢失应用状态。 热重装可以描述为一种方法,您可以通过该方法对应用源进行更改,告诉命令行工具您要热重装,并在几秒钟内在设备或仿真器上查看更改。
在 VS Code 中,可以按以下方式执行热重装:
-
打开
lib/main.dart。 -
将
You have pushed the button this many times:字符串更改为You have clicked the button this many times:。 不要停止您的应用。 让您的应用运行。 -
保存更改:调用全部保存,或单击
Hot Reload。