diff --git a/docs/ai-py/00.md b/docs/ai-py/00.md new file mode 100644 index 0000000000000000000000000000000000000000..a5a07c2a8b0ef28a88c9b889415ec82cb3b5eefb --- /dev/null +++ b/docs/ai-py/00.md @@ -0,0 +1,155 @@ +# 前言 + +**人工智能**( **AI** )的最新发展已将强大的力量交在人类手中。 权力越大,责任就成比例。 无人驾驶汽车,聊天机器人和对未来的日益准确的预测只是 AI 能够超越人类增长和进步能力的几个例子。 + +人工智能正在成为一条核心的,变革性的道路,正在改变我们思考生活各个方面的方式。 它正在影响行业。 它正在变得越来越普遍,并嵌入到我们的日常生活中。 最令人兴奋的是,这是一个仍处于起步阶段的领域:AI 革命才刚刚开始。 + +随着我们收集越来越多的数据并使用越来越快的算法来处理这些数据,我们可以使用 AI 来构建越来越准确的模型,并回答越来越复杂,以前难以解决的问题。 + +因此,与 AI 一起工作和充分利用其能力只会增加其价值就不足为奇了。 在本书中,我们探索了各种现实情况,并学习了如何将相关的 AI 算法应用于各种问题。 + +本书从最基本的 AI 概念开始,并逐步以这些概念为基础来解决日益棘手的问题。 它将使用在开始的章节中收集的初始知识作为基础,以使读者能够探索和解决 AI 中一些更复杂的问题。 到本书结尾,读者将对许多 AI 技术有深入的了解,并对何时使用这些技术有信心。 + +我们将从谈论 AI 的各个领域开始。 然后,我们将继续讨论更复杂的算法,例如极其随机的森林,隐马尔可夫模型,遗传算法,人工神经网络,卷积神经网络等等。 + +本书适用于希望使用 AI 算法创建实际应用程序的 Python 程序员。 这本书对 Python 初学者很友好,但是熟悉 Python 编程肯定会有所帮助,因此您可以试用这些代码。 对于希望实施人工智能技术的经验丰富的 Python 程序员而言,它也很有用。 + +您将学习如何对需要使用的算法类型做出明智的决策,以及如何实现这些算法以获得最佳结果。 如果您想构建可以理解图像,文本,语音或其他某种形式的数据的通用应用程序,那么这本有关人工智能的书肯定会助您一臂之力! + +# 这本书适合谁 + +本书适用于希望构建现实人工智能应用程序的 Python 开发人员。 这本书对 Python 初学者很友好,但是熟悉 Python 对于玩弄代码很有帮助。 对于希望在现有技术堆栈中使用人工智能技术的经验丰富的 Python 程序员而言,它也将很有用。 + +# 这本书涵盖的内容 + +*第 1 章*和*人工智能简介* + +本章提供了将在整本书中使用的一些基本定义和分组。 它还将提供当今人工智能和机器学习领域的总体分类。 + +*第 2 章*和*人工智能的基本用例* + +人工智能是一个引人入胜的话题,知识领域广泛。 在当前状态下,它产生的问题多于答案,但是肯定有很多地方在应用人工智能,甚至在许多情况下,我们甚至都没有意识到。 在深入研究驱动 AI 的基本算法之前,我们将分析迄今为止该技术的一些最受欢迎的用例。 + +*第 3 章*和*机器学习管道* + +模型训练只是机器学习过程中的一小部分。 数据科学家经常花费大量时间来清理,转换和准备数据,以备 AI 模型使用。 由于数据准备是一项非常耗时的活动,因此,我们将介绍最先进的技术来促进此活动以及精心设计的生产数据管道应具有的其他组件。 + +*第 4 章*和*特征选择和特征工程* + +通过选择正确的尺寸传递给模型以及发现可以丰富输入数据集的新尺寸,可以提高模型性能。 本章将说明如何从现有功能以及外部资源创建新功能。 它还将介绍如何消除冗余或低价值的功能。 + +*第 5 章*和*使用监督学习的分类和回归* + +本章详细定义了监督学习。 它提供了属于该分类的各种方法和算法的分类法。 + +*第 6 章*和*集成学习的预测分析* + +集成学习是一项强大的技术,可让您汇总各个模型的功能。 本章介绍了不同的合奏方法,以及何时使用它们的指南。 最后,本章将介绍如何将这些技术应用于实际事件预测。 + +*第 7 章*和*在无监督学习的情况下检测模式* + +本章将探讨聚类和数据分割的概念,以及它们与无监督学习的关系。 它还将介绍如何执行聚类以及如何应用各种聚类算法。 它将显示几个示例,使读者可以直观地看到这些算法的工作方式。 最后,它将介绍这些算法在实际情况下执行聚类和分段的应用。 + +*第 8 章*和*构建推荐系统* + +本章将演示如何构建推荐系统。 它还将显示如何保留用户首选项。 它将涵盖最近邻居搜索和协作过滤的概念。 最后,将有一个示例显示如何构建电影推荐系统。 + +*第 9 章*和*逻辑编程* + +本章将介绍如何使用逻辑编程编写程序。 它将讨论各种编程范例,并了解如何使用逻辑编程构造程序。 它将重点介绍逻辑编程的构建块,并介绍如何解决该领域的问题。 最后,将为解决各种问题的各种求解器构建各种 Python 程序实现。 + +*第 10 章*和*启发式搜索技术* + +本章介绍启发式搜索技术。 启发式搜索技术用于搜索解决方案空间以得出答案。 使用指导搜索算法的试探法进行搜索。 启发式算法允许算法加快处理过程,否则将需要很长时间才能得出解决方案。 + +*第 11 章*和*遗传算法和遗传编程* + +我们将讨论基因编程的基础及其在 AI 领域中的重要性。 我们将学习如何使用遗传算法解决简单的问题。 我们将了解一些用于基因编程的基本概念。 然后,我们将看到如何将此应用于实际问题。 + +*第 12 章*和*云上的人工智能* + +云使我们能够加速 AI 开发,工作负载和部署。 在本章中,我们将探讨支持和加速 AI 项目的最受欢迎供应商提供的不同产品。 + +*第 13 章*和*使用人工智能构建游戏* + +本章将介绍如何使用人工智能技术构建游戏。 搜索算法将用于制定制胜游戏的策略和战术。 最后,将为各种游戏构建智能机器人。 + +*第 14 章*和*构建语音识别器* + +本章将介绍如何执行语音识别。 它将显示如何处理语音数据并从中提取特征。 最后,它将演示如何使用提取的功能来构建语音识别系统。 + +*第 15 章*,*自然语言处理* + +本章将重点介绍 AI 的重要领域,即**自然语言处理**( **NLP** )。 它将讨论各种概念,例如标记化,词干化和词形化处理文本。 它还将介绍如何构建“语言袋”模型并将其用于分类文本。 它将演示如何使用机器学习来分析给定句子的情感。 最后,它将显示主题建模并介绍用于识别文档中主题的系统实现。 + +*第 16 章*和*聊天机器人* + +聊天机器人可以通过提高生产力和转移呼叫帮助节省资金并更好地为客户服务。 在本章中,我们将介绍聊天机器人的基础知识以及可用于构建聊天机器人的工具。 + +最后,我们将从头开始构建一个成熟的聊天机器人,它将实现一个实际的用例,包括错误处理,将其连接到外部 API 以及部署该聊天机器人。 + +*第 17 章*和*顺序数据和时间序列分析* + +我们将讨论概率推理的概念。 我们将学习如何应用该概念为顺序数据建立模型。 我们将学习时间序列数据的各种特征。 我们将讨论隐马尔可夫模型以及如何使用它们分析顺序数据。 然后,我们将使用此技术来分析股票市场数据。 + +*第 18 章*,*图像识别* + +在本章中,我们将讨论如何处理图像。 我们将学习如何检测和跟踪实时视频中的对象。 然后,我们将学习如何应用这些技术来跟踪人脸的各个部分。 + +*第 19 章*和*神经网络* + +我们将讨论人工神经网络。 我们将学习感知器,并了解它们如何用于构建神经网络。 我们将学习如何构建单层和多层神经网络。 我们将讨论神经网络如何学习训练数据并建立模型。 我们将学习成本函数和反向传播。 然后,我们将使用这些技术来执行光学字符识别。 + +*第 20 章*和*带卷积神经网络的深度学习* + +我们将在本章中讨论深度学习的基础知识。 将向读者介绍卷积神经网络中的各种概念,以及如何将它们用于图像识别。 我们将讨论卷积神经网络中的各个层。 然后,我们将使用这些技术来构建实际应用程序。 + +*第 21 章*,*递归神经网络和其他深度学习模型* + +本章将继续涵盖其他类型的深度学习算法。 首先将介绍递归神经网络,然后将介绍较新的算法,例如注意力,自我注意和变压器模型。 本章将介绍使用这些网络的用例以及使用这些模型体系结构的优势及其局限性。 最后,讨论的技术将用于构建实际应用程序。 + +*第 22 章*和*通过强化学习创建智能代理* + +本章将定义**强化学习**( **RL** ),并涵盖 RL 模型中的组件。 它将详细介绍用于构建 RL 系统的技术。 最后,它将演示如何构建可以通过与环境交互进行学习的学习代理。 + +*第 23 章*和*人工智能和大数据* + +本章将分析如何将大数据技术应用于加速机器学习流程,以及涵盖可用于简化数据集提取,转换和验证的各种技术。 最后,它将引导读者阅读使用 Apache Spark 的实际示例,以演示本章介绍的概念。 + +# 这本书需要什么 + +本书侧重于 Python 中的 AI,而不是 Python 本身。 我们已经使用 Python 3 来构建各种应用程序。 我们专注于如何以最佳方式利用各种 Python 库来构建实际应用程序。 本着这种精神,我们试图使所有代码尽可能友好和可读。 我们认为,这将使我们的读者能够轻松理解代码并在不同的场景中轻松使用它。 + +## 使用的约定 + +在本书中,您将找到许多可以区分不同类型信息的文本样式。 以下是这些样式的一些示例,并对其含义进行了解释。 + +`CodeInText`:指示文本,数据库表名称,文件夹名称,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄中的代码字。 例如:“ `n_estimators`参数是指将要构造的树的数量。” + +代码块设置如下: + +```py +# Create label encoder and fit the labels +encoder = preprocessing.LabelEncoder() +encoder.fit(input_labels) +``` + +当我们希望引起您对代码块特定部分的注意时,相关的行或项目以粗体显示: + +```py +# Create label encoder and fit the labels +encoder = preprocessing.LabelEncoder() +encoder.fit(input_labels) +``` + +任何命令行输入或输出的编写方式如下: + +```py +$ python3 random_forests.py --classifier-type rf +``` + +**粗体**:表示新术语,重要单词或您在屏幕上看到的单词,例如,在菜单或对话框中,也显示在这样的文本中。 例如:“ **监督学习**是指基于标记的训练数据构建机器学习模型的过程。” + +警告或重要提示会出现在这样的框中。 + +提示和技巧如下所示。 diff --git a/docs/ai-py/01.md b/docs/ai-py/01.md new file mode 100644 index 0000000000000000000000000000000000000000..2f6efeb9d5391bb5de488b5e97644bf479f8da64 --- /dev/null +++ b/docs/ai-py/01.md @@ -0,0 +1,403 @@ +# 1 + +# 人工智能简介 + +在本章中,我们将讨论**人工智能**( **AI** )的概念及其在现实世界中的应用。 我们在日常生活中花费了大量时间与智能系统进行交互。 这可以采取以下形式:在互联网上搜索某些内容,进行生物特征识别的面部识别或将口语单词转换为文本。 人工智能是这一切的核心,它正在成为我们现代生活方式的重要组成部分。 所有这些系统都是复杂的实际应用程序,而 AI 通过数学和算法解决了这些问题。 在整本书中,我们将学习可用于构建此类应用程序的基本原理。 我们的总体目标是使您能够应对日常生活中可能遇到的具有挑战性的新 AI 问题。 + +到本章末,您将了解: + +* 什么是人工智能,为什么我们需要学习它? +* 人工智能有哪些应用? +* 人工智能分支的分类 +* 机器学习的五个部落 +* 什么是图灵测试? +* 什么是理性代理? +* 什么是一般问题解决器? +* 如何建立智能代理 +* 如何安装 Python 3 和相关软件包 + +# 什么是 AI? + +如何定义 AI 可能会有很大差异。 从哲学上讲,什么是“智能”? 一个人如何感知智能又定义了它的人为对应物。 对 AI 领域的广泛而乐观的定义可能是:“计算机科学领域,它研究机器如何执行通常需要有知觉的代理的任务。” 从这样的定义可以说,像计算机乘以两个数字这样简单的东西就是“人工智能”。 这是因为我们设计了一种能够接受输入并独立产生逻辑输出的机器,而该逻辑输出通常需要有生命的实体进行处理。 + +更具怀疑性的定义可能更狭窄,例如:“研究机器如何紧密模仿人类智能的计算机科学领域”。 根据这样的定义,怀疑论者可能会认为我们今天所拥有的不是人工智能。 到目前为止,他们已经能够指出计算机无法执行的任务的示例,因此声称如果计算机不能令人满意地执行这些功能,它们将无法“思考”或展现人工智能。 + +本书倾向于更乐观的 AI 观,我们更喜欢惊叹于计算机当前可以执行的任务数量。 + +在我们前面提到的乘法任务中,如果两个数字足够大,那么计算机肯定会比人类更快,更准确。 目前在其他领域,人类可以比计算机表现更好。 例如,人类可以通过几个示例来识别,标记和分类对象,而当前计算机可能需要数千个示例才能以相同的准确度执行。 研究和改进工作一直在不懈地进行,我们将继续看到计算机能够解决越来越多的问题,而几年前,我们只能梦想着解决它们。 随着本书的发展,我们将探索许多这些用例并提供大量示例。 + +考虑人工智能领域的一种有趣方式是,从某种意义上讲,人工智能是科学的又一分支,正在研究我们所知道的最迷人的计算机:大脑。 借助 AI,我们试图在计算中反映大脑的某些系统和机制,从而发现自己从诸如神经科学的领域中借鉴并与之互动。 + +# 为什么我们需要学习 AI? + +人工智能可以影响我们生活的各个方面。 AI 领域试图了解实体的模式和行为。 借助 AI,我们希望构建智能系统并理解智能的概念。 我们构建的智能系统对于理解像我们的大脑这样的智能系统如何构建另一个智能系统非常有用。 + +让我们看一下我们的大脑如何处理信息: + +![](img/B15441_01_01.png) + +图 1:基本的大脑成分 + +与已经存在了数百年的数学或物理学等其他领域相比,人工智能还处于起步阶段。 在过去的几十年中,人工智能生产了一些引人注目的产品,例如自动驾驶汽车和可以行走的智能机器人。 根据我们前进的方向,很明显,获得智能将在未来几年对我们的生活产生重大影响。 + +我们不禁要问,人类的大脑如何如此轻松地做到这一点。 我们可以用大脑识别物体,理解语言,学习新事物并执行许多更复杂的任务。 人脑如何做到这一点? 对于这个问题,我们还没有很多答案。 当您尝试使用机器复制大脑执行的任务时,您会发现它落后了! 在许多方面,我们自己的大脑比机器复杂得多,能力也更强。 + +当我们尝试寻找诸如外星生命或时空旅行之类的事物时,我们不知道这些事物是否存在; 我们不确定这些追求是否值得。 关于 AI 的好处是它已经存在理想化的模型:我们的大脑是智能系统的圣杯! 我们要做的就是模仿它的功能,以创建一个智能系统,该系统可以执行与我们的大脑相似或更好的事情。 + +让我们看看如何通过各种处理级别将原始数据转换为智能: + +![](img/B15441_01_02.png) + +图 2:将数据转换为智能 + +我们想学习 AI 的主要原因之一是使许多事情自动化。 我们生活在一个世界: + +* 我们处理海量且无法克服的数据。 人脑无法跟踪这么多数据。 +* 数据同时来自多个来源。 数据是杂乱无章的。 +* 从这些数据中获得的知识必须不断更新,因为数据本身一直在变化。 +* 传感和驱动必须实时,高精度地进行。 + +即使人脑擅长分析我们周围的事物,也无法跟上上述条件。 因此,我们需要设计和开发可以做到这一点的智能机器。 我们需要可以实现以下目的的 AI 系统: + +* 以有效的方式处理大量数据。 随着云计算的出现,我们现在能够存储大量数据。 +* 同时从多个源摄取数据,没有任何滞后。 对数据进行索引和组织,以使我们能够获得见解。 +* 从新数据中学习,并使用正确的学习算法不断进行更新。 根据情况实时思考和应对情况。 +* 继续执行任务,而不会感到疲劳或需要休息。 + +人工智能技术正在积极地用于使现有机器更智能,以便它们可以更快,更高效地执行 。 + +# AI 的分支 + +了解 AI 的各个研究领域非常重要,这样我们才能选择正确的框架来解决给定的现实世界问题。 有几种方法可以对 AI 的不同分支进行分类: + +* 监督学习与无监督学习与强化学习 +* 人工智能与狭义智力 +* 按人体功能: + * 机器视觉 + * 机器学习 + * 自然语言处理 + * 自然语言生成 + +接下来,我们介绍一个常见的分类: + +* **Machine learning and pattern recognition**: This is perhaps the most popular form of AI out there. We design and develop software that can learn from data. Based on these learning models, we perform predictions on unknown data. One of the main constraints here is that these programs are limited to the power of the data. + + 如果数据集很小,那么学习模型也将受到限制。 让我们看看典型的机器学习系统是什么样的: + +![](img/B15441_01_03.png) + +图 3:典型的计算机系统 + +当系统接收到以前看不见的数据点时,它将使用先前看过的数据(训练数据)中的模式来推断该新数据点。 例如,在面部识别系统中,该软件将尝试匹配眼睛,鼻子,嘴唇,眉毛等的图案,以便在现有用户数据库中找到面部。 + +* **基于逻辑的 AI** :数学逻辑用于在基于逻辑的 AI 中执行计算机程序。 用基于逻辑的 AI 编写的程序基本上是一组逻辑形式的语句,用于表达有关问题域的事实和规则。 它广泛用于模式匹配,语言解析,语义分析等。 +* **搜索**:AI 技术中广泛使用搜索技术。 这些程序检查了许多可能性,然后选择了最佳路径。 例如,在策略游戏中,例如国际象棋,网络,资源分配,调度等,经常使用它。 +* **Knowledge representation**: The facts about the world around us need to be represented in some way for a system to make sense of them. The languages of mathematical logic are frequently used here. If knowledge is represented efficiently, systems can be smarter and more intelligent. Ontology is a closely related field of study that deals with the kinds of objects that exist. + + 它是域中存在的实体的属性和关系的正式定义。 通常使用分类法或的层次结构完成此操作。 下图显示了信息和知识之间的区别: + +![](img/B15441_01_04.png) + +图 4:信息与知识 + +* **计划**:此字段用于优化计划,从而以最小的成本为我们带来最大的回报。 这些软件程序从有关情况的事实和目标说明开始。 这些程序还了解世界的事实,因此他们知道规则是什么。 他们从这些信息中得出实现目标的最佳方案。 +* **启发式**:启发式是用于解决给定问题的技术,该技术在短期内解决该问题是实用且有用的,但不能保证是最优的。 这更像是对应该采用何种方法解决问题的有根据的猜测。 在 AI 中,我们经常遇到无法检查所有可能性以选择最佳选项的情况。 因此,我们需要使用启发式方法来实现目标。 它们在机器人,搜索引擎等领域的 AI 中得到了广泛使用。 +* **遗传编程**:遗传编程是一种通过使程序配对并选择最适合的条件来获取程序来解决任务的方法。 程序被编码为一组基因,使用一种算法来获得可以很好地执行给定 任务的程序。 + +# 机器学习的五个部落 + +机器学习可以通过各种方式进一步分类。 我们最喜欢的分类之一是 Pedro Domingos 在他的书*主算法*中提供的分类。 在他的书中,他根据萌芽了思想的科学领域对机器学习进行了分类。 例如,遗传算法起源于生物学概念。 以下是完整的分类,Domingos 为部落使用的名称,每个部落使用的主要算法以及值得注意的支持者: + + +| **部落** | **来源** | **主导算法** | **支持者** | +| 象征主义者 | 逻辑与哲学 | 反演 | 汤姆·米切尔史蒂夫·麦格尔顿罗斯·昆兰 | +| 联络员 | 神经科学 | 反向传播 | Yan LeCun 杰弗里·欣顿尤舒亚·本吉奥(Yoshua Bengio) | +| 进化论者 | 生物学 | 基因编程 | 约翰·科扎约翰·霍兰德霍德·利普森 | +| 贝叶斯 | 统计 | 概率推断 | 大卫·赫克曼犹太珍珠迈克尔·乔丹 | +| 模拟器 | 心理学 | 内核机器 | 彼得·哈特弗拉基米尔·瓦普尼克道格拉斯·霍夫施塔特 | + +**象征主义者** –象征主义者使用归纳或反演的概念作为主要工具。 当使用归纳法时,逆推演不是从前提和结论开始,而是从一系列前提和结论开始,然后反向进行以填补缺失的部分。 + +推论的一个例子: + +苏格拉底是人类+所有人类都是凡人=可以推断出什么? (苏格拉底是凡人) + +归纳示例: + +苏格拉底是人类+ ?? =苏格拉底是凡人(人类是凡人?) + +**连通论者** –连通论者使用大脑,或者至少是我们对大脑的粗略了解,作为主要工具-主要是神经网络。 神经网络是一种算法,可以在大脑之后粗略地建模,旨在识别模式。 他们可以识别向量中包含的数字模式。 为了使用它们,需要将所有输入(例如图像,声音,文本或时间序列)转换为这些数字矢量。 很难打开杂志或新闻网站,而不阅读“深度学习”示例。 深度学习是神经网络的一种特殊类型。 + +**进化论者** –进化论者专注于使用进化,自然选择,基因组和 DNA 突变的概念,并将其应用于数据处理。 进化算法将不断变异,进化并适应未知条件和过程。 + +**贝叶斯** –贝叶斯将集中在使用概率推断来处理不确定性上。 视觉学习和垃圾邮件过滤是贝叶斯方法解决的一些问题。 通常,贝叶斯模型将采用假设并应用一种“先验”推理,假设某些结果更有可能出现。 然后,他们在看到更多数据时会更新假设。 + +**模拟器** –模拟器着重于发现示例之间相似之处的技术。 最著名的模拟器模型是 *k 最近邻*算法。 + +# 使用图灵测试定义情报 + +传说中的计算机科学家和数学家 *Alan Turing* 提出了 Turing 测试以提供智能的定义。 这是测试计算机是否可以学习模仿人类行为的测试。 他将聪明的行为定义为在对话中达到人类智能的能力。 这种表现应该足以诱使询问者认为答案来自人。 + +为了查看机器是否可以做到这一点,他提出了一个测试设置:他提议人类应该通过文本界面来询问机器。 另一个限制是,人不能知道谁在询问的另一端,这意味着它可以是机器,也可以是人。 为了启用此设置,人员将通过文本界面与两个实体进行交互。 这两个实体称为受访者。 其中一个将是人类,另一个将是 m 机器。 + +如果询问机无法告诉答案是来自机器还是人,则应答机通过测试。 下图显示了图灵测试的设置: + +![](img/B15441_01_05.png) + +图 5:图灵测试 + +可以想像,对于响应方的机器而言,这是一项艰巨的任务。 对话期间发生了很多事情。 至少,机器必须精通以下内容: + +* **自然语言处理**:机器需要此语言才能与询问器通信。 机器需要解析句子,提取上下文并给出适当的答案。 +* **知识表示**:机器需要存储在询问之前提供的信息。 它还需要跟踪在对话过程中提供的信息,以便它再次出现时可以做出适当的响应。 +* **推理**:对于机器来说,了解如何解释所存储信息的很重要。 人们倾向于自动执行此操作,以便实时得出结论。 +* **机器学习**:这是所必需的,以便机器可以实时适应新条件。 机器需要分析和检测模式,以便得出推断。 + +您一定想知道为什么人类要与文本界面进行通信。 根据 Turing 的说法,人的物理模拟对于智能是不必要的。 这就是图灵测试避免人与机器之间直接进行物理交互的原因。 + +还有另一种叫做总图灵测试的东西,它涉及视觉和运动。 要通过此测试,机器需要使用计算机视觉并使用 ng 机器人在周围移动来查看物体。 + +# 使机器像人一样思考 + +数十年来,我们一直在尝试让机器更像人类那样思考。 为了实现这一目标,我们首先需要了解人类的想法。 我们如何理解人类思维的本质? 做到这一点的一种方法是记下我们对事情的反应。 但这很快就变得棘手,因为有太多事情需要注意。 这样做的另一种方法是根据预定义的格式进行实验。 我们提出了一定数量的问题,以涵盖各种各样的人类主题,然后看看人们如何回应。 + +一旦收集了足够的数据,我们就可以创建一个模型来模拟人类过程。 该模型可用于创建可以像人类一样思考的软件。 当然,这说起来容易做起来难! 我们关心的只是给定输入的程序输出。 如果程序的行为与人类行为相匹配,那么我们可以说人类具有类似的思考机制。 + +下图显示了不同层次的思维以及我们的大脑如何对事物进行优先排序: + +![](img/B15441_01_06.png) + +图 6:思想水平 + +在计算机科学内部,有一个名为**认知模型**的研究领域,该领域致力于模拟人类的思维过程。 它试图了解人类如何解决问题。 它需要进入解决问题过程的思维过程,并将其转变为软件模型。 然后可以使用此模型来模拟人类行为。 + +认知建模用于各种 AI 应用程序,例如深度学习,专家系统,自然语言处理,roboti cs 等。 + +# 建立理性代理 + +人工智能的许多研究都集中在构建理性主体上。 什么是理性代理? 在此之前,让我们在 AI 的上下文中定义*合理性*一词。 合理性是指遵守一组规则并遵循其逻辑含义以实现理想的结果。 这需要以使执行该操作的实体获得最大利益的方式执行。 因此,如果在给定的一组规则下采取行动以实现其目标,则该行动者被称为理性行动。 它只是根据可用信息感知并采取行动。 当将机器人发送到未知地形时,该系统在 AI 中被大量用于设计机器人。 + +我们如何定义*理想的*是什么? 答案是,这取决于代理商的目标。 该代理应该是智能且独立的。 我们希望赋予适应新情况的能力。 它应该了解其环境,然后采取相应行动,以实现符合其最大利益的结果。 最大的利益取决于它要实现的总体目标。 让我们看看如何将输入转换为动作: + +![](img/B15441_01_07.png) + +图 7:将输入转化为行动 + +我们如何定义理性代理人的绩效指标? 也许有人会说它与成功的程度成正比。 设置代理以完成任务,因此性能度量取决于该任务完成的百分比。 但是我们必须考虑什么构成了整体的合理性。 如果只是结果,我们不考虑导致结果的行动。 + +做出正确的推断是理性的一部分,因为主体必须理性地行动以实现其目标。 这将有助于它得出可以连续使用的结论。 + +但是,在没有可证明的正确事情要做的情况下呢? 在某些情况下,代理不知道要做什么,但它仍然必须做一些事情。 + +让我们设置一个方案,使最后一点更加清楚。 想象一下,一辆无人驾驶汽车以每小时 60 英里的速度行驶,突然有人越过它。 对于本示例,假设给定汽车行驶的速度,则只有两种选择。 汽车要么撞向护栏,要么就知道会杀死汽车乘员,要么撞向行人并杀死他们。 正确的决定是什么? 该算法如何知道该怎么做? 如果您在开车,您会做什么吗? + +现在,我们将学习理性代理的最早示例之一-通用问题解决器。 就像我们将看到的那样,尽管它的名称很高大,但它确实无法解决任何问题,但是由于它,它在计算机科学领域是一个巨大的飞跃。 + +# 通用问题解决器 + +**通用问题解决器**( **GPS** )是由 Herbert Simon,J.C。Shaw 和 Allen Newell 提出的 AI 程序。 它是 AI 世界中第一个有用的计算机程序。 目的是使其能够作为通用的问题解决机器。 当然,以前有很多软件程序,但是这些程序执行特定的任务。 GPS 是第一个旨在解决任何一般问题的程序。 GPS 应该针对每个问题使用相同的基本算法来解决所有问题。 + +您一定已经意识到,这是艰巨的战斗! 为了对 GPS 进行编程,作者创建了一种新语言,称为**信息处理语言**( **IPL** )。 基本前提是使用一组格式正确的公式来表达任何问题。 这些公式将成为有多个源和汇的有向图的一部分。 在图中,源是指起始节点,宿是指终止节点。 对于 GPS,源是公理,汇是结论。 + +即使 GPS 只是通用的,它也只能解决明确定义的问题,例如证明几何和逻辑上的数学定理。 它还可以解决单词拼图和下棋。 原因是这些问题可以在合理范围内形式化。 但是在现实世界中,由于您可以采取多种可能的途径,因此这很快变得很棘手。 如果它试图通过计算图表中的步数来强行解决问题,则完全无法计算 。 + +## 解决 GPS 问题 + +让我们看看如何构造一个给定的问题以使用 GPS 解决它: + +1. 第一步是定义目标。 假设我们的目标是从杂货店里买些牛奶。 +2. 下一步是定义前提条件。 这些前提条件与目标有关。 要从杂货店获取牛奶,我们需要有一种运输方式,杂货店应该有牛奶。 + +1. 此后,我们需要定义运算符。 如果我的交通工具是汽车,并且汽车的燃油不足,那么我们需要确保可以向加油站付款。 我们需要确保您可以在商店购买牛奶。 + +操作员要注意条件以及影响条件的一切。 它由行动,前提条件和采取行动引起的变化组成。 在这种情况下,操作就是向杂货店捐款。 当然,这首先要取决于您有钱,这是前提。 通过给他们钱,您正在改变您的金钱状况,这将导致您得到牛奶。 + +如果您可以像我们刚才那样解决问题,GPS 将会起作用。 限制在于它使用搜索过程来执行其工作,这对于任何有意义的现实应用程序而言都过于复杂且耗时。 + +在本节中,我们了解了什么是理性主体。 现在,让我们学习如何使这些理性主体更加智能和有用。 + +# 建立智能代理 + +有很多方法可以将智能传递给代理。 最常用的技术包括机器学习,存储的知识,规则等。 在本节中,我们将重点关注机器学习。 在这种方法中,我们通过 gh 数据和训练来向代理赋予情报。 + +让我们看看智能代理如何与环境交互: + +![](img/B15441_01_08.png) + +图 8:智能代理与其环境的交互 + +通过机器学习,有时我们希望对机器进行编程以使用标记的数据来解决给定的问题。 通过浏览数据和相关标签,机器学习了如何提取模式和关系。 + +在前面的示例中,智能代理依赖于学习模型来运行推理引擎。 传感器感知到输入后,会将其发送到特征提取模块。 一旦提取了相关特征,训练有素的推理引擎将基于学习模型执行预测。 该学习模型是使用机器学习构建的。 然后,推理引擎做出决定并将其发送给执行器,执行器随后在现实世界中采取所需的操作。 + +今天存在许多机器学习的应用程序。 它用于图像识别,机器人技术,语音识别,预测股市行为等。 为了理解机器学习并构建完整的解决方案,您将必须熟悉来自不同领域的许多技术,例如模式识别,人工神经网络,数据挖掘, 统计等。 上。 + +## 型号类型 + +AI 世界中有两种类型的模型:分析模型和学习模型。 在我们拥有可以计算的机器之前,人们曾经依靠分析模型。 + +使用数学公式得出分析模型,这基本上是遵循一系列步骤以得出最终方程式的步骤。 这种方法的问题在于它是基于人类的判断。 因此,这些模型很简单,而且往往不准确,仅带有几个参数。 想想牛顿和其他古老的科学家在拥有计算机之前是如何进行计算的。 在得出工作公式之前,此类模型通常涉及长时间推导和长时间的反复试验。 + +然后,我们进入了计算机的世界。 这些计算机擅长分析数据。 因此,人们越来越多地使用学习的模型。 这些模型是通过训练过程获得的。 在训练期间,机器会查看许多输入和输出示例,以得出方程式。 这些学习的模型通常是复杂且准确的,具有数千个参数。 这产生了一个非常复杂的数学方程式,该方程式控制着可以帮助进行预测的数据。 + +机器学习使我们可以获得可以在推理引擎中使用的学习模型。 最好的事情之一是,我们不需要导出基本的数学公式。 您不需要了解复杂的数学,因为机器会根据数据得出公式。 我们需要做的就是创建输入列表和相应的输出。 我们得到的的学习模型只是标记的输入与与所需输出之间的关系。 + +# 安装 Python 3 + +在本书中,我们将使用 Python 3。 确保在计算机上安装了最新版本的 Python 3。 键入以下命令进行检查: + +```py +$ python3 --version +``` + +如果看到打印出类似 Python 3.x.x(其中 x.x 是版本号)的内容,那很好。 如果不是,则直接安装在 中。 + +## 在 Ubuntu 上安装 + +在 Ubuntu 14.xx 及更高版本上,Python 3 已默认安装。 如果没有,您可以使用以下命令来安装: + +```py +$ sudo apt-get install python3 +``` + +像我们在操纵器中一样执行检查命令: + +```py +$ python3 --version +``` + +您应该看到版本号作为输出。 + +## 在 Mac OS X 上安装 + +如果您使用的是 Mac OS X,建议使用 Homebrew 来安装 Python3。它是 Mac OS X 的出色软件包安装程序,非常易于使用。 如果没有 Homebrew,则可以使用以下命令进行安装: + +```py +$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +``` + +让我们更新程序包管理器: + +```py +$ brew update +``` + +让我们安装 Python 3: + +```py +$ brew install python3 +``` + +像之前一样运行 check 命令: + +```py +$ python3 --version +``` + +您应该看到在输出上打印的数字为 。 + +## 在 Windows 上安装 + +如果使用 Windows,建议您使用 Python 3 的`SciPy-stack`兼容发行版。Anaconda 非常流行并且易于使用。 您可以在以下位置找到安装说明: [https://www.continuum.io/downloads](https://www.continuum.io/downloads) 。 + +如果要检查 Python 3 的其他`SciPy-stack`兼容发行版,可以在 [http://www.scipy.org/install.html](http://www.scipy.org/install.html) 中找到它们。 这些发行版的好处是它们附带了所有必需的软件包。 如果使用这些版本之一,则无需单独安装软件包。 + +安装后,请像前面的 id 一样运行 check 命令: + +```py +$ python3 --version +``` + +您应该看到已输出的 ve 个版本号。 + +# 安装软件包 + +在本书中,我们将使用各种软件包,例如 NumPy,SciPy,scikit-learn 和 matplotlib。 在继续之前,请确保安装这些软件包。 + +如果您使用 Ubuntu 或 Mac OS X,则安装这些软件包非常简单。 所有这些软件包都可以使用单行命令安装。 这是与相关的安装链接: + +* NumPy: [http://docs.scipy.org/doc/numpy-1.10.1/user/install.html](http://docs.scipy.org/doc/numpy-1.10.1/user/install.html ) +* SciPy: [http://www.scipy.org/install.html](http://www.scipy.org/install.html ) +* scikit-learn: [http://scikit-learn.org/stable/install.html](http://scikit-learn.org/stable/install.html ) +* matplotlib: [http://matplotlib.org/1.4.2/users/installing.html](http://matplotlib.org/1.4.2/users/installing.html ) + +如果您使用的是 Windows,则应该已安装 Python 3 的`SciPy-stack`兼容版本。 + +# 正在加载数据 + +为了构建学习模型,我们需要代表世界的数据。 现在我们已经安装了必要的 Python 软件包,让我们看看如何使用这些软件包与数据进行交互。 通过键入以下命令,输入 Python 命令提示符: + +```py +$ python3 +``` + +让我们导入包含所有数据集的包: + +```py +>>> from sklearn import datasets +``` + +让我们加载房价数据集: + +```py +>>> house_prices = datasets.load_boston() +``` + +打印数据: + +```py +>>> print(house_prices.data) +``` + +您将看到类似于以下内容的输出: + +![](img/B15441_01_09.png) + +图 9:输入房屋价格的输出 + +我们来看看标签。 + +您将看到以下输出: + +![](img/B15441_01_10.png) + +图 10:预测房价的输出 + +实际的数组较大,因此图像表示该数组中的前几个值。 + +scikit-learn 包中还有可用的图像数据集。 每个图像的形状均为 8×8。 让我们加载它: + +```py +>>> digits = datasets.load_digits() +``` + +打印礼物 h 图像: + +```py +>>> print(digits.images[4]) +``` + +您将看到此输出: + +![](img/B15441_01_11.png) + +图 11:scikit-learn 图像数组的输出 + +如您所见,它具有八行八列。 + +# 摘要 + +在本章中,我们讨论了: + +* 人工智能的意义何在?为什么我们需要研究它 +* 人工智能的各种应用和分支 +* 图灵测试是什么以及如何进行 +* 如何使机器像人一样思考 +* 理性主体的概念及其应如何设计 +* 通用问题解决器(GPS)以及如何使用 GPS 解决问题 +* 如何使用机器学习开发智能代理 +* 不同类型的机器学习模型 + +我们还介绍了如何在各种操作系统上安装 Python 3,以及如何安装构建 AI 应用程序所需的必要软件包。 我们讨论了如何使用这些包来加载 scikit-learn 中可用的数据。 + +在下一章中,我们将学习监督学习以及如何建立分类和回归模型。 \ No newline at end of file diff --git a/docs/ai-py/02.md b/docs/ai-py/02.md new file mode 100644 index 0000000000000000000000000000000000000000..fee8a09b859eae26fb7e2395a675316b50aec17f --- /dev/null +++ b/docs/ai-py/02.md @@ -0,0 +1,507 @@ +# 2 + +# 人工智能的基本用例 + +在本章中,我们将讨论**人工智能**( **AI** )的一些用例。 这绝不是详尽的清单。 许多行业已受到 AI 的影响,但尚未受影响的那些行业的清单每天都在缩短。 具有讽刺意味的是,机器人,自动化和 AI 无法接手的某些工作是薪水较低,需要较少“大脑”力量的工作。 例如,在我们能够代替发型师和水管工之前还需要一段时间。 这两项工作都需要机器人还需要掌握很多技巧和细节。 我知道,我的妻子要花很长时间才能将她的头发信任除当前发型师以外的任何人,更不用说机器人了。 + +本章将讨论: + +* 一些具有代表性的 AI 用例 +* 最长的工作将由自动化取代 +* 受 AI 影响最大的行业 + +# 代表性 AI 用例 + +从金融到医学,很难找到一个不受人工智能干扰的行业。 我们将重点关注日常生活中最流行的 AI 应用的真实示例。 我们将探索当前的技术水平以及即将发生的事情。 最重要的是,也许这本书会激发您的想象力,并且您会提出一些对社会产生积极影响的新想法,我们可以将其添加到本书的下一版中。 + +人工智能,认知计算,机器学习和深度学习只是当今促成快速变化的一些破坏性技术。 由于云计算,**物联网**( **IoT** )和边缘计算的进步,可以更快地采用这些技术。 通过将所有这些技术整合在一起,组织正在重塑开展业务的方式。 这仅仅是个开始; 我们甚至不在第一局,甚至还没有记录过第一击! + +到此为止,让我们开始看一下 AI 的一些当代应用。 + +# 数字个人助理和聊天机器人 + +不幸的是,对于某些呼叫中心来说,仍然太普遍以至于无法使用制作为的传统**交互式语音响应**( **IVR** )**系统** 称他们为耐心锻炼。 但是,我们在自然语言处理领域取得了巨大的进步:聊天机器人。 一些最受欢迎的示例是: + +* **Google 助手**:Google 助手于 2016 年启动,是目前可用的最高级聊天机器人之一。 可以在各种设备中找到它,例如电话,耳机,扬声器,洗衣机,电视和冰箱。 如今,大多数 Android 手机都包含 Google Assistant。 Google Home 和 Nest Home Hub 也支持 Google Assistant。 +* **Amazon Alexa** :Alexa 是由 Amazon 开发和销售的虚拟助手。 它可以通过语音和执行命令(例如播放音乐,创建待办事项,设置警报,播放有声读物和回答基本问题)与用户互动。 它甚至可以按需告诉您一个笑话或故事。 Alexa 还可以用于控制兼容的智能设备。 开发人员可以通过安装技能来扩展 Alexa 的功能。 Alexa 技能是第三方供应商开发的其他功能。 +* **Apple Siri** :Siri 可以接受用户语音命令和自然语言用户界面,以通过解析这些语音命令并将这些请求委派给一组 Internet 服务来回答问题,提出建议并执行操作。 该软件可以适应用户的个别语言使用,他们的搜索和偏好。 使用得越多,它就会学得越多,就会越好。 +* **Microsoft Cortana** :Cortana 是由 Microsoft 设计和创建的另一种数字虚拟助手。 Cortana 可以设置提醒和警报,识别自然的语音命令,并使用信息回答问题。 + +所有这些助手都将允许您执行全部或至少大部分以下任务: + +* 家里的控制设备 +* 根据命令播放音乐和显示视频 +* 设置计时器和提醒 +* 预约 +* 发送短信和电子邮件 +* 拨打电话 +* 公开申请 +* 阅读通知 +* 执行翻译 +* 从电子商务网站订购 + +可能不支持但将变得越来越普遍的一些任务是: + +* 登机 +* 预订酒店 +* 预订餐厅 + +所有这些平台还支持 3 个第三方开发人员来开发自己的应用程序或 Amazon 称呼他们的“技能”。 因此,可能性是无限的。 + +现有 Alexa 技能的一些示例: + +* **MySomm** :推荐某些肉配哪种葡萄酒 +* **调酒师**:提供有关如何制作酒精饮料的说明 +* **7 分钟锻炼**:将指导您进行艰难的 7 分钟锻炼 +* **Uber** :允许通过 Alexa 订购 Uber 游乐设施 + +前面列出的所有服务都在不断改善。 他们不断从与客户的互动中学习。 服务的开发人员以及利用服务用户每天创建的新数据点的系统都对它们进行了改进。 + +大多数云提供商都非常容易创建聊天机器人,对于某些基本示例,无需使用编程语言。 此外,将这些聊天机器人部署到 Slack,Facebook Messenger,Skype 和 WhatsApp 等服务并不难。 + +# 个人司机 + +无人驾驶或无人驾驶汽车是无需人工协助即可沿着预先建立的路线行驶的车辆。 如今,大多数自动驾驶汽车都不再依赖单一传感器和导航方法,而是使用多种技术,例如雷达,声纳,激光雷达,计算机视觉和 GPS。 + +随着技术的出现,行业开始创建标准以实施和衡量其进度。 无人驾驶技术也是如此。 SAE International 已创建标准 J3016,该标准定义了汽车的六个自动化级别,以便汽车制造商,供应商和政策制定者可以使用相同的语言来对汽车的复杂程度进行分类: + +**级别 0(无自动化)** + +该车没有自动驾驶功能。 驾驶员充分参与并负责。 驾驶员操纵,制动,加速和协商交通。 这描述了当今道路上最新的汽车。 + +**1 级(驾驶员辅助)** + +系统功能:在某些情况下,汽车可以控制转向或车辆速度,但不能同时控制两者。 + +驾驶员参与:驾驶员执行驾驶的所有其他方面,并全权负责监视道路并在辅助系统无法正常工作时接管汽车。 例如,自适应巡航控制。 + +**2 级(部分自动化)** + +在某些情况下,汽车可以转向,加速和制动。 驾驶员仍然执行许多操作,例如解释和响应交通信号或改变车道。 控制车辆的责任主要落在驾驶员身上。 制造商仍然要求驾驶员充分参与。 此级别的示例包括: + +* 奥迪交通堵塞辅助 +* 凯迪拉克超级巡航 +* 梅赛德斯-奔驰驾驶员辅助系统 +* 特斯拉自动驾驶仪 +* 沃尔沃飞行员辅助 + +**3 级(有条件的自动化)** + +2 级和 3 级之间的枢轴点很关键。 在这个级别上,控制和监视汽车的责任开始从驾驶员变为计算机。 在适当的条件下,计算机可以控制汽车,包括监视环境。 如果汽车遇到无法处理的情况,它将要求驾驶员进行干预并采取控制措施。 驾驶员通常不控制汽车,但必须随时可以接管。 奥迪交通堵塞飞行员就是一个例子。 + +**级别 4(高度自动化)** + +在大多数情况下,汽车不需要人为干预,但在某些道路,天气或地理条件下,仍需要人工协助。 在仅限于定义区域的共享汽车模型下,可能不会有人参与。 但是对于私人拥有的汽车,驾驶员可以管理地面街道上的所有驾驶职责,并且系统可以接管高速公路。 Google 现已淘汰的 Firefly 吊车就是这一级别的一个例子。 它没有踏板或方向盘。 它被限制为最高时速 25 英里/小时,并且没有在公共街道上使用。 + +**5 级(全自动)** + +无人驾驶系统可以控制并在人类可以应付的任何道路上和任何条件下操作汽车。 汽车的“操作员”只需输入目的地。 目前尚无此级别的产品,但是有几家公司已经关闭,到本书出版时可能已经存在。 + +现在,我们将回顾在该领域工作的一些领先公司: + +**Google 的 Waymo** + +截至 2018 年,Waymo 的自动驾驶汽车在公共道路上行驶了 800 万英里,在模拟环境中行驶了 50 亿英里。 在接下来的几年中,几乎可以肯定,我们将能够购买能够完全自动驾驶的汽车。 特斯拉(Tesla)等人已经通过其自动驾驶功能提供了驾驶员协助,并且可能将成为第一家提供完整自动驾驶功能的公司。 想象一个世界,今天出生的孩子将永远不必获得驾照! 仅凭人工智能的进步,对我们社会的破坏将是巨大的。 不再需要送货司机,出租车司机和卡车司机。 即使在无人驾驶的未来仍然发生交通事故,由于我们将消除分心驾驶和酒后驾驶,因此将挽救数百万生命。 + +Waymo 于 2018 年在美国亚利桑那州推出了首项商业无人驾驶服务,并计划在全国和全球范围内进行扩展。 + +**Uber ATG** + +Uber 的**先进技术小组**( **ATG** )是 Uber 的子公司,致力于开发自动驾驶技术。 2016 年,Uber 在匹兹堡的街道上推出了一项实验性汽车服务。 优步计划购买多达 24,000 辆沃尔沃 XC90,并为其配备自动驾驶技术,并于 2021 年开始以一定容量实现商业化。 + +可悲的是,2018 年 3 月,伊莱恩·赫兹伯格(Elaine Herzberg)参与了一辆 Uber 无人驾驶汽车的事故并死亡。 根据警方的报道,她在试图通过手机观看视频时,试图过马路时被 Uber 车辆撞倒。 赫兹伯格女士成为涉及无人驾驶汽车的事故中首批死亡的人之一。 理想情况下,我们希望这种技术不会发生任何事故,但是我们需要根据当前交通事故带来的危机来调整我们要求的安全水平。 就背景而言,2017 年美国有 40,100 辆汽车死亡; 即使我们继续看到无人驾驶汽车发生事故,如果将死亡人数削减一半,每年也可以挽救数千人的生命。 + +当然,可以设想一种无人驾驶汽车,其外观比起我们现有汽车的内部更像一个客厅。 不需要方向盘,踏板或任何种类的手动控制。 汽车唯一需要的输入就是您的目的地,可以在旅行开始时通过对您的汽车“说话”来输入。 由于汽车将能够感知何时需要维修或汽车功能出现问题,因此无需跟踪维护时间表。 + +车祸的责任将从车辆的驾驶员转移到车辆的制造商,而不再需要购买汽车保险。 最后一点可能是汽车制造商部署该技术的速度缓慢的原因之一。 甚至汽车拥有量也可能被颠覆,因为我们可以在需要时而不是一直需要时召唤一辆车。 + +# 货运和仓库管理 + +亚马逊的分拣设施是在人类,计算机和机器人之间形成的共生关系的最好例子之一。 计算机接受客户订单并决定将商品路由到何处,机器人充当 mu 子,在仓库中搬运托盘和库存。 人们通过手动挑选进入每个订单的物品来解决“最后一英里”问题。 机器人熟练地无意识地重复执行多次任务,只要涉及到某种模式并且要进行一定程度的预训练就可以做到。 但是,让机器人捡起 20 磅重的包装,然后立即能够抓鸡蛋而不破鸡蛋,这是机器人技术难题之一。 + +机器人在处理不同大小,重量,形状和易碎性的物体时会遇到困难。 许多人可以轻松完成的任务。 因此,人们要处理机器人遇到的困难的任务。 这三种类型的不同演员之间的相互作用转化为一个经过微调的乐团,该乐团每天可以交付数百万个程序包而几乎没有错误。 + +甚至亚马逊机器人实现总监斯科特·安德森(Scott Anderson)在 2019 年 5 月都承认,距离全自动仓库至少有 10 年的路程。 因此,我们将继续在世界各地的仓库中看到这种配置一段时间。 + +# 人类健康 + +在健康科学中应用 AI 的方法几乎是无限的。 我们将在这里讨论其中的一些,但这绝不是详尽的清单。 + +**药物发现** + +AI 可以协助生成候选药物(即要在医学上测试的分子),然后使用约束满足或实验模拟快速消除其中的一些候选药物。 在后面的章节中,我们将学习有关约束满足编程的更多信息。 简而言之,这种方法使我们能够通过快速生成数百万种可能的候选药物来加快药物发现,并且在候选药物不满足某些预定约束条件时也可以迅速拒绝它们。 + +此外,在某些情况下,我们可以在计算机中模拟实验,否则在现实生活中执行该实验会更加昂贵。 + +此外,在某些情况下,研究人员仍在进行真实世界的实验,但依靠机器人进行实验并加快处理过程。 这些新兴领域被称为**高通量筛选**( **HTS** )和**虚拟高通量筛选**( **VHTS** )。 + +机器学习已开始越来越多地用于增强临床试验。 埃森哲咨询公司开发了一种称为**智能临床试验**( **ITP** )的工具。 它用于预测临床试验的时间。 + +可以令人惊讶地使用的另一种方法是应用于药物发现的方法是**自然语言处理**( **NLP** )。 可以使用一串字母来表示基因组数据,并且可以使用 NLP 技术来处理或“理解”基因组序列的含义。 + +**保险定价** + +机器学习算法可用于通过更准确地预测在患者身上花费多少,驾驶员的健康状况或寿命长短来更好地为保险定价。 + +例如,Insilico Medicine 的 *young.ai* 项目可以准确地预测血液样本和照片中某人的寿命。 血液样本提供 21 种生物标志物,例如胆固醇水平,炎症标志物,血红蛋白计数和白蛋白水平,用作机器学习模型的输入。 模型的其他输入是种族和年龄,以及该人的照片。 + +有趣的是,到目前为止,任何人都可以通过访问 young.ai( [https://young.ai](https://young.ai) )并提供所需信息来免费使用此服务。 + +**患者诊断** + +通过使用复杂的规则引擎和机器学习,医生可以对患者进行更好的诊断,并在实践中提高生产力。 例如,在张刚[1]在加利福尼亚大学圣地亚哥分校的一项最新研究中,一种系统可以比初级儿科医生更准确地诊断儿童疾病。 该系统能够以 90%至 97%的准确度诊断以下疾病: + +* 腺热 +* 罗索拉 +* 流感 +* 水痘 +* 手足口病 + +输入数据集包含 2016 年至 2017 年间来自中国广州地区的 130 万儿童就医的医疗记录。 + +**医学影像学解释** + +医学成像数据是有关患者的复杂而丰富的信息来源。 CAT 扫描,MRI 和 X 射线包含原本无法获得的信息。 放射科医生和临床医生不足以解释它们。 从这些图像获得结果有时可能需要几天时间,有时可能会被误解。 最近的研究发现,机器学习模型的性能甚至可以比人类模型好。 + +数据科学家开发了支持 AI 的平台,与传统方法相比,该平台可以在几分钟(而不是几天)内解释 MRI 扫描和放射图像,并且准确性更高。 + +令人惊讶的是,美国放射学院的领导者们对此并不感到担心,而是将 AI 的出现视为对医生有价值的工具。 为了促进该领域的进一步发展,**美国放射学院数据研究所**( **ACR DSI** )发布了医学成像中的多个 AI 用例,并计划继续发布 更多。 + +**精神病学分析** + +与精神科医生进行一个小时的会议可能需要花费数百美元。 我们正处于能够使用 AI 聊天机器人模拟行为的风口浪尖。 至少,这些漫游器将能够在与精神科医生的会谈中提供后续护理,并在医生就诊之间为患者提供护理。 + +自动化顾问的一个早期例子是 Eliza。 它是由 Joseph Weizenbaum 在 1966 年开发的。 它允许用户与模仿罗杰式心理治疗师的计算机进行“对话”。 值得注意的是,Eliza 感觉很自然,但是它的代码只有几百行,并且其核心并没有真正使用太多的 AI。 + +最近和更先进的示例是 Ellie。 Ellie 由南加州大学创意技术研究所创建。 它有助于治疗抑郁症或创伤后应激障碍患者。 埃莉(Ellie)是位虚拟的治疗师(她出现在屏幕上),对情绪暗示做出反应,在适当的时候肯定地点头,并转移座位。 她可以感知一个人的脸上的 66 个点,并使用这些输入来读取一个人的情绪状态。 艾莉(Ellie)的秘密之一是,她显然不是人,这使人们对她的判断力降低了,对她的开放更自在。 + +**智能健康记录** + +众所周知,医学落后于电子记录。 数据科学提供了多种方法来简化对患者数据的捕获,包括 OCR,手写识别,语音到文本捕获以及实时读取和分析患者生命体征。 不难想象不久的将来,由于迫在眉睫的健康问题,人工智能引擎可以实时分析这些信息,从而做出诸如调整人体葡萄糖水平,服用药物或寻求医疗帮助等决策。 + +**疾病检测与预测** + +人类基因组是的最终数据集。 在不久的某个时候,我们将能够使用人类基因组作为机器学习模型的输入,并能够使用此庞大的数据集检测和预测各种各样的疾病和状况。 + +使用基因组数据集作为机器学习的输入是一个令人兴奋的领域,它正在迅速发展,并将彻底改变医学和卫生保健。 + +人类基因组包含超过 30 亿个碱基对。 我们在两个方面将取得进展,这将加快进度: + +* 对基因组生物学的理解不断进步 +* 大数据计算的进步,可以更快地处理大量数据 + +有很多研究将深度学习应用于基因组学领域。 尽管基因组学的深度学习仍处于早期阶段,但它有可能为以下领域提供信息: + +* 功能基因组学 +* 肿瘤科 +* 人口遗传学 +* 临床遗传学 +* 作物单产提高 +* 流行病学与公共卫生 +* 进化和系统发育分析 + +# 知识搜索 + +我们到了的地步,在某些情况下,甚至都没有意识到我们正在使用人工智能。 一个技术或产品是好的标志是当我们不必停止思考它是如何做的时。 Google 搜索就是一个很好的例子。 该产品已经在我们的生活中无处不在,我们还没有意识到它在多大程度上依赖人工智能来产生惊人的结果。 从其谷歌建议技术到其结果相关性的不断提高,人工智能已深深地嵌入其搜索过程中。 + +据彭博社报道,2015 年初,Google 开始使用名为 RankBrain 的深度学习系统来协助生成搜索查询响应。 彭博社的文章对 RankBrain 的描述如下: + +> “ RankBrain 使用人工智能将大量书面语言嵌入计算机可以理解的数学实体(称为矢量)中。如果 RankBrain 看到了自己不熟悉的单词或短语,则机器可以猜测是哪个单词或短语。 短语可能具有相似的含义并相应地过滤结果,从而使其更有效地处理了前所未有的搜索查询。” + +> -克拉克,杰克[2] + +在上一份报告中,Ranrainbrain 在数十亿个 Google 搜索查询中扮演着重要角色。 可以想像,该公司对 RankBrain 的工作原理 tight 之以鼻,而且甚至 Google 都可能很难解释它的工作方式。 您知道,这是深度学习的难题之一。 在许多情况下,它可以提供高度准确的结果,但是就为什么给出单个答案而言,深度学习算法通常很难理解。 基于规则的系统,甚至其他机器学习模型(例如 Random Forest)都更容易解释。 + +深度学习算法缺乏可解释性会产生重大影响,包括法律影响。 最近,Google 和 Facebook 等人发现自己处于显微镜下,以确定他们的结果是否有偏见。 将来,立法者和监管者可能会要求这些技术巨头为某种结果提供理由。 如果深度学习算法不能提供可解释性,则可能会迫使他们使用其他精度较低的算法来提供解释性。 + +最初,RankBrain 仅协助约 15%的 Google 查询,但现在几乎涉及所有用户查询。 + +但是,如果查询是普通查询或算法可以理解的查询,则 RankBrain 排名得分的权重很小。 如果查询是该算法之前未曾查看过的查询,或者它不知道其含义,则 RankBrain 得分更为相关。 + +# 推荐系统 + +建议系统是已融入我们日常生活的 AI 技术的另一个示例。 亚马逊,YouTube,Netflix,LinkedIn 和 Facebook 都依赖推荐技术,我们甚至没有意识到我们正在使用它。 推荐系统严重依赖数据,并且可用数据越多,它们将变得越强大。 这些公司拥有世界上最大的市值,这并非巧合,它们的力量来自能够利用客户数据中隐藏的力量。 希望这种趋势将来会继续。 + +什么是建议? 让我们首先探索什么不是答案。 这不是一个明确的答案。 某些问题,例如“二加二是什么?” 或“土星有多少颗卫星?” 有明确的答案,没有主观性的余地。 其他问题,例如“您最喜欢的电影是什么?” 或“你喜欢萝卜吗?” 是完全主观的,答案将取决于回答问题的人。 一些机器学习算法因这种“模糊性”而蓬勃发展。 同样,这些建议可能会产生巨大的影响。 + +考虑一下亚马逊不断推荐某产品而不是另一产品的后果。 如果没有找到其他分销和销售产品的方法,那么制作推荐产品的公司将会蓬勃发展,制作不推荐产品的公司可能会倒闭。 + +推荐系统可以改进的方法之一是通过从系统用户那里进行先前的选择。 如果您是第一次访问电子商务网站,但是没有订单历史记录,那么该网站将很难为您量身定制建议。 如果您购买运动鞋,则该网站现在有一个数据点,可以作为起点使用。 根据系统的复杂程度,可能会建议使用另一双运动鞋,一双运动袜甚至篮球(如果鞋子是高帮鞋)。 + +良好推荐系统的重要组成部分是随机因素,该因素偶尔会“四肢走动”,并提出可能与初始用户选择无关的奇怪建议。 推荐系统不仅从历史中学习来找到相似的建议,而且还尝试提出可能乍一看并不相关的新建议。 例如,Netflix 用户可能会观看“教父”,而 Netflix 可能会开始推荐 Al Pacino 电影或流氓电影。 但它可能会建议您使用“伯恩身份”(Bourne Identity)。 如果用户不接受推荐或不观看电影,则算法将从中学习并避免使用其他电影,例如“伯恩身份”(例如,以杰森·伯恩为主要角色的任何电影)。 + +随着推荐系统变得越来越好,可能性是令人兴奋的。 他们将能够为个人数字助理提供支持,并成为您的私人管家,该管家对您的好恶有深刻的了解,并可以提出您可能没有想到的好建议。 建议可以从这些系统中受益的领域包括: + +* 餐厅 +* 电影 +* 音乐 +* 潜在合作伙伴(在线约会) +* 书籍和文章 +* 搜索结果 +* 金融服务(机器人顾问) + +推荐系统的一些值得注意的特定示例如下: + +**Netflix 奖** + +Netflix 奖是在推荐器系统社区中引起大量嗡嗡声的竞赛。 从 2006 年到 2009 年,Netflix 赞助了比赛,奖金为一百万美元。 Netflix 提供了超过 1 亿个收视率的数据集。 + +Netflix 愿意向推荐最准确的团队支付奖金,并且比 Netflix 现有推荐系统的推荐准确性高 10%。 这项竞赛激发了人们对新的,更准确的算法的研究。 2009 年 9 月,大奖授予了 BellKor 的 Pragmatic Chaos 团队。 + +**潘朵拉** + +潘朵拉是领先的音乐服务之一。 与 Apple 和 Amazon 等其他公司不同,Pandora 的唯一重点是音乐服务。 潘多拉(Pandora)显着的服务功能之一就是定制广播电台的概念。 这些“电台”允许用户按流派播放音乐。 您可以想象,推荐系统是此功能的核心。 + +Pandora 的推荐器基于多个层次: + +* 首先,他们的音乐专家团队会根据流派,节奏和进度来注释歌曲。 +* 这些注释被转换为用于比较歌曲相似度的向量。 这种方法促进了来自未知艺术家的“长尾巴”或晦涩音乐的呈现,尽管如此,它仍然可能适合个人听众。 +* 该服务也高度依赖用户反馈,并使用它不断增强服务。 Pandora 收集了超过 750 亿个关于收听者偏好的反馈数据点。 +* 然后,潘多拉(Pandora)推荐引擎可以使用收听者的先前选择,地理位置和其他人口统计数据,根据收听者的偏好执行个性化过滤。 + +总体而言,潘多拉(Pandora)的推荐器使用大约 70 种不同的算法,其中包括 10 种用于分析内容的算法,40 种用于处理集体智慧的算法以及大约 30 种用于进行个性化过滤的算法。 + +**改善** + +机器人顾问是推荐引擎,可在最少人参与的情况下提供投资或财务建议和管理。 这些服务使用机器学习来自动分配,管理和优化客户的资产组合。 他们可以以比传统顾问更低的成本提供这些服务,因为它们的开销更低,并且其方法更具可扩展性。 + +现在,在这个领域竞争激烈,有超过 100 家公司提供此类服务。 机器人顾问被认为是一个巨大的突破。 以前,财富管理服务是专为高净值人士提供的专有且昂贵的服务。 与传统的人工服务相比,机器人顾问承诺以更低的成本向更广泛的受众提供类似的服务。 机器人顾问可以潜在地将投资分配到各种各样的投资产品中,例如股票,债券,期货,商品,房地产以及其他外来投资。 但是,为了简单起见,投资通常限于**交易所交易基金**( **ETF** )。 + +正如我们提到的,有许多公司提供机器人咨询。 例如,您可能需要研究 Betterment 以了解有关此主题的更多信息。 填写风险调查表后,Betterment 将为用户提供定制的,多样化的产品组合。 通常,Betterment 将推荐低收费股票和债券指数基金的组合。 Betterment 收取管理费(占产品组合的百分比),但低于大多数人力服务。 请注意,我们不认可这项服务,我们仅将其作为金融行业推荐引擎的示例。 + +# 智能家居 + +每当您在大街上向普通百姓提出 AI 的话题时,他们通常都会对替代人类工人的时间表示怀疑。 他们可以正确地指出我们仍然需要在房子周围做很多家务活的事实。 人工智能不仅需要在技术上成为可能,而且还需要在经济上可行才能被广泛采用。 家政服务通常是一种低薪职业,因此,取代它的自动化需要价格相同或更便宜。 此外,家务劳动需要很多技巧,而且其中包括不一定要重复的任务。 让我们列出该自动机需要熟练执行的一些任务: + +* 洗衣服 +* 叠衣服 +* 煮晚餐 +* 铺床 +* 捡起地板上的物品 +* 拖把,灰尘和真空 +* 洗碗 +* 监控房屋 + +众所周知,其中一些任务很容易在机器上执行(即使没有 AI),而其中一些则非常困难。 因此,出于经济考虑,房屋可能成为最后一个完全自动化的地方之一。 尽管如此,让我们看一下在这一领域取得的一些惊人进步。 + +**家庭监控** + +家庭监视是领域,通常已经可以找到很好的解决方案。 亚马逊提供的 Ring 视频门铃和 Google Nest 温控器是两种价格低廉的选择,已广泛使用和流行。 这是今天可以购买的智能家居设备的两个简单示例。 + +环形视频门铃是连接到互联网的智能家居设备,可以通过智能手机通知房主家中的活动,例如访客。 该系统不会连续记录,而是在按下门铃或激活移动探测器时激活。 然后,环形门铃可以让房主观看活动或使用内置的麦克风和扬声器与访客交流。 有些型号还允许房主通过智能锁远程打开门,并让访客进入房屋。 + +Nest Learning Thermostat 是一款智能家用设备,最初由 Nest Labs 开发,该公司后来被 Google 收购。 它是由 Tony Fadell,Ben Filson 和 Fred Bould 设计的。 它是可编程的,支持 Wi-Fi 且具有自学习功能。 它使用人工智能来优化房屋的温度,同时节省能源。 + +在使用的最初几周中,将恒温器设置为首选设置,这将作为基准。 温控器将了解您的日程安排和您的首选温度。 使用内置传感器和手机的位置,当没人在家时,恒温器将转换为节能模式。 + +自 2011 年以来,Nest 温控器已为全球数百万个家庭节省了数十亿千瓦时的能源。 独立研究表明,它可以为人们节省平均 10%至 12%的取暖费用和 15%的冷却费用,因此大约 2 年内它可以收回成本。 + +**吸尘和拖把** + +吸尘和拖把是流行的移交给机器人的两个任务。 机器人吸尘器是使用 AI 吸尘表面的自主机器人吸尘器。 根据设计的不同,其中一些机器使用旋转刷达到狭窄的角落,某些机型除了具有吸尘功能外还包括其他一些功能,例如拖把和紫外线杀菌。 推广这项技术的大部分功劳归于公司(而非电影) *iRobot* 。 + +iRobot 于 1990 年由 Rodney Brooks,Colin Angle 和 Helen Greiner 在麻省理工学院的人工智能实验室工作后相识而创立。 iRobot 以其吸尘机器人(Roomba)而闻名,但很长一段时间以来,他们还拥有一个专门从事军事机器人开发的部门。 Roomba 于 2002 年开始销售。截至 2012 年,iRobot 已售出超过 800 万台家用机器人,并制造了 5,000 多台国防和安全机器人。 该公司的 PackBot 是美军使用的炸弹处理机器人,已在伊拉克和阿富汗广泛使用。 PackBots 还被用于在福岛第一核电站的危险条件下收集信息。 在墨西哥湾的 Deepwater Horizo​​n 溢油事故发生之后,iRobot 的 Seaglider 被用来探测水下的油藏。 + +另一款 iRobot 产品是 Braava 系列清洁剂。 Braava 是一款小型机器人,可以拖地和扫地。 它适用于浴室和厨房等狭小空间。 它喷水并使用各种不同的垫来有效,安静地清洁。 某些 Braava 型号具有内置的导航系统。 Braava 没有足够的能力去除深层的污渍,因此它不是完整的人类替代品,但确实具有广泛的接受度和很高的评价。 我们希望他们继续受欢迎。 + +家庭智能设备的潜在市场是巨大的,几乎可以肯定的是,我们将继续看到成熟的公司和初创公司都试图利用这个尚未开发的市场。 + +**收拾烂摊子** + +正如我们在运输用途案例中了解到的一样,挑选具有不同重量,尺寸和形状的物体是最困难的自动化任务之一。 机器人可以在均匀的条件下(例如工厂车间,某些机器人专门从事某些任务)高效地执行任务。 然而,在拾起椅子之后拾起一双鞋可能是巨大的挑战,而且代价昂贵。 因此,不要指望机器很快就会以具有成本效益的方式普及这种家庭杂务。 + +**私人厨师** + +就像从地板上捡起物品一样,烹饪需要捡起不同的物品。 然而,有两个原因可以使我们期望“自动烹饪”会更快发生: + +* 某些餐馆可能会收取数百美元的食物费用,并为熟练的厨师付出高昂的代价。 因此,如果这样做可以提高利润,他们可能会愿意使用技术来替换高价员工。 一个五星级的寿司餐厅就是一个例子。 +* 厨房中的某些任务是重复性的,因此适合自动化。 想想快餐店,汉堡和炸薯条可能要成百上千。 因此,不是由一个机器来处理整个不同的烹饪过程,而是一系列机器可以处理该过程的各个重复阶段。 + +智能假肢是人工智能增强人类而不是替代人类的典范。 有不止几名厨师在事故中失去了手臂或出生时没有肢体。 + +一个例子是厨师迈克尔凯恩斯(Michael Caines),他经营一家米其林两星级餐厅,并在一场可怕的车祸中失去了手臂。 凯恩斯厨师一直担任英格兰德文郡吉德利公园的主厨,直到 2016 年 1 月。[3] 他目前是埃克塞特和埃克斯茅斯之间的林普斯通庄园酒店的行政总厨。 他现在用假肢做饭,但考虑到他的食物质量,您永远不会知道。 + +另一个例子是运动员和厨师 Eduardo Garcia –世界上最先进的仿生手使这一切成为可能。 + +2011 年 10 月,猎弓麋鹿在蒙大拿州偏僻地区被电死。 爱德华多(Eduardo)于 2011 年 10 月独自打猎。当他看到一只死去的小黑熊时,他正在乡下。 他停下来检查一下,跪下来,用刀刺了一下。 + +这样做时,他的身体承受了 2400 伏的电压-熊宝宝被一根埋在地下的带电电线杀死。 他幸免于难,但在事件中失去了手臂。 + +2013 年 9 月,Advanced Arm Dynamics 为 Garcia 配备了 Touch Bionics 设计的仿生手。 仿生手由加西亚的前臂肌肉控制,可以用 25 种不同的方式抓握。 用他的新手,加西亚可以执行通常需要非常敏捷的任务。 他的新手仍然有一些局限性。 例如,加西亚(Garcia)无法举重。 但是,他现在可以执行某些以前无法执行的操作。 例如,他可以从烤箱中拿出东西,而不会被灼伤,并且不可能割伤手指。 + +相反,机器人可以代替厨房中的人类来代替人类,而不是。 一个例子就是机器人厨房 Moley。 Moley 目前尚未投入生产,但 Moley 机器人厨房的最先进原型由两个机械臂组成,这些机械臂的手配有触觉传感器,炉灶,烤箱,洗碗机和触摸屏单元。 这些人造手可以举起,抓住并与大多数厨房设备互动,包括刀具,打蛋器,汤匙和搅拌器。 + +使用 3D 相机和手套,它可以记录人类厨师做饭的情况,然后将详细的步骤和说明上载到存储库中。 然后使用手势识别模型将厨师的动作转换为机器人动作。 这些模型是与斯坦福大学和卡内基梅隆大学合作创建的。 之后,Moley 可以重复相同的步骤,并从头开始烹饪完全相同的餐点。 + +在当前的原型中,用户可以使用触摸屏或智能手机应用程序对其进行操作,并预先准备好配料并将其放置在预设位置。 该公司的长期目标是允许用户从 2000 多种食谱中轻松选择一个选项,Moley 会在几分钟内准备好餐点。 + +# 游戏 + +可能没有例子比在游戏领域取得的进步更好的例子来证明人工智能的惊人发展。 人类具有天生的竞争能力,在我们自己的游戏中让机器击败我们是衡量该领域突破的有趣标准。 长期以来,计算机一直能够在一些更基础,更确定性,计算量更少的游戏(例如说跳棋)中击败我们。 直到最近几年,机器才能够始终击败某些较难游戏的主人。 在本节中,我们将介绍其中的三个示例。 + +**星际争霸 2** + +电子游戏被用作测试 AI 系统性能的基准已有数十年。 随着功能的增强,研究人员可以使用需要不同类型智能的更为复杂的游戏。 从此游戏中开发出来的策略和技术可以转移到解决实际问题上。 《星际争霸 2》的游戏被认为是最难的游戏之一,尽管按照视频游戏标准它是古老的游戏。 + +DeepMind 的团队引入了一个名为 AlphaStar 的程序,该程序可以玩《星际争霸 II》,并且首次击败了顶级职业玩家。 在 2018 年 12 月举行的比赛中,AlphaStar 击败了由 Grzegorz“ MaNa” Komincz 组建的团队,Grzegorz“ MaNa” Komincz 是全球最强的星际争霸职业球员之一,得分为 5-0。 游戏是在专业比赛条件下进行的,没有任何游戏限制。 + +与以前使用 AI 限制游戏的尝试相反,AlphaStar 可以不受限制地玩完整游戏。 它使用了深度神经网络,可以通过监督学习和强化学习直接从原始游戏数据中进行训练。 + +使《星际争霸 2》如此困难的一件事是需要平衡短期和长期目标并适应意外情况。 这通常对先前的系统提出了巨大的挑战。 + +尽管 StarCraft 只是一个游戏,尽管很困难,但是 AlphaStar 提出的概念和技术对于解决其他现实世界中的挑战很有用。 例如,AlphaStar 的体系结构能够根据不完善的信息来对很长的可能动作序列进行建模-游戏通常持续数小时,并进行成千上万次动作。 在许多实际问题中,可以找到对长序列进行复杂预测的主要概念,例如: + +* 天气预报 +* 气候模拟 +* 自然语言理解 + +AlphaStar 在星际争霸中表现出的成功代表着现有最困难的视频游戏之一的重大科学突破。 这些突破代表了人工智能系统的巨大飞跃,该系统可以转让并可以帮助解决基本的现实世界中的实际问题。 + +**危险** + +IBM 和 Watson 团队在 2011 年创造了历史,当时他们设计了系统,该系统能够击败两个最成功的 Jeopardy 冠军。 + +肯·詹宁斯(Ken Jennings)连续 74 次出场,是该演出历史上最长的不败战绩。 布拉德·拉特(Brad Rutter)赢得了最大的奖金,奖金总额为 325 万美元。 + +两位球员都同意与沃森进行一场比赛。 + +Watson 是一个问答系统,可以回答以自然语言提出的问题。 它最初由主要研究人员 David Ferrucci 领导的 IBM DeepQA 研究团队创建。 + +Watson 使用的问题解答技术与常规搜索(例如 Google 搜索)之间的主要区别在于,常规搜索将关键字作为输入,并根据与查询的相关性对文档列表进行排名。 像 Watson 所使用的一样,问答技术采用自然语言表达一个问题,试图更深入地理解该问题,并试图为该问题提供准确的答案。 + +Watson 的软件架构使用: + +* IBM 的 DeepQA 软件 +* Apache UIMA(非结构化信息管理架构) +* 多种语言,包括 Java,C ++和 Prolog +* SUSE Linux Enterprise Server +* 适用于分布式计算的 Apache Hadoop + +**国际象棋** + +我们许多人都记得 1996 年 Deep Blue 击败国际象棋大师 Gary Kasparov 时的新闻。Deep Blue 是 IBM 创建的国际象棋应用程序。 + +在第一轮比赛中,深蓝赢得了对阵加里·卡斯帕罗夫的第一场比赛。 但是,他们原定要打六场比赛。 卡斯帕罗夫(Kasparov)在接下来的五场比赛中赢得了三场胜利,并赢得了其中的两场,从而以 4–2 的比分击败了深蓝。 + +Deep Blue 团队回到进行了制图,对该软件进行了很多增强,并在 1997 年再次使用了 Kasparov。Deep Blue 在第二轮击败 Kasparov 的比赛中赢得了第二轮比赛,以 3½的比分赢得了六局比赛 2½。 然后,它成为第一个在标准国际象棋锦标赛规则和时间控制下击败比赛的世界冠军的计算机系统。 + +一个鲜为人知的例子是 AlphaZero 团队在国际象棋领域的成就,这是一种机器击败人类的迹象。 + +Google 的 AlphaZero 研究团队的科学家在 2017 年创建了一个系统,该系统只花了四个小时就学会了象棋规则,然后粉碎了当时最先进的世界冠军象棋程序 *Stockfish* 。 到现在为止,有关计算机还是人类在国际象棋方面更胜一筹的问题已得到解决。 + +让我们暂停片刻,然后考虑一下。 该系统超越了人类对古代象棋的所有知识,如果该系统在早晨开始学习,那么它将在午餐时间完成。 + +该系统已获得国际象棋规则,但未获得任何策略或进一步的知识。 然后,在几个小时内,AlphaZero 达到了足以击败 Stockfish 的程度。 + +在与 Stockfish 的一系列 100 场比赛中,AlphaZero 以白色打赢了 25 场比赛(白色具有优势,因为它排名第一)。 它还赢得了三场黑色比赛。 其余的比赛是平局。 Stockfish 没有获得任何胜利。 + +**AlphaGo** + +象象棋一样困难,的难度与古代 Go 棋相比没有。 + +不仅(19 x 19)棋盘位置比可见宇宙中的原子还多,而且可能的国际象棋棋盘位置与棋盘位置的数量可以忽略不计。 但是围棋至少比象棋游戏复杂几个数量级,因为围棋的发展有很多可能的方式,使每种棋子都朝着另一条发展路线发展。 使用围棋,单块石头可以影响和影响整个棋盘状况的移动次数也比带棋的单块移动大很多数量级。 + +有一个强大的程序示例,它可以玩由 DeepMind 开发的名为 AlphaGo 的 Go 游戏。 AlphaGo 还有三个功能更强大的继任者,分别称为 AlphaGo Master,AlphaGo Zero 和 AlphaZero。 + +2015 年 10 月,原始的 AlphaGo 成为第一个在全尺寸 19 x 19 板上击败无障碍职业人类围棋选手的计算机围棋程序。 2016 年 3 月,它在五场比赛中击败了李·塞多尔。 这成为围棋程序第一次击败没有障碍的 9 杆专业运动员。 尽管 AlphaGo 在第四局中输给 Lee Sedol,但 Lee 在最后一场比赛中辞职,最终比分是 4 比 1。 + +在 2017 年未来围棋峰会上,AlphaGo 的继任者 AlphaGo Master 在三场比赛中击败了大师 Kejie。 柯洁当时被评为世界排名第一的球员。 此后,AlphaGo 被中国围棋协会授予专业 9 丹。 + +AlphaGo 及其继任者使用蒙特卡洛树搜索算法,基于先前通过机器学习“学习”的知识来找到他们的动作,特别是使用深度学习和训练,既可以与人类玩耍,也可以与人类玩耍。 该模型经过训练可以预测 AlphaGo 自己的举动以及获胜者的游戏。 这种神经网络提高了树形搜索的强度,从而在后续游戏中提供了更好的动作和更强的玩法。 + +# 电影制作 + +几乎可以肯定的是在接下来的几十年内可以制作出 100%计算机生成的电影。 构想一个系统,其中输入是一个书面剧本,而输出是一个完整的故事片,这是不容置疑的。 此外,天然发电机已经取得了一些进步。 因此,最终甚至不需要脚本。 让我们进一步探讨。 + +**仿冒品** + +Deepfake 是*术语*或“深度学习”和“ fake”的混合。 这是一种合并视频图像的 AI 技术。 常见的应用是将某人的脸重叠到另一张上。 它的一个邪恶版本被用来与著名人物合并色情场景或创建复仇色情片。 Deepfake 还可以用于创建虚假新闻或恶作剧。 可以想象,如果滥用该技术,将会对社会产生严重影响。 + +中国一家名为 Momo 的公司开发了类似软件的最新版本,后者开发了一个名为 *Zao* 的应用程序。 它使您可以将某人的脸重叠在诸如 Titanic 之类的短片上,效果令人印象深刻。 此应用程序和其他类似应用程序毫无争议。 隐私团体抱怨说,按照用户协议条款提交给网站的照片将成为 Momo 的财产,然后可用于其他应用程序。 + +有趣的是,技术在该领域将如何继续发展。 + +**电影脚本生成** + +他们不会很快获得的奥斯卡金像奖,但是有两个项目致力于制作电影剧本。 最著名的例子之一是 Sunspring。 + +《 Sunspring》是一部实验科幻短片,于 2016 年发行。它完全是使用深度学习技术编写的。 这部电影的剧本是使用名为本杰明(Benjamin)的**长短期记忆**( **LSTM** )模型创建的。 它的创作者是由 BAFTA 提名的电影人奥斯卡·夏普(Oscar Sharp)和纽约大学 AI 研究人员罗斯·古德温(Ross Goodwin)。 电影中的演员是托马斯·米德迪奇,伊丽莎白·格雷和汉弗莱·克尔。 它们的字符名称分别是 H,H2 和 C,它们将保留在未来。 他们最终相互联系,形成了三角恋。 + +最初在科幻-伦敦电影节的 48 小时挑战赛上放映,并于 2016 年 6 月由技术新闻网站 Ars Technica 在线发布。 + +# 承销和交易分析 + +什么是承保? 简而言之,承保是过程,机构通过该过程确定机构是否要承担财务风险以换取保费。 需要承保的交易示例如下: + +* 签发保险单 + * 健康 + * 生活 + * 家 + * 驾驶 +* 贷款额 + * 分期贷款 + * 信用卡 + * 按揭 + * 商业信贷 +* 证券承销和首次公开募股(IPO) + +可以预见,确定是否应该签发保险单或贷款,如果做出错误的决定,以什么价格会付出很高的代价。 例如,如果一家银行发行了一笔贷款,但贷款违约,则将需要数十笔其他表现良好的贷款来弥补这一损失。 相反,如果银行在借款人将要偿还全部贷款的情况下放弃贷款,这也将损害银行的财务状况。 因此,银行花费大量时间分析或“承销”贷款,以确定借款人的信用价值以及担保该贷款的抵押品的价值。 + +即使进行了所有这些检查,承销商仍然会出错并发行拖欠或绕过应得的借款人的贷款。 当前的承保过程遵循必须满足的一组标准,但是特别是对于较小的银行,在此过程中仍然存在一定程度的人为主观性。 这不一定是一件坏事。 让我们来看一个场景,以进一步探索它: + +*最近,一位高净值人士从环球旅行中回来。 三个月前,他们在一家著名的医疗机构找到了一份工作,其信用评分超过 800。* + +你会借钱给这个人吗? 有了这些特征,它们似乎是一个很好的信用风险。 但是,正常的承保规则可能会使他们失去资格,因为最近两年没有使用这些规则。 手动承保会审视整个情况,并可能会批准它们。 + +同样,机器学习模型可能能够将其标记为有价值的帐户并发放贷款。 机器学习模型没有严格的规则,而是“通过示例学习”。 + +许多贷方已经在其承销中使用机器学习。 Zest Finance 是一家专门从事这一领域的公司的有趣示例。 Zest Finance 使用 AI 技术来协助贷方进行承销。 人工智能可以帮助增加收入并降低风险。 最重要的是,一般而言应用良好的 AI,尤其是 Zest Finance 可以帮助公司确保所使用的 AI 模型符合某个国家/地区的法规。 一些 AI 模型可能是一个“黑匣子”,很难解释为什么一个借款人被拒绝而另一个借款人被接受。 Zest Finance 可以全面解释数据建模结果,衡量业务影响并遵守法规要求。 Zest Finance 的秘密武器之一是使用非传统数据,包括贷方可能拥有的内部数据,例如: + +* 客户支持数据 +* 付款历史 +* 购买交易 + +他们还可能考虑非传统信用变量,例如: + +* 客户填写表格的方式 +* 客户用来到达站点的方法或他们导航站点的方式 +* 填写申请所需的时间 + +# 数据清理和转换 + +正如汽油为汽车提供动力一样,数据是 AI 的命脉。 古老的格言“垃圾进,垃圾出”仍然令人痛苦。 因此,拥有干净准确的数据对于生成一致,可重现和准确的 AI 模型至关重要。 其中一些数据清理需要艰苦的人类参与。 通过某种方法,据说数据科学家花费了大约 80%的时间来清理,准备和转换输入数据,而花费了 20%的时间运行并优化模型。 例如 ImageNet 和 MS-COCO 图像数据集。 两者都包含超过一百万个带有标签的各种对象和类别的图像。 这些数据集用于训练模型,该模型可以区分不同类别和对象类型。 最初,这些数据集是由人类艰苦而耐心地标记的。 随着这些系统的普及,我们可以使用 AI 进行标记。 此外,还有许多支持 AI 的工具可帮助进行清理和重复数据删除过程。 + +亚马逊湖形成就是一个很好的例子。 在 2019 年 8 月,亚马逊正式提供了 Lake Lake 的服务。 Amazon Lake Formation 自动执行创建数据湖通常涉及的一些步骤,包括数据的收集,清理,重复数据删除,编目和发布。 然后可以使数据可用于分析和构建机器模型。 要使用“湖泊形成”,用户可以使用预定义的模板将数据从一系列来源中引入湖泊。 然后,他们可以根据组织中各组所需的访问级别来定义用于控制数据访问的策略。 + +数据经过一些自动的准备,清理和分类,它们使用机器学习来自动执行这些任务。 + +Lake Formation 还提供了集中式仪表板,管理员可以在其中管理和监视跨多个分析引擎的数据访问策略,治理和审计。 用户还可以在结果目录中搜索数据集。 随着该工具在未来几个月和几年中的发展,它将使用他们最喜欢的分析和机器学习服务促进数据分析,其中包括: + +* 数据块 +* 板 +* 亚马逊 Redshift +* 亚马逊雅典娜 +* AWS 胶水 +* 亚马逊电子病历 +* 亚马逊 QuickSight +* 亚马逊 SageMaker + +# 摘要 + +本章提供了一些 AI 应用的示例。 也就是说,这里的内容并没有开始刮擦表面! 我们试图将用例保留在广泛使用的技术上,或者至少将在不久之后变得可用的技术上。 不难推断该技术将如何继续改进,变得更便宜并得到更广泛的应用。 例如,当自动驾驶汽车开始流行时,这将非常令人兴奋。 + +但是,我们可以肯定的是,甚至还没有构思出 AI 的更大应用。 而且,人工智能的进步将对我们的社会产生广泛的影响,并且在某些时候,我们将不得不处理以下问题: + +* 如果 AI 如此进化以至于变得有意识,会发生什么? 应该给它权利吗? +* 如果用机器人代替人类,是否应该要求公司继续为该流离失所者支付工资税? +* 我们是否可以做到计算机无所不能,如果这样做,我们将如何适应? 我们将如何度过时间? +* 更糟糕的是,该技术是否使少数人能够控制所有资源? 会否出现一个个人可以追求自己的利益的普遍收入社会? 还是流离失所的人民生活在贫困中? + +比尔·盖茨和埃隆·马斯克警告说,人工智能要么疯狂地追求自己的目标就摧毁地球,要么是偶然地(或偶然地)破坏了人类。 我们将对 AI 的影响持更为乐观的“半满”看法,但是可以肯定的是,这将是一段有趣的旅程。 + +# 参考 + +1. 艾米丽(Emily)威灵厄姆(Willingham),*一台机器在诊断患病儿童方面获得很高的评价*,《科学美国人》,10 月 7 日。,2019, [https://www.scientificamerican.com/article/ 一台机器获得高分以诊断生病的孩子/](https://www.scientificamerican.com/article/a-machine-gets-high-marks-for-diagnosing-sick-children/ ) +2. 克拉克·杰克(Clark,Jack), *Google 将其获利的网络搜索转向了 AI 机器*,彭博社,10 月 26 日 th ,2015 年, [https://www.bloomberg.com/news/ articles / 2015-10-26 / google-turning-its-lucrative-web-search-to-ai-machines](https://www.bloomberg.com/news/articles/2015-10-26/google-turning-its-lucrative-web-search-over-to-ai-machines) + +1. [https://www.michaelcaines.com/michael-caines/about-michael/](https://www.michaelcaines.com/michael-caines/about-michael/) \ No newline at end of file diff --git a/docs/ai-py/03.md b/docs/ai-py/03.md new file mode 100644 index 0000000000000000000000000000000000000000..eb1173652646957b13849b27907ea36f638e34ab --- /dev/null +++ b/docs/ai-py/03.md @@ -0,0 +1,359 @@ +# 3 + +# 机器学习管道 + +模型训练只是机器学习过程中的一小部分。 数据科学家经常花费大量时间来清理,转换和准备数据,以使其准备好供机器学习模型使用。 由于数据准备是一项非常耗时的活动,因此,我们将介绍最先进的技术来促进该活动以及其他组成良好设计的生产机器学习管道的组件。 + +在本章中,我们将涵盖以下关键主题: + +* 机器学习管道到底是什么? +* 生产质量的机器学习管道的组成部分是什么? +* 部署机器学习模型时的最佳实践是什么? +* 机器学习管道到位后,我们如何缩短部署周期? + +# 什么是机器学习管道? + +许多开始进行机器学习训练的年轻数据科学家都希望立即跳入模型构建和模型调整阶段。 他们没有意识到,创建成功的机器学习系统所涉及的不仅仅是在随机森林模型和支持向量机模型之间进行选择。 + +从选择适当的提取机制到数据清理再到特征工程,机器学习管道中的初始步骤与模型选择一样重要。 出色的结果和平庸的结果之间的区别还在于,能够正确地测量和监视模型在生产中的性能,并决定何时以及如何重新训练模型。 随着世界的变化,您的输入变量也会变化,并且您的模型也必须随之变化。 + +随着数据科学的发展,期望值越来越高。 数据源变得更加多样化,数量庞大(就大小而言)并且数量众多(就数量而言),并且管道和工作流变得更加复杂。 我们期望越来越多的数据本质上是实时的,这无济于事。 考虑一下网络日志,点击数据,电子商务交易和自动驾驶汽车输入。 这些系统中的数据来得太快了,我们必须拥有能够比接收到的信息更快处理信息的方法。 + +存在许多机器学习解决方案来实现这些管道。 当然,仅使用 Python 或 R 语言就可以建立基本的机器学习管道。 我们将通过布局使用 Python 的管道示例来开始建立我们的理解。 在本章中,我们将详细探讨一些利用当今最流行工具的体系结构。 数据管道通常利用的一些工具是: + +* Hadoop 的 +* 火花 +* 火花流 +* 卡夫卡 +* 蔚蓝 +* AWS +* Google Cloud Platform +* [R +* SAS +* 数据块 +* 蟒蛇 + +正如我们将看到的,其中一些更适合管道的某些阶段。 让我们快速概述一下设置机器学习管道所需的最少步骤。 + +要考虑的一个重要事项是,管道中的每个步骤都会产生输出,该输出将成为管道中下一步的输入。 术语*管道*有点误导,因为它暗示了单向数据流。 实际上,机器学习管道可以是循环的和迭代的。 可能会重复管道中的每个步骤,以获得更好的结果或更干净的数据。 最后,下一次执行管道循环时,可以将输出变量用作输入。 + +机器学习管道的主要步骤是: + +1. **问题定义**:定义业务问题。 +2. **数据摄取**:识别并收集数据集。 +3. **数据准备**:使用以下技术处理和准备数据: + * 估算缺失值 + * 删除重复的记录 + * 标准化值(更改数据集中的数值以使用通用刻度) + * 执行另一种类型的清理或映射 + * 完整的特征提取 + * 消除相关特征 + * 执行特征工程 +4. **数据隔离**:将数据拆分为训练集,验证集和测试集。 +5. **模型训练**:针对训练数据集训练机器模型。 这是数据科学的核心。 在本章中,我们将仅涉及此步骤的表面以及后续步骤。 本书中还有其他章节将更详细地介绍模型训练。 在这里列出它主要是为了使读者完整了解整个管道。 +6. **候选模型评估**:使用数据的测试和验证子集来测量模型的性能,以确定模型的准确性。 +7. **模型部署**:一旦选择了模型,请将其部署到生产中以进行推断。 + +1. **性能监视**:连续监视模型性能,相应地进行重新训练和校准。 收集新数据以继续改进模型并防止其过时: + +![](img/B15441_03_01.png) + +图 1:机器学习管道 + +让我们进一步探索并深入研究管道的组成部分。 + +# 问题定义 + +在设置管道时,此可能是最关键的步骤。 在此花费的时间可以在管道的后期阶段为您节省大量的时间。 这可能意味着技术突破或失败之间的差异,也可能意味着创业公司成功或破产之间的差异。 提出和提出正确的问题至关重要。 考虑以下警告故事: + +> “鲍勃花了数年时间计划,执行和优化如何征服山丘。不幸的是,结果证明那是错误的山丘。” + +例如,假设您要创建一个管道来确定贷款违约预测。 您最初的问题可能是: + +*对于给定的贷款,它是否违约?* + +现在,这个问题不区分贷款第一个月或贷款二十年的违约。 显然,与发行终止后的贷款相比,在发行时出现违约的贷款的利润要低得多。因此,一个更好的问题可能是: + +*贷款何时会违约?* + +这是一个更有价值的问题。 我们可以做得更好吗? 有时借款人可能每月不会发送全额应付款。 有时,借款人可能会零星发送款项。 为了解决这个问题,我们可能会进一步完善问题: + +*给定的贷款将获得多少钱?* + +让我们进一步改善它。 今天的一美元比将来的一美元更值。 因此,财务分析师使用公式来计算货币的现值。 关于借款人偿还多少贷款同样重要的是他们何时偿还贷款的问题。 另外,您还有预付款的问题。 如果借款人预付了一笔贷款,这将使该笔贷款的利润减少,因为它将收取的利息减少。 让我们再次更改问题: + +*一笔给定贷款的利润是多少?* + +我们完成问题了吗? 也许。 让我们再考虑一件事。 根据法律,某些输入变量不允许用于确定默认利率。 例如,种族和性别是不能用来确定贷款资格的两个因素。 再尝试一次: + +*如果不使用不允许的输入功能,给定贷款的利润将是多少?* + +我们将其留给读者以进一步完善问题。 如您所见,机器学习管道中的第一步和关键步骤需要考虑很多问题。 + +# 数据提取 + +一旦达到您满意的程度,您就可以精心制作和完善问题了,现在该收集原始数据以帮助您回答问题了。 这并不意味着一旦继续进行下一步,就无法更改您的问题。 您应该不断完善问题陈述并根据需要进行调整。 + +为管道收集正确的数据可能是一项艰巨的任务。 根据您要解决的问题,获取相关数据集可能非常困难。 + +另一个重要的考虑因素是决定如何将数据来源,摄取和存储: + +* 我们应该使用什么数据提供者或供应商? 他们可以信任吗? +* 如何摄取? Hadoop,Impala,Spark,仅 Python 等? +* 应该将其存储为文件还是数据库中? +* 什么类型的数据库? 传统的 RDBMS,NoSQL,图。 +* 它甚至应该存储吗? 如果我们有实时输入管道,甚至可能没有必要或没有效率地存储输入。 +* 输入应采用什么格式? 实木复合地板,JSON,CSV。 + +很多时候,我们甚至可能无法控制输入源来决定应采用的格式,我们应按原样进行处理,然后决定如何进行转换。 此外,我们可能没有唯一的数据源。 在我们可以将它们输入模型之前,可能需要合并,合并和合并多个源。 + +尽管我们长期以来都希望人工智能和能够取代人类智能,但是确定输入数据集中应包含哪些变量仍然需要人类智能,甚至可能有些古老的人类直觉。 + +如果您要预测股价,那么前一天的股价似乎是显而易见的输入。 其他输入(例如利率,公司收益,新闻标题等)可能不太明显。 + +对于餐厅的日常销售,前一天的销售可能也很重要。 其他可能包括:星期几,节假日或不节假日,下雨或不下雨,每天的人流量等等。 + +对于象棋和围棋这样的游戏系统,我们可能会提供以前的游戏或成功的策略。 例如,人类学习国际象棋的最佳方法之一是学习大师级玩家过去成功使用的开局和技巧以及观看过去锦标赛中完成的游戏。 通过使用此先前的知识和历史来决定将来的游戏方式,计算机可以以相同的方式学习。 + +到目前为止,选择相关的输入变量并建立成功的模型仍然需要数据科学家具有领域知识。 并在某些情况下具有深厚的领域知识。 让我们进一步探讨一个例子。 + +继续以贷款违约为例,让我们考虑一些最重要的功能,以便做出准确的预测。 这是该列表的第一个选项。 由于篇幅所限,我们将不列出所有通常使用的功能。 我们将从数据中学到的内容添加和删除项目: + + +| **功能名称** | **功能描述** | **为什么有用?** | +| 欠款帐户 | 借款人现在拖欠的帐户数。 | 如果借款人在支付账单方面遇到困难,他们可能会在支付新贷款方面遇到困难。 | +| 贸易账户 | 在过去 24 个月内开设的交易数量。 | 如果数量太少,这只是一个问题。 | +| 借款人地址 | 借款人在贷款申请中提供的地址。 | 放下 地址是唯一的。 唯一变量不提供预测能力。 | +| 邮政编码 | 借款人在贷款申请中提供的邮政编码。 | 这不是唯一的,可以具有预测能力。 | +| 年收入 | 借款人在注册期间提供的自报年收入。 | 更多的收入使借款人可以更轻松地处理更大的付款。 | +| 当前余额 | 所有帐户的平均当前余额。 | 孤立地没有价值。 需要相对。 | +| 冲销 | 12 个月内的注销次数。 | 指示借款人以前的违约行为。 | +| 逾期金额 | 借款人现在拖欠其帐户的逾期金额。 | 指示借款人以前的违约行为。 | +| 最早的帐户 | 自最早的循环帐户开设以来的月数。 | 表示借款人借钱的经历。 | +| 就业时间 | 就业年限。 | 表示借款人的稳定性。 | +| 贷款额度 | 在该时间点对该贷款的承诺总额。 | 孤立地没有价值。 需要相对。 | +| 查询数量 | 个人理财查询数量。 | 借款人寻找信用。 | +| 利率 | 贷款利率。 | 如果贷款的利率很高,则还款额将更多,并且可能难以偿还。 | +| 最大余额 | 所有循环帐户上的最大当前余额。 | 如果接近 100%,则可能表明借款人有财务困难。 | +| 自上次公开记录以来的月数 | 自上次公开记录以来的月数。 | 指示先前的财务困难 | +| 逾期帐户数 | 逾期 120 天或以上的帐户数 | 当前财务困难的指示 | +| 公开记录 | 贬损的公共记录数 | 指示先前的财务困难 | +| 术语 | 每月偿还贷款的次数。 | 贷款时间越长,潜在的违约可能性就越大。 | +| 当前总余额 | 所有帐户的当前总余额 | 孤立地没有价值。 需要相对。 | + +正如我们所看到的,其中某些变量本身并没有提供含义,需要将它们组合起来以进行预测。 这将是特征工程的一个例子。 新变量的两个示例是: + + +| 信贷运用 | 在所有交易中将平衡到信用额度。 当前余额与信用额度的比较。 | 较高的百分比表示借款人“已用光”,并且在获得新的信贷方面遇到困难。 | +| 债务到收入 | 使用每月总债务支付的总债务(不包括抵押和请求的贷款)除以借款人的自我报告的每月收入来计算。 | 较低的债务对收入比率表明借款人有足够的资源来偿还其债务,并且不应该有满足这些债务的问题。 | + +# 数据准备 + +下一步是处理原始数据的数据转换层; 需要完成的一些转换是: + +* 数据清理 +* 过滤 +* 聚合 +* 增加 +* 合并 +* 存储 + +云提供商已成为主要的数据科学平台。 一些最受欢迎的堆栈是围绕以下构建的: + +* Azure ML 服务 +* AWS SageMaker +* GCP Cloud ML 引擎 +* SAS +* RapidMiner +* 尼姆 + +执行这些转换的最流行的工具之一是 Apache Spark,但它仍然需要数据存储。 对于持久性,最常见的解决方案是: + +* Hadoop 分布式文件系统(HDFS) +* HBase 的 +* 阿帕奇·卡桑德拉(Apache Cassandra) +* 亚马逊 S3 +* Azure Blob 存储 + +还可以在数据库内部就地处理数据以进行机器学习。 SQL Server 和 SQL Azure 等数据库正在添加特定的机器学习功能,以支持机器学习管道。 Spark 具有 Spark Streaming 内置的功能。 它可以从 HDFS,Kafka 和其他来源读取数据。 + +还有其他替代方法,例如 Apache Storm 和 Apache Heron。 不管管道中有什么其他内容,通常都在交互式 Jupyter 笔记本电脑或 R Studio 中完成数据的初始探索。 + +某些实时数据处理解决方案提供了容错,可伸缩,低延迟的数据提取。 一些最喜欢的是: + +* 阿帕奇·卡夫卡 +* Azure 事件中心 +* AWS Kinesis + +现在,让我们探讨数据准备的关键操作之一-数据清理。 我们需要确保数据是干净的。 数据将很可能不是完美的,并且数据质量将不会达到最佳。 数据可能由于以下原因而不合适: + +## 缺少值 + +我们的数据经常包含缺失值,或者缺失值被零或 N / A 代替。 我们该如何处理这个问题? 以下是处理缺失值的六种不同方法: + +* **不执行任何操作**:有时最好的操作是不执行任何操作。 根据所使用的算法,并非总是需要对缺失值进行任何操作。 XGBoost 是可以优雅处理缺失值的算法示例。 +* **使用中值进行插补**:当缺少值时,分配给丢失数据的合理值是该变量的所有其余非缺失值的中值。 此替代方案易于计算,并且快速,并且适用于小型数据集。 但是,它没有提供太多的准确性,也没有考虑与其他变量的相关性。 +* **使用最频繁的值或常量**进行插补:另一种选择是分配最频繁的值或常量(如零)。 这种方法的一个优势是它适用于非数值变量。 像以前的方法一样,它不考虑与其他变量的相关性,并且根据空值的频率,它可以在数据集中引入偏差。 + +## 重复的记录或值 + +如果两个值确实相同,则很容易创建可以查找重复值的查询或程序。 如果应该假设两个记录或值标识相同的实体,但是两个值之间存在细微差异,那么麻烦就开始了。 传统的重复数据库查询可能找不到拼写错误,缺少值,地址更改或遗漏中间名的人。 有些人使用别名。 + +直到最近,查找和修复重复记录一直是一个手动过程,该过程耗时且耗费资源。 但是,一些使用 AI 查找重复项的技术和研究开始兴起。 除非所有详细信息完全匹配,否则很难确定不同的记录是否引用同一实体。 另外,通常大多数重复项都是假阳性。 两个人可能具有相同的姓名,地址和出生日期,但仍然是不同的人。 + +识别重复项的解决方案是使用模糊匹配而不是精确匹配。 模糊匹配是一种计算机辅助技术,可对数据相似性进行评分。 它广泛用于执行模糊匹配。 讨论模糊匹配不在本书讨论范围之内,但对读者进一步研究该主题可能很有用。 + +## 功能缩放 + +数据集通常包含大小不等的特征。 要素大小的这种变化通常会对预测的准确性产生不利影响(但并非总是如此;例如,Random Forest 不需要要素缩放)。 许多机器学习算法使用数据点之间的欧几里得距离进行计算。 如果不进行此调整,则数量级高的要素将对结果产生过重的影响。 + +用于特征缩放的最常用的方法是: + +* 重新缩放(最小-最大归一化) +* 平均归一化 +* 标准化(Z 分数标准化) +* 缩放到单位长度 + +## 值不一致 + +数据可能经常包含不一致的值。 此外,数据可能以多种方式不一致。 数据不一致的一个示例是街道地址修饰符。 考虑以下数据点: + +* 第五大道 +* 第五大道 +* 第五大道 +* 第五大道 + +作为人类,我们可以快速确定所有这些示例确实具有相同的价值。 计算机很难得出这个结论。 + +解决此问题的两种方法是基于规则的和基于示例的。 当数据中的可变性较小且不会快速更改时,基于规则的系统会更好地工作。 当我们拥有快速移动的数据时,基于规则的方法就会中断。 + +考虑一个垃圾邮件过滤器。 我们可以创建一个规则,将带有“ Viagra”一词的任何内容标记为垃圾邮件,但是垃圾邮件发送者可能会变得很聪明,并开始更改数据以绕过该规则(“ Vi @ gra”)。 在这种情况下,基于机器学习示例的清洁器会更好地工作。 + +有时,我们可能想考虑混合方法并同时使用这两种方法。 例如,一个人的身高应始终为正值。 因此,我们可以为此编写一条规则。 对于具有更多可变性的其他值,我们可以使用机器学习方法。 + +## 日期格式不一致 + +* 11/1/2016 +* 11/01/2016 +* 11/1/16 +* 16 年 11 月 1 日 +* 2016 年 11 月 1 日 st + +这些都是相同的值。 因此,我们需要标准化日期。 + +这不是完整的数据准备清单,而是旨在让您了解需要进行的不同转换才能清理和准备数据以使其有用。 + +# 数据隔离 + +按照的顺序使用已处理的数据训练模型,建议将数据分为两个子集: + +* 训练数据 +* 测试数据 + +有时分为三个: + +* 训练数据 +* 验证数据 +* 测试数据 + +然后,您可以在训练数据上训练模型,以便以后对测试数据进行预测。 训练集对于模型是可见的,并根据此数据进行训练。 训练会创建一个推理引擎,以后可以将其应用于模型之前未看到的新数据点。 测试数据集(或子集)表示该看不见的数据,现在可以将其用于对该先前看不见的数据进行预测。 + +# 模型训练 + +一旦我们拆分了数据,现在就可以通过一系列模型来运行训练和测试数据,并评估各种模型的性能并确定每个候选模型的准确性。 这是一个反复的过程,可能需要测试各种算法,直到您拥有一个可以充分回答您问题的模型为止。 + +在后面的章节中,我们将深入研究此步骤。 本书的其余部分提供了大量的模型选择材料。 + +## 候选模型评估和选择 + +用训练模型后,各种算法又迈出了关键的一步。 现在是时候选择哪种模型最适合当前的问题。 我们并不总是选择性能最好的模型。 对训练数据执行良好的算法在生产中可能无法很好地执行,因为它可能过度拟合了训练数据。 在这个时间点上,模型选择更多的是一门艺术,而不是一门科学,但是有一些技术需要进一步探索以确定哪种模型最好。 + +## 模型部署 + +一旦选择了模型并确定了模型,即可将其用于进行预测。 它通常通过 API 公开,并作为分析解决方案的一部分嵌入到决策框架中。 + +如何公开和部署它应由业务需求决定。 选择部署时要考虑的一些问题: + +* 系统是否需要能够进行实时预测(如果是,预测速度如何:以毫秒,秒,分钟,小时为单位?) +* 需要多久更新一次模型? +* 预计会有多少数量或流量? +* 数据集的大小是多少? +* 是否有需要遵循和遵守的法规,政策和其他约束条件? + +确认要求之后,我们现在可以考虑用于模型部署的高级体系结构。 以下是多种选择。 无论如何,这并不是一个详尽的清单,但它确实包含了一些较流行的体系结构: + + +| | **RESTful API 架构** | **共享数据库体系结构** | **流架构** | **移动应用架构** | +| **训练方法** | 批量 | 批量 | 流媒体 | 流媒体 | +| **预测方法** | 即时的 | 批量 | 流媒体 | 即时的 | +| **结果交付** | 通过 RESTful API | 通过共享数据库 | 通过消息队列流式传输 | 通过移动设备上的进程内 API | +| **预测延迟** | 低 | 高 | 非常低 | 低 | +| **系统可维护性** | 中 | 简单 | 难 | 中 | + +如表中所总结,这四个选项各有利弊。 当我们深入研究体系结构的细节时,还需要考虑更多的考虑因素。 作为示例,可以使用模块化微服务架构或以整体方式来实现这些架构中的每一个。 同样,选择应该由业务需求决定。 例如,可以选择整体方法,因为我们的用例非常有限,需要极低的延迟。 + +无论为模型部署选择哪种体系结构,使用以下原则都是一个好主意: + +* **可再现性**:存储所有模型输入和输出,以及所有相关元数据,例如配置,依赖项,地理位置和时区。 需要解释过去的预测。 确保每个部署捆绑包的最新版本均可用,其中还应包含培训数据。 例如,这对于受到严格监管的领域尤其重要。 +* **自动化**:尽可能早地自动化训练和模型发布。 +* **可扩展性**:如果需要定期更新模型,则需要从头开始制定计划。 +* **模块化**:尽可能地对代码进行模块化,并确保已放置控件以忠实地再现跨环境的管道(DEV,QA,TEST)。 +* **测试**:分配时间表的重要部分以测试机器学习管道。 尽可能使自动化,并从一开始就将其集成到您的过程中。 探索**测试驱动开发**( **TDD** )和**行为驱动开发**( **BDD** )。 + +## 性能监控 + +一旦模型制作成,我们的工作就不会完成。 将模型投入生产可能并不容易,但是一旦部署了模型,就必须对其进行严格监控,以确保模型运行令人满意。 使模型投入生产需要涉及多个步骤。 对该模型进行连续监控,以观察其在现实世界中的行为并进行相应的校准。 收集新数据以逐步改进它。 同样,监视部署的机器学习模型需要从各个角度进行关注,以确保模型正在运行。 让我们分析一下在监视机器学习模型时需要考虑的这些不同指标,以及为什么每个指标都很重要: + +### 模型性能 + +数据科学环境中的性能并不意味着模型运行的速度如何,而是预测的准确性。 监视机器学习模型的数据科学家主要关注一个指标:漂移。 当数据不再是模型的相关输入或有用输入时,就会发生漂移。 数据可能会更改并失去其预测价值。 数据科学家和工程师必须不断监控模型,以确保模型特征继续与模型训练期间使用的数据点相似。 如果数据漂移,则预测结果将变得不太准确,因为输入功能已过时或不再相关。 例如,考虑一下股票市场数据。 30 年前,市场大为不同。 它与众不同的一些方式包括: + +* 证券交易所的交易量大大低于今天 +* 高频交易甚至不是一个主意 +* 被动指数基金不那么受欢迎 + +可以想象,这些特征使库存性能显着不同。 如果我们使用具有 30 年历史的数据来训练模型,则很可能无法使用当今的数据来执行模型。 + +### 营运表现 + +最终,机器学习管道仍然是软件系统。 因此,监视资源消耗仍然很重要,包括: + +* **CPU 利用率**:识别峰值以及是否可以解释峰值。 +* **内存使用情况**:正在消耗多少内存。 +* **磁盘使用率**:我们的应用程序消耗了多少磁盘空间。 +* **网络 I / O 流量**:如果我们的应用程序跨越实例,则衡量网络流量非常重要。 +* **延迟**:发生数据传输所花费的时间。 +* **吞吐量**:成功传输的数据量。 + +如果这些指标发生变化,则需要对其进行分析以了解为什么会发生这些变化。 + +### 总拥有成本(TCO) + +数据科学家需要根据每秒记录的数量来监视其模型的性能。 尽管这可以使您更深入地了解模型的效率,但公司还应将重点放在从模型中获得的收益与成本之间。 建议监视机器学习管道的所有步骤的成本。 如果密切跟踪此信息,则企业可以就如何降低成本以及如何把握新机遇,或者某些管道是否不能提供足够的价值而做出明智的决定,而需要进行更改或关闭 下。 + +### 服务表现 + +不在业务问题的上下文中的技术是没有用的。 企业经常与技术部门签订**服务级别协议**( **SLA** ),或至少应该拥有。 SLA 的示例: + +* 在一天内修复所有关键错误 +* 确保 API 在 100 毫秒内响应 +* 每小时至少处理一百万个预测 +* 复杂的模型必须在 3 个月内设计,开发和部署 + +为了使业务最佳运行,重要的是建立,监视和满足先前商定的 SLA。 + +机器学习模型对于企业而言可能至关重要。 确保它们不会成为瓶颈的关键是正确监视已部署的模型。 作为您的机器学习管道的一部分,请确保监视部署的机器学习模型并与 SLA 进行比较,以确保获得令人满意的业务成果。 + +# 摘要 + +本章详细列出了创建机器学习管道所涉及的不同步骤。 此游览应被视为所涉及步骤的初步概述。 随着本书的发展,您将学习如何改善自己的管道,但是我们确实学习了一些当今用于建立管道的最佳实践和最受欢迎的工具。 在审查中,成功管道的步骤如下: + +* 问题定义 +* 数据提取 +* 资料准备 +* 数据隔离 +* 候选模型选择 +* 模型部署 +* 性能监控 + +在下一章中,我们将更深入地研究机器学习管道的步骤之一。 我们将学习如何执行特征选择,并学习什么是特征工程。 这两种技术对于提高模型性能至关重要。 \ No newline at end of file diff --git a/docs/ai-py/04.md b/docs/ai-py/04.md new file mode 100644 index 0000000000000000000000000000000000000000..d6f79dc7bdc1f42d156af446c597d65b3d2d6a48 --- /dev/null +++ b/docs/ai-py/04.md @@ -0,0 +1,640 @@ +# 4 + +# 特征选择和特征工程 + +特征选择(也称为变量选择,属性选择或变量子集选择)是一种用于从初始数据集中选择特征子集(变量,尺寸)的方法。 特征选择是构建机器学习模型过程中的关键步骤,并且可能对模型的性能产生巨大影响。 使用正确且相关的特征作为模型的输入还可以减少过度拟合的机会,因为拥有更多相关的特征会减少模型使用不添加信号作为输入的嘈杂特征的机会。 最后,减少输入功能会减少训练模型所需的时间。 学习选择哪些功能是数据科学家开发的一项技能,通常仅来自数月和数年的经验,并且可以说是一门艺术,而不是一门科学。 功能选择很重要,因为它可以: + +* 缩短培训时间 +* 简化模型并使它们更易于解释 +* 通过减少过度拟合来增强测试集性能 + +删除特征的一个重要原因是输入变量之间的高度相关性和冗余性,或者某些特征的不相关性。 因此,可以删除这些输入变量,而不会造成很多信息丢失。 冗余和无关是两个截然不同的概念,因为一个相关特征在存在与之高度相关的另一个相关特征的情况下可能是多余的。 + +在某些方面,要素工程与要素选择相反。 使用功能选择,可以删除变量。 在特征工程中,您可以创建新变量来增强模型。 在许多情况下,您将使用领域知识进行增强。 + +特征选择和特征工程是机器学习管道中的重要组成部分,这就是为什么整章专门讨论该主题的原因。 + +到本章末,您将了解: + +* 如何决定是否应从数据集中删除要素 +* 了解共线性,相关性和因果关系的概念 +* 了解特征工程的概念及其与特征选择的区别 +* 了解手动特征工程和自动特征工程之间的区别。 什么时候适合使用每个? + +# 功能选择 + +在上一章的中,我们探讨了机器学习管道的组件。 管道的关键组成部分是确定哪些要素将用作模型的输入。 对于许多模型,一小部分输入变量提供了大部分的预测能力。 在大多数数据集中,由少数几个要素负责大部分信息信号是很常见的,而其余的要素则主要是噪声。 + +出于多种原因,降低输入功能的数量很重要,包括: + +* 减少输入特征的多重共线性将使机器学习模型参数更易于解释。 *多重共线性*(也*共线性*)是在数据集中的特征中观察到的现象,在该数据集中可以从另一个模型的特征中线性预测另一个预测器特征,并且准确性很高。 +* 减少运行模型所需的时间和模型所需的存储空间,将使我们能够运行模型的更多变体,从而带来更快更好的结果。 +* 模型需要的输入功能越少,说明它就越容易。 当特征数量增加时,模型的可解释性下降。 减少输入要素的数量还可以简化为较小尺寸(例如 2D 或 3D)时可视化数据的过程。 +* 随着维数的增加,可能的配置呈指数增加,而观测值覆盖的配置数则减少。 随着您具有描述目标的更多功能,您也许可以更精确地描述数据,但是您的模型将不会使用新的数据点进行泛化-您的模型将过度拟合数据。 这就是维度的*诅咒。* + +让我们通过一个例子直观地思考一下。 美国有一个房地产网站,允许房地产经纪人和房主列出出租房屋或出售房屋。 Zillow 因其 Zestimate 而闻名。 Zestimate 是使用机器学习的估计价格。 Zillow 估计,如果今天将其投放市场,房屋将要出售的价格。 Zestimates 会不断更新和重新计算。 Zillow 如何得出这个数字? 如果您想了解更多,可以在 Kaggle 上进行一场比赛,该比赛在 Zestimate 上有很多资源。 你可以在这里查询更多详情: + +[https://www.kaggle.com/c/zillow-prize-1](https://www.kaggle.com/c/zillow-prize-1 ) + +Zestimate 算法的确切细节是专有的,但是我们可以做一些假设。 现在,我们将开始探索如何提出自己的 Zestimate。 让我们为我们的机器学习模型列出潜在的输入变量,以及它们可能很有价值的原因: + +* **平方英尺**:直观上来说,房屋越大,价格就越高。 +* **卧室数量**:更多房间,更多费用。 +* **浴室数量**:卧室需要浴室。 +* **抵押贷款利率**:如果利率较低,则抵押贷款支付将降低,这意味着潜在的房主可以负担得起更昂贵的房屋。 +* **建成年份**:通常,较新的房屋比旧的房屋更贵。 老房子通常需要更多的维修。 +* **财产税**:如果财产税高,那将增加每月的还款额,房主将只能负担更便宜的房屋。 +* **房屋颜色**:乍看之下,这似乎并不是一个相关变量,但是如果房屋涂成石灰绿色怎么办? +* **邮政编码**:位置,位置,位置。 在房地产中,房屋所在地是价格的重要决定因素。 在某些情况下,一个街区的房屋可能比下一个街区的房屋多几十万美元。 位置可能很重要。 +* **可比较的销售额**:评估师和房地产经纪人通常用来评估房屋价值的一种指标是寻找与最近出售或至少列出的“标的”房地产相似的房地产 销售,以查看销售价格或当前的上市价格。 +* **税收评估**:财产税是根据县目前认为财产的价值来计算的。 这是可公开访问的信息。 + +这些都可能是具有较高预测能力的变量,但直觉上我们可以假设平方英尺,卧室数量和浴室数量高度相关。 同样,从直觉上讲,平方英尺比卧室或浴室的数量更精确。 因此,我们可以减少卧室和浴室的数量,并保持平方英尺,而不会损失太多精度。 确实,我们可以通过降低噪声来提高准确性。 + +此外,我们很可能会在不损失精度的情况下降低房屋的颜色。 + +在不影响模型精度的情况下可以删除的特征分为两类: + +* **冗余**:此功能与其他输入功能高度相关,因此不会在信号中添加太多新信息。 +* **不相关**:此功能与目标功能的相关性较低,因此提供的噪声大于信号。 + +找出我们的假设是否正确的一种方法是在有或没有我们的假设的情况下训练模型,并查看产生更好结果的方法。 我们可以对每个单个功能使用此方法,但是如果我们具有大量功能,则可能会迅速增加组合的数量。 + +正如我们之前提到的,探索性数据分析是获得直观了解和深入了解正在使用的数据集的好方法。 让我们分析通常用于获取这些见解的三种方法。 他们是: + +* 功能重要性 +* 单变量选择 +* 具有热图的相关矩阵 + +## 功能重要性 + +可以使用此方法确定数据集每个特征的重要性。 + +特征重要性为数据集中的每个特征提供分数。 较高的分数意味着该要素相对于输出要素具有更高的重要性或相关性。 + +功能重要性通常是*基于树的分类器*随附的内置类。 在以下示例中,我们使用 *Extra Tree Classifier* 确定数据集中的前五项功能: + +```py +import pandas as pd +``` + +```py +from sklearn.ensemble import ExtraTreesClassifier +import numpy as np +import matplotlib.pyplot as plt +``` + +```py +data = pd.read_csv("train.csv") +X = data.iloc[:,0:20] #independent columns +y = data.iloc[:,-1] # pick last column for the target feature +``` + +```py +model = ExtraTreesClassifier() +model.fit(X,y) +print(model.feature_importances_) #use inbuilt class +#feature_importances of tree based classifiers +#plot graph of feature importances for better visualization +feat_importances = pd.Series(model.feature_importances_, index=X.columns) +feat_importances.nlargest(5).plot(kind='barh') +plt.show() +``` + +您应该将视为输出: + +![](img/B15441_04_01.png) + +图 1:功能重要性图 + +## 单变量选择 + +可以使用统计测试来确定哪些特征与输出变量具有最强的相关性。 scikit-learn 库具有一个名为`SelectKBest`的类,该类提供一组统计测试以选择数据集中的 *K* “最佳”特征。 + +以下是一个示例,该示例对非负特征使用*卡方(chi* ²*)统计检验,以选择输入数据集中的五个最佳特征:* + +```py +import pandas as pd +import numpy as np +from sklearn.feature_selection import SelectKBest +from sklearn.feature_selection import chi2 +data = pd.read_csv("train.csv") +X = data.iloc[:,0:20] #independent columns +y = data.iloc[:,-1] #pick last column for the target feature +#apply SelectKBest class to extract top 5 best features +bestfeatures = SelectKBest(score_func=chi2, k=5) +fit = bestfeatures.fit(X,y) +dfscores = pd.DataFrame(fit.scores_) +``` + +```py +dfcolumns = pd.DataFrame(X.columns) +scores = pd.concat([dfcolumns,dfscores],axis=1) +scores.columns = ['specs','score'] +print(scores.nlargest(5,'score')) #print the 5 best features +``` + +并且您应该看到这样的输出: + +![](img/B15441_04_02.png) + +图 2:最佳功能图 + +## 相关热图 + +当特征的不同值之间存在关系时,两个特征之间存在相关性。 例如,如果房价随着平方英尺的增加而上涨,则这两个特征被认为是正相关的。 可能存在不同程度的相关性。 如果一个特征相对于另一个特征一致地改变,则这些特征被认为是高度相关的。 + +相关可以是正的(特征的一个值的增加会增加目标变量的值)或负的(特征的一个值的增加会降低目标变量的值)。 + +关联是-1 和 1 之间的连续值。 + +* 如果两个变量之间的相关性为 1,则存在完美的直接相关性。 +* 如果两个特征之间的相关性为-1,则存在理想的逆相关性。 +* 如果两个要素之间的相关性为 0,则两个要素之间没有相关性。 + +热图可轻松识别与目标变量最相关的功能。 我们将使用`seaborn`库并使用以下代码来绘制相关要素的热图: + +```py +import pandas as pd +import numpy as np +import seaborn as sns +import matplotlib.pyplot as plt +``` + +```py +data = pd.read_csv("train.csv") +X = data.iloc[:,0:20] #independent columns +y = data.iloc[:,-1] # pick last column for the target feature +#get the correlations of each feature in the dataset +correlation_matrix = data.corr() +top_corr_features = correlation_matrix.index +plt.figure(figsize=(20,20)) +#plot heat map +g=sns.heatmap(data[top_corr_features].corr(),annot=True,cmap="RdYlGn") +``` + +您应该获得与以下类似的输出: + +![](img/B15441_04_03.png) + +图 3:相关热图 + +存在更多形式化和较少的直观方法来自动选择特征。 存在许多这些方法,并且在 scikit-learn 软件包中实现了许多方法。 接下来提到对这些方法进行分类的一种方法。 + +### 基于包装的方法 + +使用包装器方法时,使用以下步骤可将特征选择的问题实质上减少为搜索问题: + +1. 功能子集用于训练模型 +2. 根据每次迭代的结果,将要素添加到子集中或从子集中删除 + +包装方法通常在计算上很昂贵。 以下是一些示例: + +* **正向选择**:正向选择方法是一个迭代过程,其开始时数据集中没有特征。 在每次迭代过程中,都会添加功能以改善模型的性能。 如果性能得到改善,则保留功能。 无法改善结果的功能将被丢弃。 该过程一直持续到模型停止改进为止。 +* **向后消除**:当使用向后消除方法时,所有特征最初都在数据集中出现。 重要性最低的要素将在每次迭代过程中删除,然后该过程将检查模型的性能是否得到改善。 重复该过程,直到没有观察到明显的改善为止。 +* **递归特征消除**:递归特征消除方法是一种贪婪的优化算法,其目标是找到性能最佳的特征子集。 它迭代创建模型,并在每次迭代期间存储性能最佳或最差的功能。 它将使用其余功能构造下一个模型,直到所有功能用尽。 然后根据特征的消除顺序对其进行排序。 + +### 基于过滤器的方法 + +指定度量,并基于该度量特征过滤。 基于过滤器的方法的示例包括: + +* **皮尔逊相关**:此算法用作量化两个连续变量 *X* 和 *Y* 之间的线性相关性的量度。 它的值可以介于-1 到+1 之间。 +* **线性判别分析(LDA)**:LDA 可用于查找特征的线性组合,这些特征描述或分隔了分类变量的两个或更多个级别(或类)。 +* **方差分析(ANOVA)**:ANOVA 类似于 LDA,不同之处在于它是使用一个或多个分类自变量和一个连续因变量计算的。 它提供了统计检验,以了解几组的平均值是否相等。 +* **卡方**:卡方是一种统计测试,应用于类别变量组,以使用它们的频率分布确定它们之间相关或关联的可能性。 + +要记住的一个问题是,基于*过滤器的方法*不会消除多重共线性。 因此,在根据输入数据创建模型之前,必须执行其他处理以处理特征多重共线性。 + +### 嵌入式方法 + +嵌入式方法使用具有内置特征选择方法的算法。 嵌入式方法结合了 filter 和 wrapper 方法的优点。 通常使用带有内置特征选择方法的算法来实现它们。 + +两种流行的嵌入式方法实现如下: + +* **套索回归**:执行 L1 正则化,这会增加与系数的绝对值相等的惩罚 +* **Ridge 回归**:执行 L2 正则化,这将增加与系数幅度的平方相等的惩罚 + +这些算法也很常用: + +* 模因算法 +* 随机多项式对数 +* 正则化树 + +到此结束本章的第一部分。 您应该准备好应对自己的功能选择项目。 现在,准备好进入特征工程领域。 特征选择与我们如何减少变量数量以提高准确性有关。 特征工程则相反。 它问:我们如何创建新变量以使我们的模型更具性能? + +# 功能工程 + +根据最近在《福布斯》杂志上进行的调查,数据科学家将其 80%的时间用于数据准备: + +![https://miro.medium.com/max/1200/0*-dn9U8gMVWjDahQV.jpg](img/B15441_04_04.png) + +图 4:数据科学家花费的时间细分(来源:《福布斯》) + +该统计数据突显了数据准备和特征工程在数据科学中的重要性。 + +就像明智的和系统化的特征选择可以通过删除特征使模型更快,更高效一样,特征工程可以通过添加新特征来实现相同的目的。 乍看之下这似乎是矛盾的,但是要添加的功能不是由功能选择过程删除的功能。 要添加的要素是可能未包含在初始数据集中的要素。 您可能拥有世界上最强大,设计最完善的机器学习算法,但是如果您的输入功能不相关,您将永远无法产生有用的结果。 让我们分析几个简单的例子以获得一些直觉。 + +在上一章中,我们探讨了贷款违约问题。 凭直觉,可以推测,如果借款人的工资高,借款人的违约率就会降低。 同样,我们可以假设,与余额较低的人相比,信用卡余额较大的借款人可能很难偿还这些余额。 + +现在我们有了新知识,让我们尝试直观地确定谁将偿还贷款,谁不偿还贷款。 如果借款人 *A* 的信用卡余额为$ 10,000,而借款人 *B* 的余额为$ 20,000,您认为谁有更大的机会还清债务? 如果没有其他信息,我们可以说借款人 *A* 是更安全的选择。 现在,如果我告诉您借款人 *A* 年收入为 20,000 美元,借款人 *B* 年收入为 100,000 美元,该怎么办? 那改变了一切,不是吗? 我们如何定量捕捉两个特征之间的关系? 银行经常使用所谓的,**债务与收入**( **DTI** )比率,该比率的计算方法如下: + +![](img/B15441_04_001.png) + +因此,借款人 A 的 DTI 为 0.50,借款人 *B* 的债务收入比为 0.20。 换句话说,借款人 *A* 的债务是其债务的两倍,借款人 *B* 的债务是其债务的 5 倍。 借款人 *B* 有更大的偿还债务空间。 我们当然可以在这些借款人的配置文件中添加其他功能,这将改变其配置文件的构成,但是希望这有助于说明功能工程的概念。 + +更正式地讲,特征工程是数据科学家或计算机生成可增强机器学习模型的预测能力的特征的过程。 特征工程是机器学习中的一个基本概念,可能既困难又昂贵。 许多数据科学家希望直接跳入模型选择,但是辨别哪些新功能将增强模型的能力是一项关键技能,可能需要花费数年才能掌握。 + +更好的特征工程算法的发展目前正在紧锣密鼓地进行,有一天,在特征工程决策方面,这些特征可能会比高级数据科学家更好,但是,在接下来的几年中,我们预测优秀的数据科学家 仍会需求。 + +特征工程过程可以描述如下: + +1. 集体讨论哪些功能相关 +2. 确定哪些功能可以改善模型性能 +3. 创建新功能 +4. 确定新功能是否会增加模型性能; 如果没有,放下它们 +5. 返回*步骤 1* ,直到模型的性能达到预期 + +正如我们在示例中看到的那样,拥有域知识并熟悉数据集对于特征工程很有用。 但是,也有一些通用的数据科学技术可应用于数据准备和功能工程步骤中,而不管其域是什么。 让我们花一些时间分析这些技术。 + +我们将探讨的技术是: + +* 归因 +* 离群值管理 +* 一键编码 +* 对数转换 +* 缩放比例 +* 日期操作 + +## 估算 + +数据集“肮脏”且不完美的情况并不少见。 缺少值的行是一个常见问题。 值丢失的原因可能有很多: + +* 数据集不一致 +* 笔误 +* 隐私问题 + +不管出于何种原因,缺少值都会影响模型的性能,并且在某些情况下,由于某些算法不会善待丢失值,因此可能会导致模型停止运行。 有多种技术可以处理缺失值。 它们包括: + +**删除缺少值的行** –此技术会降低模型的性能,因为它减少了模型必须训练的数据点的数量。 + +让我们看一个示例,该示例删除缺少 60%以上数据的列: + +```py +threshold = 0.6 +#Drop columns with a missing value rate higher than threshold +data = data[data.columns[data.isnull().mean() < threshold]] +``` + +```py +#Drop rows with missing value rate higher than threshold +data = data.loc[data.isnull().mean(axis=1) < threshold] +threshold = 0.6 +#Drop columns with a missing value rate higher than threshold +data = data[data.columns[data.isnull().mean() < threshold]] +``` + +```py +#Drop rows with missing value rate higher than threshold +data = data.loc[data.isnull().mean(axis=1) < threshold] +print(data) +``` + +输出应如下所示: + +![](img/B15441_04_05.png) + +图 5:丢弃缺失值输出 + +**数值插补** –插补是处理缺失值的另一种方法。 归因只是将缺失的值替换为另一个“有意义的”值。 + +对于数字变量,这些是常见的替换: + +* 将零用作替换值是一种选择 +* 计算整个数据集的平均值,然后用平均值替换缺失值 +* 计算整个数据集的平均值,然后用平均值替换缺失值 + +通常最好使用平均值而不是平均值,因为平均值更容易受到异常值的影响。 让我们看一些替换示例: + +```py +#Filling all missing values with 0 +data = data.fillna(0) +``` + +```py +#Filling missing values with medians of the columns +data = data.fillna(data.median()) +print(data) +``` + +您将能够滚动显示输出并查看更改的值: + +![](img/B15441_04_06.png) + +图 5:丢弃缺失值输出 + +**分类插补** –分类变量不包含数字,而是包含类别。 例如,红色,绿色和黄色。 或香蕉,苹果和橙子。 因此,平均值和均值不能与分类变量一起使用。 常用的技术是用出现最多的值替换所有丢失的值。 + +在存在许多类别或类别均匀分布的情况下,使用诸如“其他”之类的名称可能有意义。 让我们看一下 Python 中的示例,该示例将所有缺少的值替换为最常出现的值(Python 中的`idxmax`返回整个功能中最常见的值): + +```py +#Max fill function for categorical columns +import pandas as pd +``` + +```py +data = pd.read_csv("dataset.csv") +``` + +```py +data['color'].fillna(data['color'].value_counts().idxmax(), inplace=True) +print(data) +``` + +输出应类似于以下内容: + +![](img/B15441_04_07.png) + +图 7:填充缺失值输出 + +# 离群值管理 + +房价是一个很好的领域,需要进行分析以了解为什么我们需要特别注意异常值。 无论您居住在世界的哪个区域,您附近的大多数房屋都将落入一定范围内,并且将具有某些特征。 也许是这样的: + +* 1 至 4 间卧室 +* 1 个厨房 +* 500 至 3000 平方英尺 +* 1 至 3 间浴室 + +2019 年美国的平均房价为 226,800 美元。 您可以猜测,这种房屋可能会具有上述某些特征。 但也可能有几所房子是*异常值*。 也许有 10 或 20 间卧室的房子。 其中一些房屋可能价值一百万或一千万美元,具体取决于这些房屋可能具有的疯狂定制数量。 正如您可能想象的那样,这些离群值将影响数据集中的均值,并且将对均值产生更大的影响。 因此,鉴于这些房屋的数量不多,最好删除这些离群值,以免影响其他较常见的数据点的预测。 让我们看一些房屋价值的图表,然后尝试画出两条最合适的线:一条去除所有数据,一条去除高价房屋离群值: + +![](img/B15441_04_08.png) + +图 8:最佳拟合图 + +如您所见,如果从最佳拟合线的计算中除去异常值,则该线将更准确地预测低价房屋。 因此,简单地删除异常值是处理异常值影响的简单而有效的方法。 + +那么,我们如何确定某个值是否为离群值并应将其删除? 一种常见的方法是删除落在数据集中某个要素值的标准偏差的某个倍数的离群值。 用于乘数的常量更多的是一门艺术,而不是一门科学,但是 2 到 4 之间的值很常见: + +```py +#Dropping the outlier rows with standard deviation +import pandas as pd +``` + +```py +data = pd.read_csv("train.csv") +``` + +```py +#Dropping the outlier rows with standard deviation +factor = 2 +upper_lim = data['battery_power'].mean () + data['battery_power'].std () * factor +lower_lim = data['battery_power'].mean () - data['battery_power'].std () * factor +``` + +```py +data = data[(data['battery_power'] < upper_lim) & (data['battery_power'] > lower_lim)] +print(data) +``` + +输出应为,类似于: + +![](img/B15441_04_09.png) + +图 9:删除异常行输出 + +检测和消除异常值的另一种方法是使用百分位。 使用这种方法,我们仅假设要素值的一定百分比是离群值。 下降多少百分比值仍然是主观的,并且将取决于领域。 + +让我们看一个 Python 示例,在其中将最高和最低 1%的部分删除: + +```py +#Dropping the outlier rows with Percentiles +upper_lim = data['battery_power'].quantile(.99) +lower_lim = data['battery_power'].quantile(.01) +``` + +```py +data = data[(data['battery_power'] < upper_lim) & (data['battery_power'] > lower_lim)] +print(data) +``` + +预期的输出如下: + +![](img/B15441_04_10.png) + +图 10:输出异常值行 + +处理离群值的另一种方法是设置值上限而不是删除值。 封顶值而不是删除行允许您保留数据点,并有可能提高模型的性能。 但是,保留数据点但限制值的上限会使该数据点成为估计值而不是实际观察值,这也可能会影响结果。 决定使用哪种方法将取决于对特定数据集的分析。 这是一个使用上限值而不是删除行的示例: + +```py +#Capping the outlier rows with percentiles +upper_lim = data['battery_power'].quantile(.99) +lower_lim = data['battery_power'].quantile(.01) +``` + +```py +data.loc[(data['battery_power'] > upper_lim), 'battery_power'] = upper_lim +data.loc[(data['battery_power'] < lower_lim), 'battery_power'] = lower_lim +print(data) +``` + +以下是应遵守的输出。 如果您应滚动浏览输出,则可能会注意到一些更改的值: + +![](img/B15441_04_11.png) + +图 11:帽异常行输出 + +# 一键编码 + +单热编码是机器学习中用于特征工程的一种常用技术。 一些机器学习算法无法处理分类特征,因此单热编码是一种将这些分类特征转换为数值特征的方法。 假设您有一个标记为“状态”的功能,该功能可以采用三个值(红色,绿色或黄色)之一。 由于这些值是分类的,因此不存在哪个值是*较高*或*较低*的概念。 我们可以将这些值转换为数值,从而赋予它们这种特性。 例如: + +黄色= 1 + +红色= 2 + +绿色= 3 + +但这似乎有些武断。 如果我们知道红色是不好的,绿色是好的,而黄色在中间,那么我们可以将映射更改为: + +红色= -1 + +黄色= 0 + +绿色= 1 + +这样可能会产生更好的性能。 但是,现在让我们看看如何对这个示例进行一次热编码。 为了实现此一个变量的一次性编码,我们为每个值创建了一个新功能。 在这种情况下,我们的一些数据(您可能会在野外遇到的东西)可能看起来像这样: + + +| **红色** | **黄色** | **绿色** | **状态** | +| 1 | 0 | 0 | 红 | +| 0 | 1 | 0 | 黄色 | +| 0 | 0 | 1 | 绿色 | +| 0 | 0 | 1 | 绿色 | + +由于我们已经对数据进行了一次热编码,因此状态功能现在变得多余,因此我们可以从数据集中消除它: + + +| **红色** | **黄色** | **绿色** | +| 1 | 0 | 0 | +| 0 | 1 | 0 | +| 0 | 0 | 1 | +| 0 | 0 | 1 | + +此外,我们可以从其他两个颜色特征中计算出任何颜色特征的值。 如果红色和黄色都为 0,则意味着绿色需要为 1,依此类推。 因此,在一键编码中,我们始终可以删除其中一项功能而不会丢失信息。 像这样: + + +| **红色** | **黄色** | +| 1 | 0 | +| 0 | 1 | +| 0 | 0 | +| 0 | 0 | + +现在让我们看一个示例,说明如何使用`get_dummies`函数使用 Pandas 库对功能进行一次热编码: + +```py +import pandas as pd +``` + +```py +data = pd.read_csv("dataset.csv") +``` + +```py +encoded_columns = pd.get_dummies(data['color']) +data = data.join(encoded_columns).drop('color', axis=1) +print(data) +``` + +输出应如下所示: + +![](img/B15441_04_12.png) + +图 12:一个热编码输出 + +# 对数变换 + +对数转换(或对数转换)是常见的特征工程转换。 对数转换有助于展平高度偏斜的值。 应用日志转换后,数据分布将被标准化。 + +让我们再看一个例子,再次获得一些直觉。 请记住,当您 10 岁时,看着 15 岁的男孩和女孩时,他们在想:“他们比我大得多!” 现在想想一个 50 岁的人和另一个 55 岁的人。 在这种情况下,您可能会认为年龄差异并不大。 在这两种情况下,年龄差异均为 5 岁。 但是,在第一种情况下,15 岁的年龄比 10 岁的年龄大 50%,在第二种情况下,55 岁的年龄比 50 岁的年龄大 10%。 + +如果我们对所有这些数据点应用对数变换,则将这样的幅度差异归一化。 + +由于幅值差异的归一化,使用对数变换的模型也将减少异常值的影响,并且使用对数变换的模型将变得更加健壮。 + +使用此技术时要考虑的一个关键限制是,仅当所有数据点均为正值时才应应用对数转换。 另外,您可以在应用转换之前将 1 加到数据中。 因此,您确保转换的输出为正: + +*日志(x + 1)* + +这是在 Python 中执行日志转换的方法: + +```py +#Log Transform Example +data = pd.DataFrame({'value':[3,67, -17, 44, 37, 3, 31, -38]}) +data['log+1'] = (data['value']+1).transform(np.log) +``` + +```py +#Negative Values Handling +#Note that the values are different +data['log'] = (data['value']-data['value'].min()+1) .transform(np.log) +print(data) +``` + +这是应产生的输出: + +![](img/B15441_04_13.png) + +图 13:对数变换输出 + +# 缩放 + +在许多情况下,数据集中的数字特征在规模上会与其他特征有很大差异。 例如,房屋的典型平方英尺数可能是 1000 到 3000 平方英尺之间的数字,而房屋中卧室数量的 2、3 或 4 可能是更典型的数字。 如果我们不理会这些值,则如果不放下比例较高的要素,则可能会赋予较高的权重。 如何解决此问题? + +缩放可以解决此问题。 应用缩放后,连续特征在范围上变得可比。 并非所有算法都需要标定值(Random Forest 浮现在脑海),但是如果未事先对数据集进行标定,则其他算法将产生无意义的结果(例如 k 近邻或 k 均值)。 现在,我们将探讨两种最常见的缩放方法。 + +**归一化**(或 minmax 归一化)将的所有值缩放到介于 0 到 1 之间的固定范围内。更正式的说,可以使用以下公式对特征的每个值进行归一化: + +![](img/B15441_04_002.png) + +哪里: + +* *X* -功能的任何给定值 +* *X* min –数据集中所有数据点的最小值 +* *X* max –数据集中所有数据点的最大值 +* *X* 规范 –应用公式后的归一化值 + +规范化不会更改特征的分布,并且由于减少的标准偏差,离群值的影响会增加。 因此,建议在规范化之前处理离群值。 现在,让我们看一个 Python 示例: + +```py +data = pd.DataFrame({'value':[7,25, -47, 73, 8, 22, 53, -25]}) +``` + +```py +data['normalized'] = (data['value'] - data['value'].min()) / (data['value'].max() - data['value'].min()) +print(data) +``` + +期望看到以下输出: + +![](img/B15441_04_14.png) + +图 14:标准化输出 + +**标准化**(或 z 分数标准化)是一种缩放方法,其中包括标准偏差作为其计算的一部分。 标准化最小化并平滑了缩放中异常值的影响。 让我们看看如何计算它: + +![](img/B15441_04_003.png) + +哪里: + +* ![](img/B15441_04_004.png) =平均值 +* ![](img/B15441_04_005.png) =标准偏差 +* *x* =数据点 + +并使用 Python 计算: + +```py +data = pd.DataFrame({'value':[7,25, -47, 73, 8, 22, 53, -25]}) +``` + +```py +data['standardized'] = (data['value'] - data['value'].mean()) / data['value'].std() +print(data) +``` + +您应该在控制台中看到以下内容: + +![](img/B15441_04_15.png) + +图 15:标准化输出 + +# 日期操作 + +对于某些数据科学问题,时间特征可能至关重要。 在时间序列分析中,日期显然至关重要。 如果您没有在预测中附加日期,则预测 S & P 500 将达到 3,000 将毫无意义。 + +没有进行任何处理的日期可能对大多数模型没有多大意义,并且这些值将太独特而无法提供任何预测能力。 为什么 10/21/2019 与 10/19/2019 不同? 如果我们使用某些领域知识,则可能能够极大地增加功能的信息价值。 例如,将日期转换为分类变量可能会有所帮助。 如果目标功能是您试图确定何时支付租金,则将日期转换为二进制值,其中可能的值为: + +* 该月的第 5 个 = 1 +* 当月的第 5 个后的 = 0 + +如果要求您预测餐厅的人流和销售情况,那么看每个月的 21 st 可能没有任何流量模式,但是可以想象,如果 日期是星期日与星期二,或者月是 10 月与 12 月(例如圣诞节)。 如果这是一家国际连锁餐厅,那么餐厅位置和月份可能就非常重要(美国的圣诞节与印度的排灯节)。 + +可以操纵日期的其他可能方式包括: + +* 将日期分为不同的部分:年,月,日等 +* 以年,月,日等方式计算当前日期与所讨论的值之间的时间段 +* 从日期中提取特定的功能: + * 星期几(星期一,星期二,依此类推) + * 周末与否 + * 是否休假 + +还有许多其他可能性。 我们将其留给读者集思广益或研究其他方法。 + +# 摘要 + +在本章中,我们分析了机器学习流程中的两个重要步骤: + +* 功能选择 +* 特征工程 + +如我们所见,这两个过程目前既是一门艺术,又是一门科学。 与确定要删除的特征以及要生成的特征添加到模型中相比,选择一个模型在管道中使用可能是一件容易的事。 本章并不是要对功能选择和功能工程进行全面的分析,而只是一个小尝试,希望它激发您进一步探索该主题的兴趣。 + +在下一章中,我们将开始深入研究机器学习。 我们将从监督学习模型开始构建机器学习模型。 \ No newline at end of file diff --git a/docs/ai-py/05.md b/docs/ai-py/05.md new file mode 100644 index 0000000000000000000000000000000000000000..be4113750172b46ff4d1ca7b2f3fb598cadf85d2 --- /dev/null +++ b/docs/ai-py/05.md @@ -0,0 +1,1181 @@ +# 5 + +# 使用监督学习的分类和回归 + +在本章中,我们将使用监督学习技术来学习数据的分类和回归。 在本章的最后,您将对这些主题有更好的理解: + +* 监督学习与无监督学习之间的差异 +* 分类方法 +* 数据预处理方法 +* 标签编码 +* 逻辑回归分类器 +* 朴素贝叶斯分类器 +* 混淆矩阵 +* 支持向量机和 SVM 分类器 +* 线性和多项式回归 +* 单变量和多变量线性回归 +* 使用支持向量回归机估算房价 + +# S 优于无监督学习 + +从流行的新闻界看到并不难,当今人工智能领域最热门的领域之一就是机器学习。 机器学习通常分为有监督的学习和无监督的学习。 还存在其他分类,但我们将在后面讨论。 + +在给出更正式的定义之前,让我们对监督学习与无监督学习有一些直观的了解。 假设您有一组人物肖像。 这一组中的人是一个非常多样化的男人和女人,您具有各种国籍,年龄,体重等。 最初,您将数据集通过无监督学习算法进行处理。 在这种情况下,在没有任何先验知识的情况下,无监督算法将根据其识别为相似的某些特征开始对这些照片进行分类。 例如,它可能独自开始认识到男人和女人是不同的,并且可能开始将男人归为一组,将女人归为另一组。 但是不能保证它将找到该模式。 由于某些肖像具有深色背景而另一些肖像具有浅色背景,因此它可能使图像聚类,这可能是无用的推断。 + +现在拍摄同一组照片,但是这次我们在每张照片上都有一个标签。 假设标签是性别。 因为我们现在有了数据的标签,所以我们可以将数据通过监督算法处理,并使用输入变量(在这种情况下,输入变量是照片像素)来计算目标变量(在这种情况下,性别)。 更正式的: + +**监督学习**是指指的是基于标记的训练数据构建机器学习模型的过程。 在监督学习中,每个示例或行都是一个由输入变量和所需目标变量组成的元组。 例如,机器学习中使用的常见数据集是“泰坦尼克号”数据集。 该数据集包含描述著名船只 RMS Titanic 的乘客的特征。 一些输入功能是: + +* 旅客姓名 +* 性别 +* 客舱等级 +* 年龄 +* 登船地点 + +在这种情况下,目标变量将是乘客是否幸存。 + +**无监督学习**是指指的是不依赖标记的训练数据来构建机器学习模型的过程。 从某种意义上说,这与监督学习相反。 由于没有可用的标签,因此您仅需要根据提供给您的数据来提取见解。 通过无监督学习,我们正在训练一个系统,其中单独的数据点可能会分成多个群集或组。 需要重点强调的一点是,我们并不确切地知道分离的标准是什么。 因此,无监督学习算法需要以可能的最佳方式将给定的数据集分为几组。 + +现在,我们已经描述了机器学习方法的主要分类方法之一,让我们开始研究如何分类 d ata。 + +# 什么是分类? + +在本节中,我们将讨论监督分类技术。 分类过程是一种用于将数据排列成固定数量的类别,以便可以有效地使用它的技术。 + +在机器学习中,分类用于标识新数据点所属的类别。 基于包含数据点和相应标签的训练数据集建立分类模型。 例如,假设我们要确定给定的图像是否包含一个人的脸。 我们将构建一个训练数据集,其中包含与两个类别相对应的类别:面部和无面部。 然后将基于可用的训练样本来训练模型。 然后可以将训练后的模型用于推理。 + +良好的分类系统使查找和检索数据变得容易。 分类广泛用于面部识别,垃圾邮件识别,推荐引擎等。 好的数据分类算法将自动生成正确的标准,以将给定的数据分为给定数量的 cla sses。 + +为了进行分类以产生不错的结果,将需要足够数量的样本,以便可以概括这些标准。 如果样本数量不足,该算法将过度适合训练数据。 这意味着它将无法很好地处理未知数据,因为它对模型进行了微调,无法适应训练数据中观察到的模式。 这实际上是机器学习领域中常见的问题。 在构建各种机器学习机器时,考虑这个因素是一个好主意。 + +# 预处理数据 + +原始数据是机器学习算法的推动力。 但是就像我们不能将原油放进汽车中,而是必须使用汽油一样,机器学习算法希望在训练过程开始之前就可以以某种方式格式化数据。 为了准备通过机器学习算法提取的数据,必须对数据进行预处理并将其转换为正确的格式。 让我们看一下实现这一目标的一些方法。 + +对于我们将要分析工作的示例,我们将需要导入一些 Python 软件包: + +```py +import numpy as np +from sklearn import preprocessing +``` + +另外,让我们定义一些样本数据: + +```py +input_data = np.array([[5.1, -2.9, 3.3], + [-1.2, 7.8, -6.1], + [3.9, 0.4, 2.1], + [7.3, -9.9, -4.5]]) +``` + +这些是我们将要分析的预处理技术: + +* 二值化 +* 均值去除 +* 缩放比例 +* 规范 l 化 + +## 二值化 + +二进制化用于将数值转换为布尔值。 让我们使用一种内置方法,以`2.1`作为阈值 ld 值,对进行二值化。 + +将以下行添加到同一 Python 文件中: + +```py +# Binarize data +data_binarized = preprocessing.Binarizer(threshold=2.1).transform(input_data) +print("\nBinarized data:\n", data_binarized) +``` + +如果运行代码,将看到以下输出: + +```py +Binarized data: +[[ 1\. 0\. 1.] +[ 0\. 1\. 0.] +[ 1\. 0\. 0.] +[ 1\. 0\. 0.]] +``` + +如我们在这里看到的,`2.1`以上的所有值都变为`1`。 剩余值 变为`0`。 + +## 去除均值 + +去除均值是机器学习中常用的预处理技术。 通常,从特征向量中删除平均值非常有用,这样每个特征都以零为中心。 我们这样做是为了消除特征向量中的特征偏差。 + +将以下行添加到与上一节相同的 Python 文件中: + +```py +# Print mean and standard deviation +print("\nBEFORE:") +print("Mean =", input_data.mean(axis=0)) +print("Std deviation =", input_data.std(axis=0)) +``` + +前一行显示输入数据的平均值和标准偏差。 让我们去除均值: + +```py +# Remove mean +data_scaled = preprocessing.scale(input_data) +print("\nAFTER:") +print("Mean =", data_scaled.mean(axis=0)) +print("Std deviation =", data_scaled.std(axis=0)) +``` + +如果运行代码,将看到以下输出: + +```py +BEFORE: +Mean = [ 3.775 -1.15 -1.3 ] +Std deviation = [ 3.12039661 6.36651396 4.0620192 ] AFTER: +Mean = [ 1.11022302e-16 0.00000000e+00 2.77555756e-17] +Std deviation = [ 1\. 1\. 1.] +``` + +从获得的值可以看出,平均值非常接近`0`,标准 de 的标准是`1`。 + +## 缩放 + +正如在上一节中所做的一样,让我们​​通过访问一个示例来直观了解什么是缩放。 假设您有一个包含与房屋相关的特征的数据集,并且您正在尝试预测这些房屋的价格。 这些功能的数值范围可能会大不相同。 例如,房屋的平方英尺通常为数千,而房间数通常少于 10。此外,其中一些功能可能包含一些异常值。 例如,我们的数据集中可能有一些大厦使其他数据集倾斜。 + +我们需要找到一种缩放这些特征的方法,以使赋予每个特征的权重大致相同,而离群值的重要性也不会太大。 一种方法是重新调整所有功能,使它们落入较小的范围内,例如`0`和`1`。 MinMaxScaler 算法可能是实现此目的的最有效方法。 该算法的公式为: + +![](img/B15441_05_001.png) + +其中 *max(x)*是变量的最大值, *min(x)*是最小值,并且 *x* i 分别 个人价值。 + +在我们的特征向量中,每个特征的值可以在许多随机值之间变化。 因此,对这些功能进行缩放以具有一个公平的竞争环境以训练机器学习算法就变得很重要。 仅仅由于测量的性质,任何功能都不能人为地变大或变小。 + +要在 Python 中实现此功能,请在文件中添加以下几行: + +```py +# Min max scaling +data_scaler_minmax = preprocessing.MinMaxScaler(feature_range=(0, 1)) +data_scaled_minmax = data_scaler_minmax.fit_transform(input_data) +print("\nMin max scaled data:\n", data_scaled_minmax) +``` + +如果运行代码,将看到以下输出: + +```py +Min max scaled data: + [[ 0.74117647 0.39548023 1\. ] + [ 0\. 1\. 0\. ] + [ 0.6 0.5819209 0.87234043] + [ 1\. 0\. 0.17021277]] +``` + +缩放每一行,以使最大值为`1`,而所有其他值相对 e 为该值。 + +## 标准化 + +民间通常会混淆缩放和规范化。 术语经常被混淆的原因之一是因为它们实际上非常相似。 在这两种情况下,您都在转换数据以使数据更有用。 但是,在缩放时,您正在更改变量值的*范围*,而通过归一化,您正在更改数据分布的*形状。 为了使机器学习模型更好地工作,希望特征的值呈正态分布。* + +但是现实是混乱的,有时情况并非如此。 例如,值的分布可能会偏斜。 规范化通常会分配数据。 以下是规范化之前和之后的数据图: + +![](img/B15441_05_01.png) + +图 1:标准化前后 + +我们使用规范化过程来修改特征向量中的值,以便可以在一个通用尺度上对其进行测量。 在机器学习中,我们使用许多不同形式的规范化。 某些最常见的规范化形式旨在修改值,使它们的总和为 1。 ],确保每行的绝对值之和为 *1* 。 **L2 归一化**是指最小二乘,它通过确保平方和为 *1* 来工作。 + +通常,L1 归一化技术被认为比 L2 归一化技术更健壮。 L1 归一化技术很强大,因为它可以抵抗数据中的异常值。 很多时候,数据倾向于包含离群值,而我们对此无能为力。 我们希望使用可以在计算过程中安全有效地忽略它们的技术。 如果我们要解决离群值很重要的问题,那么 L2 规范化可能会成为更好的选择。 + +将以下行添加到同一 Python 文件中: + +```py +# Normalize data +data_normalized_l1 = preprocessing.normalize(input_data, norm='l1') +data_normalized_l2 = preprocessing.normalize(input_data, norm='l2') +print("\nL1 normalized data:\n", data_normalized_l1) +print("\nL2 normalized data:\n", data_normalized_l2) +``` + +如果运行代码,将看到以下输出: + +```py +L1 normalized data: + [[ 0.45132743 -0.25663717 0.2920354 ] + [-0.0794702 0.51655629 -0.40397351] + [ 0.609375 0.0625 0.328125 ] + [ 0.33640553 -0.4562212 -0.20737327]] +L2 normalized data: + [[ 0.75765788 -0.43082507 0.49024922] + [-0.12030718 0.78199664 -0.61156148] + [ 0.87690281 0.08993875 0.47217844] + [ 0.55734935 -0.75585734 -0.34357152]] +``` + +`data_preprocessor.py`文件中提供了整个部分的代码。 + +# 标签编码 + +当执行分类时,我们通常会处理很多标签。 这些标签可以是文字,数字或其他形式。 许多机器学习算法需要数字作为输入。 因此,如果它们已经是数字,则可以直接用于训练。 但这并非总是如此。 + +标签通常是单词,因为单词可以被人类理解。 训练数据用单词标记,以便可以跟踪映射。 要将单词标签转换为数字,可以使用标签编码器。 标签编码是指将单词标签转换为数字的过程。 这使算法能够处理数据。 让我们看一个例子: + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +from sklearn import preprocessing +``` + +定义一些样本标签: + +```py +# Sample input labels +input_labels = ['red', 'black', 'red', 'green', 'black', 'yellow', 'white'] +``` + +创建标签编码器对象并对其进行训练: + +```py +# Create label encoder and fit the labels +encoder = preprocessing.LabelEncoder() +encoder.fit(input_labels) +``` + +打印单词和数字之间的映射: + +```py +# Print the mapping +print("\nLabel mapping:") +for i, item in enumerate(encoder.classes_): + print(item, '-->', i) +``` + +让我们对一组随机排序的标签进行编码,以查看其性能: + +```py +# Encode a set of labels using the encoder +test_labels = ['green', 'red', 'black'] +encoded_values = encoder.transform(test_labels) +print("\nLabels =", test_labels) +print("Encoded values =", list(encoded_values)) +``` + +让我们解码一组随机数字: + +```py +# Decode a set of values using the encoder +encoded_values = [3, 0, 4, 1] +decoded_list = encoder.inverse_transform(encoded_values) +print("\nEncoded values =", encoded_values) +print("Decoded labels =", list(decoded_list)) +``` + +如果运行代码,将看到以下输出: + +![](img/B15441_05_02.png) + +图 2:编码和解码输出 + +您可以检查映射以查看编码和解码步骤是否正确。 该部分的代码在 `label_encoder.py`文件中给出。 + +# Logistic 回归分类器 + +Logistic 回归是的一种技术,用于解释输入变量和输出变量之间的关系。 回归可用于对连续值进行预测,但是对于离散预测,例如结果为*真*或*假*或*红色[* ,*绿色*或*黄色*作为另一个示例。 + +假定输入变量是独立的,输出变量称为因变量。 因变量只能采用一组固定的值。 这些值对应于分类问题的类别。 + +我们的目标是通过使用逻辑函数估计概率来确定独立变量和因变量之间的关系。 在这种情况下,此逻辑函数将是 **S 型曲线**,该曲线用于使用各种参数构建函数。 在逻辑回归模型中使用 S 形函数的一些原因是: + +* 它的范围是 0 到 1 +* 它的派生更容易计算 +* 将非线性引入模型的简单方法 + +它与广义线性模型分析密切相关,在广义线性模型分析中,我们尝试将一条线拟合到一堆点以最小化误差。 代替线性回归,我们使用逻辑回归。 Logistic 回归本身不是分类技术,但是以这种方式使用来促进分类。 由于其简单性,它通常在机器学习中使用。 让我们看看如何使用逻辑回归构建分类器。 在继续操作之前,请确保已安装软件包。 如果不是,则可以在这里找到: [https://docs.python.org/2/library/tkinter.html](https://docs.python.org/2/library/tkinter.html) 。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +from sklearn import linear_model +import matplotlib.pyplot as plt +from utilities import visualize_classifier +``` + +使用二维向量和相应的标签定义样本输入数据: + +```py +# Define sample input data +X = np.array([[3.1, 7.2], [4, 6.7], [2.9, 8], [5.1, 4.5], [6, 5], [5.6, 5], [3.3, 0.4], [3.9, 0.9], [2.8, 1], [0.5, 3.4], [1, 4], [0.6, 4.9]]) +y = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3]) +``` + +我们将使用此标记数据训练分类器。 现在创建逻辑回归分类器对象: + +```py +# Create the logistic regression classifier +classifier = linear_model.LogisticRegression(solver='liblinear', C=1) +``` + +使用之前定义的数据训练分类器: + +```py +# Train the classifier +classifier.fit(X, y) +``` + +通过查看类的边界来可视化分类器的性能: + +```py +# Visualize the performance of the classifier +visualize_classifier(classifier, X, y) +``` + +必须先定义功能,然后才能使用它。 在本章中,我们将多次使用此函数,因此最好在一个单独的文件中对其进行定义并导入该函数。 此功能在提供的`utilities.py`文件中提供。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +``` + +通过将`classifier`对象,输入数据和标签作为输入参数来创建函数定义: + +```py +def visualize_classifier(classifier, X, y): + # Define the minimum and maximum values for X and Y + # that will be used in the mesh grid + min_x, max_x = X[:, 0].min() - 1.0, X[:, 0].max() + 1.0 + min_y, max_y = X[:, 1].min() - 1.0, X[:, 1].max() + 1.0 +``` + +我们还定义了将在网格网格中使用的`X`和`Y`方向的最小值和最大值。 该网格基本上是用于评估函数的一组值,以便我们可以可视化类的边界。 定义网格的步长并使用最小值和最大值创建它: + +```py + # Define the step size to use in plotting the mesh grid mesh_step_size = 0.01 + + # Define the mesh grid of X and Y values + x_vals, y_vals = np.meshgrid(np.arange(min_x, max_x, mesh_step_size), np.arange(min_y, max_y, mesh_step_size)) +``` + +在网格上的所有点上运行分类器: + +```py + # Run the classifier on the mesh grid + output = classifier.predict(np.c_[x_vals.ravel(), y_vals.ravel()]) + + # Reshape the output array + output = output.reshape(x_vals.shape) +``` + +创建图形,选择配色方案,然后覆盖所有点: + +```py + # Create a plot + plt.figure() +``` + +```py + # Choose a color scheme for the plot + plt.pcolormesh(x_vals, y_vals, output, cmap=plt.cm.gray) + + # Overlay the training points on the plot + plt.scatter(X[:, 0], X[:, 1], c=y, s=75, edgecolors='black', linewidth=1, cmap=plt.cm.Paired) +``` + +使用最小值和最大值指定图的边界,添加复选标记,并显示图形: + +```py + # Specify the boundaries of the plot + plt.xlim(x_vals.min(), x_vals.max()) + plt.ylim(y_vals.min(), y_vals.max()) +``` + +```py + # Specify the ticks on the X and Y axes + plt.xticks((np.arange(int(X[:, 0].min() - 1), int(X[:, 0].max() + 1), 1.0))) + plt.yticks((np.arange(int(X[:, 1].min() - 1), int(X[:, 1].max() + 1), 1.0))) + plt.show() +``` + +如果代码已运行,您将看到以下屏幕截图: + +![](img/B15441_05_03.png) + +图 3:设置图的边界后显示的图 + +如果在下面的行中将`C`的值更改为`100`,您将看到边界变得更加准确: + +```py +classifier = linear_model.LogisticRegression(solver='liblinear', C=100) +``` + +原因是`C`对分类错误施加了一定的惩罚,因此该算法针对训练数据进行了更多定制。 您应该谨慎使用此参数,因为如果将其大量增加,它将过度适合训练数据,并且不能很好地泛化。 + +如果在将`C`设置为`100`的情况下运行代码,则会看到以下屏幕截图: + +![](img/B15441_05_04.png) + +图 4:在将 C 设置为 100 的情况下运行代码时的结果 + +如果与之前的数字进行比较,您会发现边界现在更好了。 该部分的代码在`logistic_regression.py`文件中给出。 + +# 朴素贝叶斯分类器 + +**朴素贝叶斯**是一种用于使用贝叶斯定理建立分类器的技术。 贝叶斯定理描述了基于与该事件相关的不同条件发生事件的概率。 通过为问题实例分配类标签,我们构建了朴素的贝叶斯分类器。 这些问题实例被表示为特征值的向量。 这里的假设是任何给定特征的值都独立于任何其他特征的值。 这称为独立性假设,它是朴素贝叶斯分类器的朴素部分。 + +给定 class 变量,我们可以仅查看给定功能如何影响它,而不管其对其他功能的影响。 例如,如果发现动物,有四条腿,有一条尾巴并且以大约 70 MPH 的速度奔跑,则可以将其视为猎豹。 朴素的贝叶斯分类器认为这些特征中的每一个都对结果有独立的贡献。 结果是指该动物是猎豹的可能性。 我们并不关心皮肤图案,腿数,尾巴的存在和运动速度之间可能存在的相关性。 让我们看看如何构建朴素贝叶斯分类器。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from sklearn.naive_bayes import GaussianNB +from sklearn import.model_selection import train_test_split +from sklearn.model_selection import .model_selection import train_test_split +from sklearn.model_selection import cross_val_score +``` + +```py +from utilities import visualize_classifier +``` + +我们将使用文件`data_multivar_nb.txt`作为源数据。 此文件在每行中包含逗号分隔的值: + +```py +# Input file containing data +input_file = 'data_multivar_nb.txt' +``` + +让我们从该文件加载数据: + +```py +# Load data from input file +data = np.loadtxt(input_file, delimiter=',') +X, y = data[:, :-1], data[:, -1] +``` + +创建朴素贝叶斯分类器的实例。 我们将在这里使用高斯朴素贝叶斯分类器。 在这种类型的分类器中,我们假设与每个类关联的值都遵循高斯分布: + +```py +# Create Naïve Bayes classifier +classifier = GaussianNB() +``` + +使用训练数据训练分类器: + +```py +# Train the classifier +classifier.fit(X, y) +``` + +对训练数据运行分类器并预测输出: + +```py +# Predict the values for training data +y_pred = classifier.predict(X) +``` + +让我们通过将预测值与真实标签进行比较来计算分类器的准确性,然后可视化性能: + +```py +# Compute accuracy +accuracy = 100.0 * (y == y_pred).sum() / X.shape[0] +print("Accuracy of Naïve Bayes classifier =", round(accuracy, 2), "%") +``` + +```py +# Visualize the performance of the classifier +visualize_classifier(classifier, X, y) +``` + +先前用于的计算分类器准确性的方法并不可靠。 我们需要执行交叉验证,以便在测试数据时不会使用相同的训练数据。 + +将数据分为训练和测试子集。 如以下行中的`test_size`参数所指定,我们将分配 80%用于训练,其余 20%用于测试。 然后,我们将在此数据上训练朴素贝叶斯分类器: + +```py +# Split data into training and test data +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=3) +classifier_new = GaussianNB() +classifier_new.fit(X_train, y_train) +y_test_pred = classifier_new.predict(X_test) +``` + +计算分类器的准确性并可视化性能: + +```py +# compute accuracy of the classifier +accuracy = 100.0 * (y_test == y_test_pred).sum() / X_test.shape[0] +print("Accuracy of the new classifier =", round(accuracy, 2), "%") +``` + +```py +# Visualize the performance of the classifier +visualize_classifier(classifier_new, X_test, y_test) +``` + +让我们使用内置函数基于三重交叉验证来计算准确性,精确度和召回值: + +```py +num_folds = 3 +accuracy_values = cross_val_score(classifier, + X, y, scoring='accuracy', cv=num_folds) +print("Accuracy: " + str(round(100*accuracy_values.mean(), 2)) + "%") +``` + +```py +precision_values = cross_val_score(classifier, + X, y, scoring='precision_weighted', cv=num_folds) +print("Precision: " + str(round(100*precision_values.mean(), 2)) + "%") +``` + +```py +recall_values = cross_val_score(classifier, + X, y, scoring='recall_weighted', cv=num_folds) +print("Recall: " + str(round(100*recall_values.mean(), 2)) + "%") +``` + +```py +f1_values = cross_val_score(classifier, + X, y, scoring='f1_weighted', cv=num_folds) +print("F1: " + str(round(100*f1_values.mean(), 2)) + "%") +``` + +如果运行代码,则第一次训练将显示: + +![](img/B15441_05_05.png) + +图 5:第一次训练后的聚类和边界 + +前面的屏幕快照显示了从分类器获得的边界。 我们可以看到它们很好地分隔了四个聚类,并根据输入数据点的分布创建了具有边界的区域。 您将在下面的屏幕截图中看到带有交叉验证的第二次训练: + +![](img/B15441_05_06.png) + +图 6:使用交叉验证的第二次训练结果 + +您将和以下打印输出: + +```py +Accuracy of Naïve Bayes classifier = 99.75 % +Accuracy of the new classifier = 100.0 % +Accuracy: 99.75% +Precision: 99.76% +Recall: 99.75% +F1: 99.75% +``` + +该部分的代码在文件`naive_bayes.py`中给出。 + +# 混淆 矩阵 + +**混淆矩阵**是图形或用于描述分类器性能的表格。 矩阵中的每一行代表预测类中的实例,而每一列代表实际类中的实例。 之所以使用此名称,是因为如果模型混淆或错误标记了两个类别,则矩阵使可视化变得容易。 我们将每个类别与其他每个类别进行比较,看看有多少样本被正确分类或分类错误。 + +在构建此表的过程中,我们遇到了一些关键指标,这些指标在机器学习领域很重要。 让我们考虑一个二进制分类的情况,其中输出为 *0* 或 *1* : + +* **真实阳性**:这些是我们预测 *1* 作为输出的样本,而真实情况也是 *1* 。 +* **真底片**:这些是我们为其预测了 *0* 作为输出的样本,并且地面真实情况也是 *0* 。 +* **假阳性**:这些是我们预测将其作为输出的 *1* 但实际情况为 *0* 的样本。 这也称为 *I 型错误*。 +* **假阴性**:这些是我们预测其输出为 *0* 但实际情况为 *1* 的样本。 这也称为 *II 型错误*。 + +根据当前的问题,我们可能必须优化算法以减少误报率或误报率。 例如,在生物识别系统中,避免误报非常重要,因为错误的人可能会访问敏感信息。 让我们看看如何创建一个混淆矩阵。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from sklearn.metrics import confusion_matrix +from sklearn.metrics import classification_report +``` + +Defi 为地面真实情况和预测的输出提供了一些样本标签: + +```py +# Define sample labels +true_labels = [2, 0, 0, 2, 4, 4, 1, 0, 3, 3, 3] +pred_labels = [2, 1, 0, 2, 4, 3, 1, 0, 1, 3, 3] +``` + +使用我们刚刚定义的标签创建混淆矩阵: + +```py +# Create confusion matrix +confusion_mat = confusion_matrix(true_labels, pred_labels) +``` + +可视化混淆矩阵: + +```py +# Visualize confusion matrix +plt.imshow(confusion_mat, interpolation='nearest', cmap=plt.cm.gray) +plt.title('Confusion matrix') +plt.colorbar() +ticks = np.arange(5) +plt.xticks(ticks, ticks) +plt.yticks(ticks, ticks) +plt.ylabel('True labels') +plt.xlabel('Predicted labels') +plt.show() +``` + +在前面的可视化代码中,`ticks`变量引用不同类的数量。 在我们的案例中,我们有五个不同的标签。 + +让我们打印分类报告: + +```py +# Classification report +targets = ['Class-0', 'Class-1', 'Class-2', 'Class-3', 'Class-4'] print('\n', classification_report(true_labels, pred_labels, target_names=targets)) +``` + +分类报告打印每个班级的表现。 如果运行代码,您将看到以下屏幕截图: + +![](img/B15441_05_07.png) + +图 7:分类报告中每个班级的表现 + +Whit e 表示较高的值,而黑色表示较低的值,如在图像右侧的颜色图键上所示。 在理想情况下,对角正方形将全为白色,其他所有区域均为黑色。 这表示 100%的准确性。 + +输出应为如下: + +![](img/B15441_05_08.png) + +图 8:分类报告中的值 + +可以看出,平均`precision`为 85%,平均`recall`为 73%。 而`f1-score`为 75%。 根据我们正在使用的域,这些结果可能是好结果,也可能是不好的结果。 如果所讨论的领域是试图确定患者是否患有癌症,而我们的准确率只能达到 85%,那么将有 15%的人口被错误分类,并且会非常不满意。 如果我们正在分析的领域是某人是否打算购买产品并且我们的精度结果相同,则可以认为这是本垒打,可以大大减少我们的营销费用。 + +该部分的代码在文件`confusion_matrix.py`中给出。 + +# 支持 ort 矢量机 + +**支持向量机**( **SVM** )是分类器,使用类别之间的分隔超平面进行定义。 此**超平面**是直线的 N 维版本。 给定带标签的训练数据和二进制分类问题,SVM 会找到将训练数据分为两类的最佳超平面。 这很容易扩展到`N`类的问题。 + +让我们考虑具有两类点的二维情况。 考虑到它是 2D,我们只必须处理 2D 平面上的点和线。 这比在高维空间中的矢量和超平面更容易可视化。 当然,这是 SVM 问题的简化版本,但是在将其应用于高维数据之前,了解它并对其进行可视化非常重要。 + +Co 位于下图: + +![](img/B15441_05_09.png) + +图 9:用超平面分离两个类 + +点分为两类,我们想找到最佳的超平面来将这两个类分开。 但是我们如何定义最优? 在此图片中,实线代表最佳超平面。 您可以绘制许多不同的线以将两类点分开,但这条线是最好的分隔符,因为它可以使每个点到分隔线的距离最大化。 虚线上的点称为支持向量。 两条虚线之间的垂直距离称为最大边距。 您可以将最大边距视为可以为给定数据集绘制的最​​粗边框。 + +# Cl 使用支持向量机将收入数据分类 + +我们将构建一个支持向量机分类器,根据 14 个属性预测给定人员的收入等级。 我们的目标是查看年收入是高于还是低于$ 50,000。 因此,这是一个二进制分类问题。 我们将使用 [https://archive.ics.uci.edu/ml/datasets/Census+Income](https://archive.ics.uci.edu/ml/datasets/Census+Income) 上提供的普查收入数据集。 此数据集中需要注意的一项是,每个数据点都是单词和数字的混合体。 我们不能使用原始格式的数据,因为算法不知道如何处理单词。 我们无法使用标签编码器来转换所有内容,因为数字数据很有价值。 因此,我们需要结合使用标签编码器和原始数值数据来构建有效的分类器。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt from sklearn import preprocessing from sklearn.svm +import LinearSVC +from sklearn.multiclass import OneVsOneClassifier +from sklearn import cross_validation +``` + +我们将使用文件`income_data.txt`加载数据。 此文件包含收入详细信息: + +```py +# Input file containing +data input_file = 'income_data.txt' +``` + +为了从文件中加载数据,我们需要对其进行预处理以准备进行分类。 每个类别最多使用 25,000 个数据点: + +```py +# Read the data X = [] +y = [] +count_class1 = 0 +count_class2 = 0 +max_datapoints = 25000 +``` + +打开文件并开始阅读以下行: + +```py +with open(input_file, 'r') as f: + for line in f.readlines(): + if count_class1 >= max_datapoints and count_class2 >= max_datapoints: + break + if '?' in line: + continue +``` + +每行都用逗号分隔,因此我们需要对其进行相应的拆分。 每行的最后一个元素代表标签。 根据该标签,我们将其分配给一个类: + +```py + data = line[:-1].split(', ') + + if data[-1] == '<=50K' and count_class1 < max_datapoints: + X.append(data) + count_class1 += 1 + + if data[-1] == '>50K' and count_class2 < max_datapoints: + X.append(data) + count_class2 += 1 +``` + +将列表转换为`numpy`数组,以便可以将其用作`sklearn`函数的输入: + +```py +# Convert to numpy array +X = np.array(X) +``` + +如果任何属性是字符串,则需要对其进行编码。 如果是数字,则可以原样保留。 请注意,我们最终将使用多个标签编码器,并且需要跟踪所有这些标签编码器: + +```py +# Convert string data to numerical data +label_encoder = [] +X_encoded = np.empty(X.shape) +for i,item in enumerate(X[0]): + if item.isdigit(): + X_encoded[:, i] = X[:, i] + else: + label_encoder.append(preprocessing.LabelEncoder()) + X_encoded[:, i] = label_encoder[-1].fit_transform(X[:, i]) +X = X_encoded[:, :-1].astype(int) +y = X_encoded[:, -1].astype(int) +``` + +使用线性内核创建`SVM`分类器: + +```py +# Create SVM classifier +classifier = OneVsOneClassifier(LinearSVC(random_state=0)) +``` + +训练分类器: + +```py +# Train the classifier +classifier.fit(X, y) +``` + +使用 80/20 分割进行交叉验证以进行训练和测试,然后预测训练数据的输出: + +```py +# Cross validation +X_train, X_test, y_train, y_test = cross_validation.train_test_split(X, y, test_size=0.2, random_state=5) +classifier = OneVsOneClassifier(LinearSVC(random_state=0)) classifier.fit(X_train, y_train) +y_test_pred = classifier.predict(X_test) +``` + +计算分类器的 F1 分数: + +```py +# Compute the F1 score of the SVM classifier +f1 = cross_validation.cross_val_score(classifier, X, y, scoring='f1_weighted', cv=3) +print("F1 score: " + str(round(100*f1.mean(), 2)) + "%") +``` + +现在分类器已经准备就绪,让我们看看如何获​​取随机输入数据点并预测输出。 让我们定义一个这样的数据点: + +```py +# Predict output for a test datapoint +input_data = ['37', 'Private', '215646', 'HS-grad', '9', 'Never-married', 'Handlers-cleaners', 'Not-in-family', 'White', 'Male', '0', '0', '40', 'United-States'] +``` + +在执行预测之前,需要使用之前创建的标签编码器对数据点进行编码: + +```py +# Encode test datapoint +input_data_encoded = [-1] * len(input_data) +count = 0 +for i, item in enumerate(input_data): + if item.isdigit(): + input_data_encoded[i] = int(input_data[i]) + else: + input_data_encoded[i] = int(label_encoder[count].transform(input_data[i])) + count += 1 +``` + +```py +input_data_encoded = np.array(input_data_encoded) +``` + +现在我们准备使用分类器预测输出: + +```py +# Run classifier on encoded datapoint and print output +predicted_class = classifier.predict(input_data_encoded) +print(label_encoder[-1].inverse_transform(predicted_class)[0]) +``` + +如果运行代码,则将花费几秒钟来训练分类器。 完成后,您将看到以下输出: + +```py +F1 score: 66.82% +``` + +您还将看到测试数据点的输出: + +```py +<=50K +``` + +如果检查该数据点中的值,您将看到它与小于 50K 类中的数据点紧密对应。 您可以通过使用不同的内核并尝试参数的多种组合来更改分类器的性能(F1 得分,准确性或查全率)。 + +该部分的代码在文件`income_classifier.py`中给出。 + +# 什么是回归? + +**回归**是估算输入和输出变量之间关系的过程。 需要注意的一项是输出变量是连续值实数。 因此,存在无限的可能性。 这与分类相反,在分类中,输出类别的数量是固定的。 这些类属于一组有限的可能性。 + +在回归中,假设输出变量取决于输入变量,因此我们想看看它们是如何关联的。 因此,输入变量称为*独立变量*,也称为,称为*预测变量*,输出变量称为,称为*因变量*,也称为 *标准变量*。 不需要输入变量彼此独立; 实际上,在许多情况下,输入变量之间存在相关性。 + +回归分析有助于我们理解当我们更改某些输入变量而其他输入变量保持固定时输出变量的值如何变化。 在线性回归中,我们假设输入和输出之间的关系是线性的。 这给我们的建模过程带来了限制,但是它是快速高效的。 + +有时,线性回归不足以解释输入和输出之间的关系。 因此,我们使用多项式回归,其中我们使用多项式来解释输入和输出之间的关系。 这在计算上更加复杂,但提供了更高的精度。 根据当前的问题,我们使用不同形式的回归来提取关系。 回归通常用于预测价格,经济型麦克风,价格变动等等。 + +# 建立单变量回归器 + +让我们看看如何构建单个变量回归模型。 创建一个新的 Python 文件并导入以下软件包: + +```py +import pickle +``` + +```py +import numpy as np +from sklearn import linear_model +import sklearn.metrics as sm +import matplotlib.pyplot as plt +``` + +我们将使用提供给您的文件`data_singlevar_regr.txt`。 这是我们的数据源: + +```py +# Input file containing data +input_file = 'data_singlevar_regr.txt' +``` + +这是一个逗号分隔的文件,因此我们可以使用单行函数调用轻松加载它: + +```py +# Read data +data = np.loadtxt(input_file, delimiter=',') +X, y = data[:, :-1], data[:, -1] +``` + +将其分为培训和测试: + +```py +# Train and test split +num_training = int(0.8 * len(X)) +num_test = len(X) - num_training +``` + +```py +# Training data +X_train, y_train = X[:num_training], y[:num_training] +``` + +```py +# Test data +X_test, y_test = X[num_training:], y[num_training:] +``` + +创建一个线性回归对象并使用训练数据对其进行训练: + +```py +# Create linear regressor object +regressor = linear_model.LinearRegression() +``` + +```py +# Train the model using the training sets +regressor.fit(X_train, y_train) +``` + +使用训练模型预测测试数据集的输出: + +```py +# Predict the output +y_test_pred = regressor.predict(X_test) +``` + +绘制输出: + +```py +# Plot outputs +plt.scatter(X_test, y_test, color='green') +plt.plot(X_test, y_test_pred, color='black', linewidth=4) +plt.xticks(()) +plt.yticks(()) +plt.show() +``` + +通过将参考实际输出的基本事实与预测输出进行比较,计算回归器的性能指标: + +```py +# Compute performance metrics +print("Linear regressor performance:") +print("Mean absolute error =", round(sm.mean_absolute_error(y_test, y_test_pred), 2)) +print("Mean squared error =", round(sm.mean_squared_error(y_test, y_test_pred), 2)) +print("Median absolute error =", round(sm.median_absolute_error(y_test, y_test_pred), 2)) +print("Explain variance score =", round(sm.explained_variance_score(y_test, y_test_pred), 2)) +print("R2 score =", round(sm.r2_score(y_test, y_test_pred), 2)) +``` + +创建模型后,我们可以将其保存到文件中,以便以后使用。 Python 提供了一个名为`pickle`的不错的模块,使我们能够执行此操作: + +```py +# Model persistence +output_model_file = 'model.pkl' +``` + +```py +# Save the model +with open(output_model_file, 'wb') as f: + pickle.dump(regressor, f) +``` + +让我们从磁盘上的文件加载模型并执行预测: + +```py +# Load the model +with open(output_model_file, 'rb') as f: + regressor_model = pickle.load(f) +``` + +```py +# Perform prediction on test data +y_test_pred_new = regressor_model.predict(X_test) +print("\nNew mean absolute error =", round(sm.mean_absolute_error(y_test, y_test_pred_new), 2)) +``` + +如果运行代码,将会看到以下屏幕: + +![](img/B15441_05_10.png) + +图 10:执行预测后的输出 + +您应该看到以下输出: + +```py +Linear regressor performance: +Mean absolute error = 0.59 +Mean squared error = 0.49 +Median absolute error = 0.51 +Explain variance score = 0.86 +R2 score = 0.86 +New mean absolute error = 0.59 +``` + +**平均绝对误差**( **MAE** )是绝对误差的平均值: + +![](img/B15441_05_002.png) + +其中 *y* i 是预测值, *x* i 是实际值。 + +**均方误差**( **MSE** )是误差平方的平均值,即,预测值和实际值之间的均方差。 由于随机性,MSE 几乎总是严格为正(而不是零)。 MSE 是评估器质量的度量。 它始终是非负值,并且值越接近零越好。 + +解释的差异衡量模型占数据集中差异的比例。 通常,将变异量化为方差; 还可以使用更具体的术语解释方差。 总变化的其余部分是无法解释的或剩余的变化。 + +确定系数或 R2 分数用于分析如何通过第二个变量的差异解释一个变量的差异。 例如,如果一个女人怀孕了,那与她们生孩子的相关性很高。 + +该部分的代码在文件`regressor_singlevar.py`中给出。 + +# 建立多元回归 + +在的上一部分中,我们讨论了如何为单个变量构建回归模型。 在本节中,我们将处理多维数据。 创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +from sklearn import linear_model +import sklearn.metrics as sm +from sklearn.preprocessing import PolynomialFeatures +``` + +我们将使用提供给您的文件`data_multivar_regr.txt`。 + +```py +# Input file containing data +input_file = 'data_multivar_regr.txt' +``` + +这是一个逗号分隔的文件,因此我们可以通过单行函数调用轻松加载它: + +```py +# Load the data from the input file +data = np.loadtxt(input_file, delimiter=',') +X, y = data[:, :-1], data[:, -1] +``` + +将数据分为训练和测试: + +```py +# Split data into training and testing +num_training = int(0.8 * len(X)) +num_test = len(X) - num_training +``` + +```py +# Training data +X_train, y_train = X[:num_training], y[:num_training] +``` + +```py +# Test data +X_test, y_test = X[num_training:], y[num_training:] +``` + +创建并训练线性回归模型: + +```py +# Create the linear regressor model +linear_regressor = linear_model.LinearRegression() +``` + +```py +# Train the model using the training sets +linear_regressor.fit(X_train, y_train)# +``` + +预测测试数据集的输出: + +```py +# Predict the output +y_test_pred = linear_regressor.predict(X_test) +``` + +打印性能指标: + +```py +# Measure performance +print("Linear Regressor performance:") +print("Mean absolute error =", round(sm.mean_absolute_error(y_test, y_test_pred), 2)) +print("Mean squared error =", round(sm.mean_squared_error(y_test, y_test_pred), 2)) +print("Median absolute error =", round(sm.median_absolute_error(y_test, y_test_pred), 2)) +print("Explained variance score =", round(sm.explained_variance_score(y_test, y_test_pred), 2)) +print("R2 score =", round(sm.r2_score(y_test, y_test_pred), 2)) +``` + +创建度数为 10 的多项式回归器。在训练数据集上训练回归器。 让我们以一个样本数据点为例,看看如何执行预测。 第一步是将其转换为多项式: + +```py +# Polynomial regression +polynomial = PolynomialFeatures(degree=10) +X_train_transformed = polynomial.fit_transform(X_train) +datapoint = [[7.75, 6.35, 5.56]] +poly_datapoint = polynomial.fit_transform(datapoint) +``` + +如果仔细观察,该数据点非常接近我们数据文件中第 11 行的数据点,即 *[7.66,6.29,5.66]* 。 因此,一个好的回归器应该预测接近 *41.35* 的输出。 创建一个线性回归对象并执行多项式拟合。 使用线性和多项式回归器执行预测以查看差异: + +```py +poly_linear_model = linear_model.LinearRegression() +poly_linear_model.fit(X_train_transformed, y_train) +print("\nLinear regression:\n", linear_regressor.predict(datapoint)) +print("\nPolynomial regression:\n", poly_linear_model.predict(poly_datapoint)) +``` + +如果运行代码,则输出应如下所示: + +```py +Linear Regressor performance: +Mean absolute error = 3.58 +Mean squared error = 20.31 +Median absolute error = 2.99 +Explained variance score = 0.86 +R2 score = 0.86 +``` + +您还将看到以下: + +```py +Linear regression: + [ 36.05286276] +Polynomial regression: + [ 41.46961676] +``` + +如您所见,线性回归为`36.05`; 多项式回归系数接近 41.35,因此多项式回归模型能够做出更好的预测。 + +该部分的代码在文件`regressor_multivar.py`中给出。 + +# 使用支持向量回归器估算房价 + +让我们看看如何使用 SVM 概念构建回归变量来估算房价。 我们将使用`sklearn`中可用的数据集,其中每个数据点由 13 个属性定义。 + +我们的目标是根据这些属性估算住房价格。 创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +from sklearn import datasets +from sklearn.svm import SVR +from sklearn.metrics import mean_squared_error, explained_variance_score +from sklearn.utils import shuffle +``` + +加载住房数据集: + +```py +# Load housing data +data = datasets.load_boston() +``` + +让我们对数据进行混洗,以免产生偏差: + +```py +# Shuffle the data +X, y = shuffle(data.data, data.target, random_state=7) +``` + +将数据集以 80/20 格式分为训练和测试: + +```py +# Split the data into training and testing datasets +num_training = int(0.8 * len(X)) +X_train, y_train = X[:num_training], y[:num_training] +X_test, y_test = X[num_training:], y[num_training:] +``` + +使用线性核创建并训练支持向量回归器。 `C`参数代表训练错误的代价。 如果增加`C`的值,则模型将对其进行微调以适合训练数据。 但这可能会导致过度拟合并使其失去通用性。 epsilon 参数指定阈值; 如果预测值在与实际值的距离之内,则不会对训练错误造成任何损失: + +```py +# Create Support Vector Regression model +sv_regressor = SVR(kernel='linear', C=1.0, epsilon=0.1) +``` + +```py +# Train Support Vector Regressor +sv_regressor.fit(X_train, y_train) +``` + +评估回归器的性能并打印指标: + +```py +# Evaluate performance of Support Vector Regressor +y_test_pred = sv_regressor.predict(X_test) +mse = mean_squared_error(y_test, y_test_pred) +evs = explained_variance_score(y_test, y_test_pred) +print("\n#### Performance ####") +print("Mean squared error =", round(mse, 2)) +print("Explained variance score =", round(evs, 2)) +``` + +让我们以测试数据点进行预测: + +```py +# Test the regressor on test datapoint +test_data = [3.7, 0, 18.4, 1, 0.87, 5.95, 91, 2.5052, 26, 666, 20.2, +351.34, 15.27] +print("\nPredicted price:", sv_regressor.predict([test_data])[0]) +``` + +如果运行代码,则应看到以下输出: + +```py +#### Performance #### +Mean squared error = 15.41 +Explained variance score = 0.82 +Predicted price: 18.5217801073 +``` + +该部分的代码在文件`house_prices.py`中给出。 查看文件的第一行,查看`18.52`的预测与实际 t arget 变量的接近程度。 + +# 摘要 + +在本章中,我们了解了监督学习和无监督学习之间的区别。 我们讨论了数据分类问题以及如何解决它。 我们了解了如何使用各种方法预处理数据。 我们还学习了标签编码以及如何构建标签编码器。 我们讨论了逻辑回归并构建了逻辑回归分类器。 我们了解了朴素的贝叶斯分类器是什么,并学习了如何建立分类器。 我们还学习了如何构建混淆矩阵。 + +我们讨论了支持向量机,并了解了如何基于该向量构建分类器。 我们了解了回归,并了解了如何对单变量和多变量数据使用线性和多项式回归。 然后,我们使用支持向量回归器通过输入属性来估算房屋价格。 + +在下一章中,我们将学习预测分析以及如何使用集成学习构建预测引擎。 \ No newline at end of file diff --git a/docs/ai-py/06.md b/docs/ai-py/06.md new file mode 100644 index 0000000000000000000000000000000000000000..81ce85a42e68ce390a4666e5b64c2f425d6ffcfc --- /dev/null +++ b/docs/ai-py/06.md @@ -0,0 +1,886 @@ +# 6 + +# 集成学习的预测分析 + +在本章中,我们将学习集成学习以及如何将其用于预测分析。 在本章的最后,您将对这些主题有更好的理解: + +* 决策树和决策树分类器 +* 整体学习学习模型 +* 随机森林和极随机森林 +* 预测的置信度估计 +* 处理阶级失衡 +* 使用网格搜索找到最佳训练参数 +* 计算相对特征重要性 +* 使用极其随机的森林回归量进行交通量预测 + +让我们从决策树开始。 首先,它们是什么? + +# 什么是决策树? + +**决策树**是将数据集划分为不同分支的方法。 然后遍历分支或分区以做出简单的决定。 决策树是由训练算法生成的,该算法确定了如何以最佳方式拆分数据。 + +决策过程从树顶部的根节点开始。 树中的每个节点都是决策规则。 算法基于输入数据和训练数据中目标标签之间的关系构造这些规则。 输入数据中的值用于估计输出值。 + +现在我们了解了决策树的基本概念,接下来要了解的概念是如何自动构建决策树。 我们需要可以根据数据构造最佳树的算法。 为了理解它,我们需要了解熵的概念。 在本文中,熵是指信息熵,而不是热力学熵。 信息熵基本上是不确定性的量度。 决策树的主要目标之一是减少从根节点到叶节点的不确定性。 当我们看到未知的数据点时,我们将完全不确定输出。 到叶子节点时,我们就可以确定输出了。 这意味着需要以减少每个级别的不确定性的方式构造决策树。 这意味着我们在沿着树前进时需要减少熵。 + +您可以在[上了解有关的更多信息,网址为 https://prateekvjoshi.com/2016/03/22/how-are-decision-trees-constructed-in-machine-learning](https://prateekvjoshi.com/2016/03/22/how-are-decision-trees-constructed-in-machine-learning) + +## 建立决策树分类器 + +让我们看看如何使用 Python 中的决策树构建分类器。 创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from sklearn.metrics import classification_report +from sklearn.model_selection import train_test_split +from sklearn.tree import DecisionTreeClassifier +``` + +```py +from utilities import visualize_classifier +``` + +我们将使用提供给您的`data_decision_trees.txt`文件中的数据。 在此文件中,每一行都包含逗号分隔的值。 前两个值对应于输入数据,最后一个值对应于目标标签。 让我们从该文件加载数据: + +```py +# Load input data +input_file = 'data_decision_trees.txt' +data = np.loadtxt(input_file, delimiter=',') +X, y = data[:, :-1], data[:, -1] +``` + +根据标签将输入数据分为两个单独的类: + +```py +# Separate input data into two classes based on labels +class_0 = np.array(X[y==0]) +class_1 = np.array(X[y==1]) +``` + +让我们使用散点图可视化输入数据: + +```py +# Visualize input data +plt.figure() +plt.scatter(class_0[:, 0], class_0[:, 1], s=75, facecolors='black', + edgecolors='black', linewidth=1, marker='x') +plt.scatter(class_1[:, 0], class_1[:, 1], s=75, facecolors='white', + edgecolors='black', linewidth=1, marker='o') +plt.title('Input data') +``` + +我们需要将数据分为训练和测试数据集: + +```py +# Split data into training and testing datasets +X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.25, random_state=5) +``` + +基于训练数据集创建,构建和可视化决策树分类器。 `random_state`参数是指决策树分类算法初始化所需的随机数生成器使用的种子。 `max_depth`参数是指我们要构造的树的最大深度: + +```py +# Decision Trees classifier +params = {'random_state': 0, 'max_depth': 4} +classifier = DecisionTreeClassifier(**params) +classifier.fit(X_train, y_train) +visualize_classifier(classifier, X_train, y_train, 'Training dataset') +``` + +计算测试数据集上分类器的输出并将其可视化: + +```py +y_test_pred = classifier.predict(X_test) +visualize_classifier(classifier, X_test, y_test, 'Test dataset') +``` + +通过打印分类报告来评估分类器的性能: + +```py +# Evaluate classifier performance +class_names = ['Class-0', 'Class-1'] +print("\n" + "#"*40) +print("\nClassifier performance on training dataset\n") +print(classification_report(y_train, classifier.predict(X_train), target_names=class_names)) +print("#"*40 + "\n") +``` + +```py +print("#"*40) +print("\nClassifier performance on test dataset\n") +print(classification_report(y_test, y_test_pred, target_names=class_names)) +print("#"*40 + "\n") +``` + +```py +plt.show() +``` + +完整代码在`decision_trees.py`文件中给出。 如果运行代码,您将看到一些数字。 第一个屏幕截图是输入数据的可视化: + +![](img/B15441_06_01.png) + +图 1:可视化输入数据 + +第二张屏幕截图显示了测试数据集上的分类器边界: + +![](img/B15441_06_02.png) + +图 2:测试数据集的分类器边界 + +您还将看到以下输出: + +![](img/B15441_06_03.png) + +图 3:训练数据集上的分类器性能 + +分类器的性能以`precision`,`recall`和`f1-scores`为特征。 精度是指分类的准确性,召回率是指检索到的项目数占应检索的项目总数的百分比。 好的分类器将具有较高的精度和较高的查全率,但是通常在这两者之间需要权衡。 因此,我们有`f1-score`来表征。 F1 分数是精度和查全率的谐波平均值,使 F1 分数在精度和查全率之间取得了很好的平衡。 + +决策树是使用单个模型进行预测的示例。 通过组合和汇总多个模型的结果,有时可以创建更强大的模型和更好的预测。 一种方法是使用集成学习,这将在下一节中讨论。 + +# 什么是整体学习? + +**集成学习**涉及建立多个模型,然后以使其产生比模型单独产生的结果更好的方式组合它们。 这些单独的模型可以是分类器,回归器或其他模型。 + +集成学习已广泛应用于多个领域,包括数据分类,预测建模和异常检测。 + +那么为什么要使用集成学习呢? 为了获得理解,让我们使用一个真实的例子。 您想购买的新电视,但您不知道最新的型号是什么。 您的目标是使钱物有所值,但您对此主题的知识不足,无法做出明智的决定。 当您必须做出类似决定时,您可能会得到该领域内多位专家的意见。 这将帮助您做出最佳决定。 通常,您可以不依靠一种意见,而可以结合这些专家的个人决定来做出决定。 这样做可最大程度地减少错误或次优 l 决策的可能性。 + +## 建立具有整体学习的学习模型 + +选择模型时,常用的程序是选择训练数据集上误差最小的模型。 这种方法的问题在于,它并不总是有效。 该模型可能会出现偏差或过度拟合训练数据。 即使使用交叉验证来训练模型,它也可能在未知数据上表现不佳。 + +整体学习模型之所以有效,是因为它们降低了选择不良模型的总体风险。 这使它能够以多种方式训练,然后在未知数据上表现良好。 使用集成学习构建模型时,各个模型需要表现出一定的多样性。 这使他们能够捕获数据中的各种细微差别。 因此整体模型变得更加准确。 + +通过为每个模型使用不同的训练参数来实现多样性。 这允许各个模型为训练数据生成不同的决策边界。 这意味着每个模型将使用不同的规则进行推断,这是验证结果的有效方法。 如果模型之间存在一致性,则可以增加预测的可信度。 + +集成学习的一种特殊类型是将决策树组合成一个集成。 这些模型通常被称为随机森林和极随机森林,我们将在 com 。 + +# 什么是随机森林和极随机森林? + +**随机森林**是集成学习的实例,其中使用决策树构造单个模型。 然后,将这种决策树集合用于预测输出值。 我们使用训练数据的随机子集来构建每个决策树。 + +这将确保各种决策树之间的多样性。 在第一部分中,我们讨论了构建良好的整体学习模型时最重要的属性之一是确保各个模型之间存在多样性。 + +随机森林的优势之一是它们不会过度拟合。 过度拟合是机器学习中的常见问题。 非参数和非线性模型在学习目标函数时具有更大的灵活性,因此过度拟合的可能性更大。 通过使用各种随机子集构建一组多样化的决策树,我们确保模型不会过度拟合训练数据。 在树的构造过程中,将节点连续拆分,并选择最佳阈值以减小每个级别的熵。 此拆分未考虑输入数据集中的所有要素。 取而代之的是,它在考虑中的特征的随机子集中选择最佳分割。 添加此随机性往往会增加随机森林的偏差,但由于求平均值,方差会减小。 因此,我们最终得到了一个健壮的模型。 + +**极随机森林**将随机性提高到一个新水平。 除了采用特征的随机子集外,还随机选择阈值。 选择这些随机生成的阈值作为分割规则,这将进一步减小模型的方差。 因此,与使用随机森林获得的决策边界相比,使用高度随机(HTG3)m 森林获得的决策边界趋于平滑。 极随机森林算法的某些实现还可以实现更好的并行化,并更好地扩展规模。 + +## 建立随机森林和极端随机森林分类器 + +让我们看看如何可以基于随机森林和极其随机森林构建分类器。 构造两个分类器的方法非常相似,因此使用输入标志来指定需要构建哪个分类器。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import argparse +``` + +```py +import numpy as np +import matplotlib.pyplot as plt +from sklearn.metrics import classification_report +from sklearn.model_selection import train_test_split +from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier +from sklearn.metrics import classification_report +``` + +```py +from utilities import visualize_classifier +``` + +为 Python 定义一个参数解析器,以便我们可以将分类器类型作为输入参数。 依靠此参数,我们可以构造一个随机森林分类器或一个非常随机的森林分类器: + +```py +# Argument parser +def build_arg_parser(): + parser = argparse.ArgumentParser(description='Classify data using \ + Ensemble Learning techniques') + parser.add_argument('--classifier-type', dest='classifier_type', + required=True, choices=['rf', 'erf'], help="Type of classifier \ + to use; can be either 'rf' or 'erf'") + return parser +``` + +定义`main`函数并解析输入参数: + +```py +if __name__=='__main__': + # Parse the input arguments + args = build_arg_parser().parse_args() + classifier_type = args.classifier_type +``` + +我们将使用提供给您的`data_random_forests.txt`文件中的数据。 该文件中的每一行都包含逗号分隔的值。 前两个值对应于输入数据,最后一个值对应于目标标签。 在此数据集中,我们有三个不同的类。 让我们从该文件加载数据: + +```py + # Load input data + input_file = 'data_random_forests.txt' + data = np.loadtxt(input_file, delimiter=',') + X, y = data[:, :-1], data[:, -1] +``` + +将输入数据分为三类: + +```py + # Separate input data into three classes based on labels + class_0 = np.array(X[y==0]) + class_1 = np.array(X[y==1]) + class_2 = np.array(X[y==2]) +``` + +让我们可视化输入数据: + +```py + # Visualize input data + plt.figure() + plt.scatter(class_0[:, 0], class_0[:, 1], s=75, facecolors='white', + edgecolors='black', linewidth=1, marker='s') + plt.scatter(class_1[:, 0], class_1[:, 1], s=75, facecolors='white', + edgecolors='black', linewidth=1, marker='o') + plt.scatter(class_2[:, 0], class_2[:, 1], s=75, facecolors='white', + edgecolors='black', linewidth=1, marker='^') + plt.title('Input data') +``` + +将数据分为训练和测试数据集: + +```py + # Split data into training and testing datasets + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.25, random_state=5) +``` + +定义构造分类器时要使用的参数。 `n_estimators`参数表示将要构建的树的数量。 `max_depth`参数指的是每棵树中的最大级别数。 `random_state`参数指的是初始化随机森林分类器算法所需的随机数生成器的种子值: + +```py + # Ensemble Learning classifier + params = {'n_estimators': 100, 'max_depth': 4, 'random_state': 0} +``` + +根据输入参数,我们可以构造一个随机森林分类器或一个非常随机的森林分类器: + +```py + if classifier_type == 'rf': + classifier = RandomForestClassifier(**params) + else: + classifier = ExtraTreesClassifier(**params) +``` + +训练和可视化分类器: + +```py + classifier.fit(X_train, y_train) + visualize_classifier(classifier, X_train, y_train, 'Training dataset') +``` + +根据测试数据集计算输出并将其可视化: + +```py + y_test_pred = classifier.predict(X_test) + visualize_classifier(classifier, X_test, y_test, 'Test dataset') +``` + +通过打印分类报告来评估分类器的性能: + +```py + # Evaluate classifier performance + class_names = ['Class-0', 'Class-1', 'Class-2'] + print("\n" + "#"*40) + print("\nClassifier performance on training dataset\n") + print(classification_report(y_train, classifier.predict(X_train), target_names=class_names)) + print("#"*40 + "\n") + + print("#"*40) + print("\nClassifier performance on test dataset\n") + print(classification_report(y_test, y_test_pred, target_names=class_names)) + print("#"*40 + "\n") +``` + +完整的代码在`random_forests.py`文件中给出。 让我们使用输入参数中的`rf`标志,使用随机森林分类器运行代码。 运行以下命令: + +```py +$ python3 random_forests.py --classifier-type rf +``` + +您会看到一些数字弹出。 第一个屏幕截图是输入数据: + +![](img/B15441_06_04.png) + +图 4:可视化输入数据 + +在前面的屏幕截图中,这三个类分别由正方形,圆形和三角形表示。 我们看到类之间有很多重叠,但是现在应该没问题。 第二张屏幕截图显示了分类器边界: + +![](img/B15441_06_05.png) + +图 5:测试数据集上的分类器边界 + +现在,通过在输入参数中使用`erf`标志,使用极其随机的森林分类器运行代码。 运行以下命令: + +```py +$ python3 random_forests.py --classifier-type erf +``` + +您会看到一些数字弹出。 我们已经知道输入数据的样子。 第二张屏幕截图显示了分类器边界: + +![](img/B15441_06_06.png) + +图 6:测试数据集上的分类器边界 + +如果将前面的屏幕截图与从随机森林分类器获得的边界进行比较,您会发现这些边界更平滑。 原因是,极随机的森林在训练 p 的过程中拥有更多的自由来提出好的决策树,因此它们通常产生更好的边界。 + +## 估计预测的置信度 + +如果分析输出,将看到为每个数据点打印了概率。 这些概率用于测量每个类别的置信度值。 估计置信度值是机器学习中的重要任务。 在同一 Python 文件中,添加以下行以定义测试数据点的数组: + +```py + # Compute confidence + test_datapoints = np.array([[5, 5], [3, 6], [6, 4], [7, 2], [4, 4], [5, 2]]) +``` + +分类器对象具有一种内置方法来计算置信度。 让我们对每个点进行分类并计算置信度值: + +```py + print("\nConfidence measure:") + for datapoint in test_datapoints: + probabilities = classifier.predict_proba([datapoint])[0] + predicted_class = 'Class-' + str(np.argmax(probabilities)) + print('\nDatapoint:', datapoint) + print('Predicted class:', predicted_class) +``` + +根据分类器边界可视化测试数据点: + +```py + # Visualize the datapoints + visualize_classifier(classifier, test_datapoints, [0]*len(test_datapoints), + 'Test datapoints') +``` + +```py + plt.show() +``` + +如果运行带有`erf`标志的代码,您将获得以下输出: + +![](img/B15441_06_07.png) + +图 7:测试数据集上的分类器边界 + +没有`erf`标志,它将产生以下输出: + +![](img/B15441_06_08.png) + +图 8:数据集概率输出 + +对于每个数据点,它计算该点属于我们的三类的概率。 我们选择最有信心的一个。 使用`erf`标志运行代码,您将获得以下输出: + +![](img/B15441_06_09.png) + +图 9:测试数据集上的分类器边界 + +如果没有`erf`标志,则应产生以下输出: + +![](img/B15441_06_10.png) + +图 10:数据集概率输出 + +可以看出,输出由 t 与先前的结果组成。 + +# 处理班级失衡 + +分类器仅与用于训练的数据一样。 现实世界中面临的一个普遍问题是数据质量问题。 为了使分类器表现良好,每个分类器需要看到相等数量的点。 但是,当在现实世界中收集数据时,并不总是能够确保每个类都具有完全相同数量的数据点。 如果一个类别的数据点数是另一类别的 10 倍,则分类器倾向于偏向更多类别。 因此,我们需要确保考虑到这种不平衡算法。 让我们看看如何做到这一点。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import sys +``` + +```py +import numpy as np +import matplotlib.pyplot as plt +from sklearn.ensemble import ExtraTreesClassifier +from sklearn.model_selection import train_test_split +from sklearn.metrics import classification_report +``` + +```py +from utilities import visualize_classifier +``` + +我们将使用文件`data_imbalance.txt`中的数据进行分析。 让我们加载数据。 + +该文件中的每一行都包含逗号分隔的值。 前两个值对应于输入数据,最后一个值对应于目标标签。 我们在这个数据集中有两个类。 让我们从该文件加载数据: + +```py +# Load input data +input_file = 'data_imbalance.txt' +data = np.loadtxt(input_file, delimiter=',') +X, y = data[:, :-1], data[:, -1] +``` + +将输入数据分为两类: + +```py +# Separate input data into two classes based on labels +class_0 = np.array(X[y==0]) +class_1 = np.array(X[y==1]) +``` + +使用散点图可视化输入数据: + +```py +# Visualize input data +plt.figure() +plt.scatter(class_0[:, 0], class_0[:, 1], s=75, facecolors='black', + edgecolors='black', linewidth=1, marker='x') +plt.scatter(class_1[:, 0], class_1[:, 1], s=75, facecolors='white', + edgecolors='black', linewidth=1, marker='o') +plt.title('Input data') +``` + +将数据分为训练和测试数据集: + +```py +# Split data into training and testing datasets +X_train, X_test, y_train, y_test = train_test_split.train_test_split( + X, y, test_size=0.25, random_state=5) +``` + +接下来,我们为极随机森林分类器定义参数。 请注意,有一个称为`balance`的输入参数可控制是否通过算法解决类不平衡问题。 如果是这样,则需要添加另一个名为`class_weight`的参数,该参数告诉分类器它应该权衡权重,以便与每个类中的数据点数量成正比: + +```py +# Extremely Random Forests classifier +params = {'n_estimators': 100, 'max_depth': 4, 'random_state': 0} +if len(sys.argv) > 1: + if sys.argv[1] == 'balance': + params = {'n_estimators': 100, 'max_depth': 4, 'random_state': 0, 'class_weight': 'balanced'} + else: + raise TypeError("Invalid input argument; should be 'balance'") +``` + +使用训练数据构建,训练和可视化分类器: + +```py +classifier = ExtraTreesClassifier(**params) +classifier.fit(X_train, y_train) +visualize_classifier(classifier, X_train, y_train, 'Training dataset') +``` + +预测测试数据集的输出并可视化输出: + +```py +y_test_pred = classifier.predict(X_test) +visualize_classifier(classifier, X_test, y_test, 'Test dataset') +``` + +计算分类器的性能并打印分类报告: + +```py +# Evaluate classifier performance +class_names = ['Class-0', 'Class-1'] +print("\n" + "#"*40) +print("\nClassifier performance on training dataset\n") +print(classification_report(y_train, classifier.predict(X_train), +target_names=class_names)) +print("#"*40 + "\n") +``` + +```py +print("#"*40) +print("\nClassifier performance on test dataset\n") +print(classification_report(y_test, y_test_pred, target_names=class_names)) +print("#"*40 + "\n") +``` + +```py +plt.show() +``` + +完整代码在文件`class_imbalance.py`中给出。 如果运行代码,您将看到以下图形。 第一个 raph 表示输入数据: + +![](img/B15441_06_11.png) + +图 11:可视化输入数据 + +第二个图形显示了测试数据 t 的分类器边界: + +![](img/B15441_06_12.png) + +图 12:测试数据集上的分类器边界 + +前面的图形指示边界无法捕获两个类之间的实际边界。 顶部附近的黑色斑点代表边界。 最后,您应该看到以下输出: + +![](img/B15441_06_13.png) + +图 13:测试数据集上分类器的性能 + +您会看到一条警告,因为第一行的值为`0`,这在我们计算`f1-score`时会导致被零除的错误(`ZeroDivisionError`异常)。 使用`ignore`标志运行代码,以免看不到被零除警告: + +```py +$ python3 --W ignore class_imbalance.py +``` + +现在,如果要解决类不平衡的问题,请使用`balance`标志运行它: + +```py +$ python3 class_imbalance.py balance +``` + +分类器输出看起来像: + +![](img/B15441_06_14.png) + +图 14:带有平衡的测试数据集的可视化 + +您应该看到以下输出: + +![](img/B15441_06_15.png) + +图 15:测试数据集上分类器的性能 + +通过考虑的类别不平衡,我们能够对的类别进行分类,从而使`Class-0` 中的数据点的整体精度不为零。 + +# 使用网格搜索找到最佳训练参数 + +在使用分类器时,并非总是可能知道要使用什么最佳参数。 通过手动检查所有可能的组合来使用蛮力效率不高。 这是网格搜索变得有用的地方。 网格搜索使我们可以指定范围为的值,分类器将自动运行各种配置以找出参数的最佳组合。 让我们来看看如何做。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from sklearn.metrics import classification_report +from sklearn import grid_search +from sklearn.ensemble import ExtraTreesClassifier +from sklearn.model_selection import train_test_split +from sklearn.metrics import classification_report +``` + +```py +from utilities import visualize_classifier +``` + +我们将使用`data_random_forests.txt`中的可用数据进行分析: + +```py +# Load input data +input_file = 'data_random_forests.txt' +data = np.loadtxt(input_file, delimiter=',') +X, y = data[:, :-1], data[:, -1] +``` + +将数据分为三类: + +```py +# Separate input data into three classes based on labels +class_0 = np.array(X[y==0]) +class_1 = np.array(X[y==1]) +class_2 = np.array(X[y==2]) +``` + +将数据分为训练和测试数据集: + +```py +# Split the data into training and testing datasets +X_train, X_test, y_train, y_test = cross_validation.train_test_split( + X, y, test_size=0.25, random_state=5) +``` + +指定参数的网格供分类器测试。 通常,一个参数保持恒定,而另一个参数变化。 然后进行反演以找出最佳组合。 在这种情况下,我们想找到`n_estimators`和`max_depth`的最佳值。 让我们指定参数网格: + +```py +# Define the parameter grid +parameter_grid = [{'n_estimators':[100], 'max_depth':[2,4,7,12,16]}, + {'max_depth':[4],'n_estimators':[25,50,100,250]}] +``` + +让我们定义分类器用来找到最佳参数组合的指标: + +```py +metrics = ['precision_weighted', 'recall_weighted'] +``` + +对于每个指标,我们需要运行网格搜索,在其中训练分类器以获取参数组合: + +```py +for metric in metrics: + print("\n##### Searching optimal parameters for", metric) + + classifier = grid_search.GridSearchCV( + ExtraTreesClassifier(random_state=0), + parameter_grid, cv=5, scoring=metric) + classifier.fit(X_train, y_train) +``` + +打印每个参数组合的分数: + +```py + print("\nGrid scores for the parameter grid:") + for params, avg_score, _ in classifier.grid_scores_: + print(params, '-->', round(avg_score, 3)) + + print("\nBest parameters:", classifier.best_params_) +``` + +打印效果报告: + +```py + y_pred = classifier.predict(X_test) + print("\nPerformance report:\n") + print(classification_report(y_test, y_pred)) +``` + +完整代码在文件`run_grid_search.py`中给出。 如果运行代码,则将使用精度度量输出以下输出: + +![](img/B15441_06_16.png) + +图 16:最佳参数搜索输出 + +基于网格搜索中的组合,它将打印出精度度量的最佳组合。 为了找到召回的最佳组合,可以检查以下输出: + +![](img/B15441_06_17.png) + +图 17:最佳参数搜索输出 + +这是用于召回的不同组合,使成为一体,因为精度和召回率是对和不同参数组合进行界定的不同指标。 + +# 计算相对特征重要性 + +在将与包含 N 维数据点的数据集一起使用时,必须理解,并非所有功能都同样重要。 有些比其他更具歧视性。 如果我们有此信息,则可以使用它来减少维数。 这对于降低复杂度和提高算法速度很有用。 有时,一些功能是完全多余的。 因此,可以轻松地将它们从数据集中删除。 + +我们将使用`AdaBoost`回归器计算特征重要性。 AdaBoost 是 Adaptive Boosting 的缩写,是一种经常与其他机器学习算法结合使用以提高其性能的算法。 在 AdaBoost 中,从分布中提取训练数据点以训练当前分类器。 该分布会进行迭代更新,以便后续的分类器可以专注于更困难的数据点。 困难的数据点是那些分类错误的数据点。 这是通过在每个步骤更新发行版来完成的。 这将使先前被错误分类的数据点更有可能出现在用于训练的下一个样本数据集中。 + +然后将这些分类器进行级联,并通过加权多数投票做出决定。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from sklearn.tree import DecisionTreeRegressor +from sklearn.ensemble import AdaBoostRegressor +from sklearn import datasets +from sklearn.metrics import mean_squared_error, explained_variance_score +from sklearn.model_selection import import train_test_split +from sklearn.utils import shuffle +``` + +```py +from utilities import visualize_feature_importances +``` + +我们将使用 scikit-learn 中提供的内置房屋数据集: + +```py +# Load housing data +housing_data = datasets.load_boston() +``` + +对数据进行混洗,以使不会偏向于分析: + +```py +# Shuffle the data +X, y = shuffle(housing_data.data, housing_data.target, random_state=7) +``` + +将数据集分为训练和测试: + +```py +# Split data into training and testing datasets +X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.2, random_state=7) +``` + +使用决策树回归器作为单独模型来定义和训练`AdaBoostregressor`: + +```py +# AdaBoost Regressor model +regressor = AdaBoostRegressor(DecisionTreeRegressor(max_depth=4), + n_estimators=400, random_state=7) +regressor.fit(X_train, y_train) +``` + +估计回归器的性能: + +```py +# Evaluate performance of AdaBoost regressor +y_pred = regressor.predict(X_test) +mse = mean_squared_error(y_test, y_pred) +evs = explained_variance_score(y_test, y_pred ) +print("\nADABOOST REGRESSOR") +print("Mean squared error =", round(mse, 2)) +print("Explained variance score =", round(evs, 2)) +``` + +此回归器具有一个内置方法,可以调用该方法来计算相对特征的重要性: + +```py +# Extract feature importances +feature_importances = regressor.feature_importances_ +feature_names = housing_data.feature_names +``` + +归一化相对要素重要性的值: + +```py +# Normalize the importance values +feature_importances = 100.0 * (feature_importances / max(feature_importances)) +``` + +对它们进行排序,以便可以进行绘制: + +```py +# Sort the values and flip them +index_sorted = np.flipud(np.argsort(feature_importances)) +``` + +排列条形图在 x 轴上的刻度: + +```py +# Arrange the X ticks +pos = np.arange(index_sorted.shape[0]) + 0.5 +``` + +绘制条形图: + +```py +# Plot the bar graph +plt.figure() +plt.bar(pos, feature_importances[index_sorted], align='center') +plt.xticks(pos, feature_names[index_sorted]) +plt.ylabel('Relative Importance') +plt.title('Feature importance using AdaBoost regressor') +plt.show() +``` + +完整代码在文件`feature_importance.py`中给出。 如果运行代码,则应该看到以下输出: + +![](img/B15441_06_18.png) + +图 18:使用 Adaboost Regressor 的功能重要性 + +根据这一分析,特征 LSTAT 是该数据集中最重要的特征。 + +# 使用极为随机的森林回归器预测流量 + +让我们将在上一节中学习的概念应用于一个实际问题。 将使用位于的可用数据集: [https://archive.ics.uci.edu/ml/datasets/Dodgers+Loop+Sensor](https://archive.ics.uci.edu/ml/datasets/Dodgers+Loop+Sensor) 。 该数据集包含对在洛杉矶道奇体育场(Lod Angeles Dodgers Stadium)进行的棒球比赛中道路上经过的车辆进行计数的数据。 为了使数据易于分析,我们需要对其进行预处理。 预处理后的数据在文件`traffic_data.txt`中。 在此文件中,每一行均包含逗号分隔的字符串。 让我们以第一行为例: + +```py +Tuesday,00:00,San Francisco,no,3 +``` + +参考上一行,其格式如下: + +一周中的一天,一天中的时间,对手团队,指示棒球比赛当前正在进行的二进制值(是/否),经过的车辆数量。 + +我们的目标是通过使用给定的信息来预测车辆行驶的数量。 由于输出变量是连续值,因此我们需要构建一个可以预测输出的回归变量。 我们将使用极其随机的森林来构建此回归器。 让我们继续进行,看看如何做到这一点。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from sklearn.metrics import classification_report, mean_absolute_error +from sklearn import cross_validation, preprocessing +from sklearn.ensemble import ExtraTreesRegressor +from sklearn.metrics import classification_report +``` + +将数据加载到文件`traffic_data.txt`中: + +```py +# Load input data +input_file = 'traffic_data.txt' +data = [] +with open(input_file, 'r') as f: + for line in f.readlines(): + items = line[:-1].split(',') + data.append(items) +``` + +```py +data = np.array(data) +``` + +数据中的非数字特征需要进行编码。 不对数字特征进行编码也很重要。 每个需要编码的功能都需要有一个单独的标签编码器。 我们需要跟踪这些编码器,因为当我们要计算未知数据点的输出时将需要它们。 让我们创建那些标签编码器: + +```py +# Convert string data to numerical data +label_encoder = [] +X_encoded = np.empty(data.shape) +for i, item in enumerate(data[0]): + if item.isdigit(): + X_encoded[:, i] = data[:, i] + else: + label_encoder.append(preprocessing.LabelEncoder()) + X_encoded[:, i] = label_encoder[-1].fit_transform(data[:, i]) +X = X_encoded[:, :-1].astype(int) +y = X_encoded[:, -1].astype(int) +``` + +将数据分为训练和测试数据集: + +```py +# Split data into training and testing datasets +X_train, X_test, y_train, y_test = cross_validation.train_test_split( + X, y, test_size=0.25, random_state=5) +``` + +训练一个非常随机的森林回归器: + +```py +# Extremely Random Forests regressor +params = {'n_estimators': 100, 'max_depth': 4, 'random_state': 0} +regressor = ExtraTreesRegressor(**params) +regressor.fit(X_train, y_train) +``` + +根据测试数据计算回归器的性能: + +```py +# Compute the regressor performance on test data +y_pred = regressor.predict(X_test) +print("Mean absolute error:", round(mean_absolute_error(y_test, y_pred), 2)) +``` + +让我们看看如何计算未知数据点的输出。 标签编码器将用于将非数字特征转换为数值: + +```py +# Testing encoding on single data instance +test_datapoint = ['Saturday', '10:20', 'Atlanta', 'no'] +test_datapoint_encoded = [-1] * len(test_datapoint) +count = 0 +for i, item in enumerate(test_datapoint): + if item.isdigit(): + test_datapoint_encoded[i] = int(test_datapoint[i]) + else: + test_datapoint_encoded[i] = int(label_encoder[count].transform(test_datapoint[i])) + count = count + 1 +``` + +```py +data = np.array(data) +test_datapoint_encoded = np.array(test_datapoint_encoded) +``` + +预测输出: + +```py +# Predict the output for the test datapoint +print("Predicted traffic:", int(regressor.predict([test_datapoint_encoded])[0])) +``` + +完整代码在文件`traffic_prediction.py`中给出。 如果运行代码,则将获得`26`作为输出,接近实际值,并确认我们的模型正在做出不错的预测。 您可以从数据文件中确认。 + +# 摘要 + +在本章中,我们学习了集成学习及其在现实世界中的使用方式。 我们讨论了决策树以及如何基于决策树构建分类器。 + +我们了解了随机森林和极端随机森林,它们是由多个决策树组成的。 我们讨论了如何基于它们构建分类器。 我们了解了如何估计预测的置信度。 我们还学习了如何处理班级失衡问题。 + +我们讨论了如何找到最佳训练参数以使用网格搜索来构建模型。 我们学习了如何计算相对特征的重要性。 然后,我们将集成学习技术应用于一个实际问题,在该问题中,我们使用一个非常随机的森林回归量来预测流量。 + +在下一章中,我们将讨论无监督学习以及如何检测股市数据中的模式。 \ No newline at end of file diff --git a/docs/ai-py/07.md b/docs/ai-py/07.md new file mode 100644 index 0000000000000000000000000000000000000000..b28c8c0ab83b67a6e956cf36068da4ffff064af2 --- /dev/null +++ b/docs/ai-py/07.md @@ -0,0 +1,806 @@ +# 7 + +# 通过无监督学习检测模式 + +在本章中,我们将学习无监督学习以及如何在现实世界中使用它。 到本章末,您将对以下主题有更好的理解: + +* 无监督学习定义 +* 使用 K-Means 算法对数据进行聚类 +* 用均值漂移算法估计聚类数 +* 用轮廓分数估算聚类的质量 +* 高斯混合模型 +* 基于高斯混合模型构建分类器 +* 使用相似性传播模型在股票市场中寻找子群体 +* 根据购物模式细分市场 + +# 什么是无监督学习? + +无监督学习是指不使用标记的训练数据而构建机器学习模型的过程。 无监督学习可在各种研究领域中找到应用,包括市场细分,股票市场,自然语言处理和计算机视觉等。 + +在前面的章节中,我们处理的是带有相关标签的数据。 在标记了训练数据之后,算法会学习根据这些标记对数据进行分类。 在现实世界中,标记数据可能并不总是可用。 + +有时,存在大量未加标签的数据,需要以某种方式对其进行分类。 这是无监督学习的完美用例。 无监督学习算法尝试使用某种相似性度量将数据分类到给定数据集中的子组中。 + +当我们有一个没有任何标签的数据集时,我们假定该数据是由于以某种方式控制分布的潜在变量而生成的。 然后可以从各个数据点开始以分级方式进行学习过程。 我们可以通过查找相似性的自然簇并尝试通过对数据进行分类和分段来获取信号和见解,从而为数据提供更深层次的表示。 让我们看看使用无监督学习对数据进行分类的一些方法。 + +# 使用 K-Means 算法聚类数据 + +聚类是最流行的无监督学习技术之一。 此技术用于分析数据并在该数据中查找聚类。 为了找到这些聚类,我们使用相似性度量(例如欧几里得距离)来找到子组。 这种相似性度量可以估计群集的紧密度。 聚类是将数据组织到元素彼此相似的子组中的过程。 + +该算法的目标是识别使数据点属于同一子组的数据点的固有属性。 没有适用于所有情况的通用相似性指标。 例如,我们可能有兴趣查找每个子组的代表性数据点,或者我们有兴趣查找数据中的异常值。 根据情况,不同的指标可能比其他指标更合适。 + +K 均值算法是一种用于对数据进行聚类的众所周知的算法。 为了使用它,预先假定簇的数量。 使用各种数据属性将数据分为 *K* 子组。 簇的数量是固定的,并且根据该数量对数据进行分类。 这里的主要思想是我们需要在每次迭代时更新质心的位置。 重心是代表群集中心的位置。 我们继续进行迭代,直到将质心放置在其最佳位置 ns。 + +我们可以看到质心的初始位置在算法中起着重要的作用。 这些质心应该以巧妙的方式放置,因为这会直接影响结果。 一个好的策略是将它们放置在尽可能远的距离。 + +基本的 K 均值算法将这些质心随机放置在`K-Means++`从数据点的输入列表中从算法上选择这些点的位置。 它试图使初始质心彼此远离,以便它们快速收敛。 然后,我们遍历训练数据集并将每个数据点分配给最接近的质心。 + +一旦我们遍历了整个数据集,第一次迭代就结束了。 这些点已根据初始化的质心进行了分组。 根据在第一次迭代结束时获得的新簇重新计算质心的位置。 一旦获得一组新的 *K* 重心,便会重复该过程。 我们遍历数据集并将每个点分配给最近的质心。 + +随着步骤不断重复,质心继续移动到其平衡位置。 经过一定数量的迭代后,质心不再更改其位置。 重心会聚到最终位置。 这些 *K* 重心是将用于推断的值。 + +让我们对二维数据应用 K 均值聚类,以了解其工作原理。 我们将使用提供给您的`data_clustering.txt`文件中的数据。 每行包含两个逗号分隔的数字。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from sklearn.cluster import KMeans +from sklearn import metrics +``` + +从文件中加载输入数据: + +```py +# Load input data +X = np.loadtxt('data_clustering.txt', delimiter=',') +``` + +在应用 K-Means 算法之前定义簇数: + +```py +num_clusters = 5 +``` + +可视化输入数据以查看展开图的样子: + +```py +# Plot input data +plt.figure() +plt.scatter(X[:,0], X[:,1], marker='o', facecolors='none', + edgecolors='black', s=80) +x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1 +y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1 +plt.title('Input data') +plt.xlim(x_min, x_max) +plt.ylim(y_min, y_max) +plt.xticks(()) +plt.yticks(()) +``` + +可以看出该数据中有五个组。 使用初始化参数创建`KMeans`对象。 `init`参数表示选择簇的初始中心的初始化方法。 而不是随机选择它们,我们使用`k-means++`以更智能的方式选择这些中心。 这样可以确保算法快速收敛。 `n_clusters`参数是指群集数。 `n_init`参数是指算法在确定最佳结果之前应运行的次数: + +```py +# Create KMeans object +kmeans = KMeans(init='k-means++', n_clusters=num_clusters, n_init=10) +``` + +使用输入数据训练 K-Means 模型: + +```py +# Train the KMeans clustering model +kmeans.fit(X) +``` + +为了可视化边界,我们需要创建一个点网格并在所有这些点上评估模型。 让我们定义这个网格的步长: + +```py +# Step size of the mesh +step_size = 0.01 +``` + +我们定义点的网格,并确保我们覆盖了输入数据中的所有值: + +```py +# Define the grid of points to plot the boundaries +x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1 +y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1 +x_vals, y_vals = np.meshgrid(np.arange(x_min, x_max, step_size), + np.arange(y_min, y_max, step_size)) +``` + +使用训练好的 K 均值模型预测网格上所有点的输出: + +```py +# Predict output labels for all the points on the grid +output = kmeans.predict(np.c_[x_vals.ravel(), y_vals.ravel()]) +``` + +绘制所有输出值并为每个区域着色: + +```py +# Plot different regions and color them +output = output.reshape(x_vals.shape) +plt.figure() +plt.clf() +plt.imshow(output, interpolation='nearest', + extent=(x_vals.min(), x_vals.max(), + y_vals.min(), y_vals.max()), + cmap=plt.cm.Paired, + aspect='auto', + origin='lower') +``` + +在这些有色区域上方叠加输入数据点: + +```py +# Overlay input points +plt.scatter(X[:,0], X[:,1], marker='o', facecolors='none', + edgecolors='black', s=80) +``` + +绘制使用 K-Means 算法获得的聚类中心: + +```py +# Plot the centers of clusters +cluster_centers = kmeans.cluster_centers_ +plt.scatter(cluster_centers[:,0], cluster_centers[:,1], + marker='o', s=210, linewidths=4, color='black', + zorder=12, facecolors='black') +x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1 +y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1 +plt.title('Boundaries of clusters') +plt.xlim(x_min, x_max) +plt.ylim(y_min, y_max) +plt.xticks(()) +plt.yticks(()) +plt.show() +``` + +完整的代码在`kmeans.py`文件的中给出。 如果运行代码,您将看到两个屏幕截图。 第一个屏幕截图是输入数据: + +![](img/B15441_07_01.png) + +图 1:可视化输入数据 + +第二个屏幕截图表示使用 K-Means 获得的边界: + +![](img/B15441_07_02.png) + +图 2:Kmeans 边界 + +每个群集中心的黑色填充圆圈表示该群集的质心。 + +涵盖了 K-Means 算法之后,我们现在将继续另一种方法-Mean Shi ft 算法。 + +## 使用均值漂移算法估算聚类数 + +**均值漂移**是用于无监督学习的功能强大的算法。 这是一种经常用于聚类的非参数算法。 它是非参数的,因为它不对基础分布进行任何假设。 这与参量技术形成对比,参量技术假定基础数据遵循标准概率分布。 Mean Shift 在对象跟踪和实时数据分析等领域中找到了许多应用程序。 + +在均值漂移算法中,整个特征空间被视为概率密度函数。 我们从训练数据集开始,并假设它是从概率密度函数中采样的。 + +在此框架中,聚类对应于基础分布的局部最大值。 如果存在 *K* 个簇,则基础数据分布中存在 *K* 个峰,均值漂移将识别这些峰。 + +Mean Shift 的目标是识别质心的位置。 对于训练数据集中的每个数据点,它在其周围定义一个窗口。 然后,它为此窗口计算质心,并将位置更新为该新质心。 然后,通过在新位置周围定义一个窗口,对该新位置重复该过程。 随着我们不断这样做,我们将更接近群集的峰值。 每个数据点都将移向其所属的群集。 运动正朝着更高密度的区域发展。 + +重心(也称为均值)不断移向每个簇的峰。 该算法的名字源于手段不断变化的事实。 这种变化一直持续到算法收敛为止,在此阶段,质心不再移动。 + +让我们看看如何使用`MeanShift`估算给定数据集中的最佳簇数。 `data_clustering.txt`文件中的数据将用于分析。 该文件与 K-Means 算法部分在*聚类数据中使用的文件相同。* + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from sklearn.cluster import MeanShift, estimate_bandwidth +from itertools import cycle +``` + +加载输入数据: + +```py +# Load data from input file +X = np.loadtxt('data_clustering.txt', delimiter=',') +``` + +估计输入数据的带宽。 带宽是 Mean Shift 算法中使用的底层内核密度估计过程的参数。 带宽会影响算法的整体收敛速度,并最终影响最终的簇数。 因此,这是一个关键参数-如果带宽太小,可能会导致群集过多,而如果值太大,则会合并不同的群集。 + +`quantile`参数影响估计带宽的方式。 分位数的较高值将增加估计的带宽,从而导致较少的群集: + +```py +# Estimate the bandwidth of X +bandwidth_X = estimate_bandwidth(X, quantile=0.1, n_samples=len(X)) +``` + +然后使用估计的带宽训练均值漂移聚类模型: + +```py +# Cluster data with MeanShift +meanshift_model = MeanShift(bandwidth=bandwidth_X, bin_seeding=True) +meanshift_model.fit(X) +``` + +提取所有群集的中心: + +```py +# Extract the centers of clusters +cluster_centers = meanshift_model.cluster_centers_ +print('\nCenters of clusters:\n', cluster_centers) +``` + +提取集群数: + +```py +# Estimate the number of clusters +labels = meanshift_model.labels_ +num_clusters = len(np.unique(labels)) +print("\nNumber of clusters in input data =", num_clusters) +``` + +可视化和数据点: + +```py +# Plot the points and cluster centers +plt.figure() +markers = 'o*xvs' +for i, marker in zip(range(num_clusters), markers): + # Plot points that belong to the current cluster + plt.scatter(X[labels==i, 0], X[labels==i, 1], marker=marker, color='black') +``` + +绘制当前群集的中心: + +```py + # Plot the cluster center + cluster_center = cluster_centers[i] + plt.plot(cluster_center[0], cluster_center[1], marker='o', + markerfacecolor='black', markeredgecolor='black', + markersize=15) +plt.title('Clusters') +plt.show() +``` + +完整代码在`mean_shift.py`文件中给出。 如果运行代码,您将看到以下屏幕快照,代表群集及其中心: + +![](img/B15441_07_03.png) + +图 3:聚类图的中心 + +您将看到以下输出:: + +![](img/B15441_07_04.png) + +图 4:集群输出的中心 + +至此,我们完成了均值漂移的概述。 到目前为止,我们已经讨论了如何对数据进行聚类。 接下来,我们将继续介绍如何使用 e Silhouette 方法估算聚类的质量。 + +## 使用轮廓分数估算聚类的质量 + +如果数据自然地组织成几个不同的簇,那么很容易在视觉上对其进行检查并得出一些推论。 不幸的是,在现实世界中很少如此。 现实世界中的数据庞大而混乱。 因此,我们需要一种量化聚类质量的方法。 + +剪影是指用于检查数据中群集一致性的方法。 它提供了每个数据点与其群集的融合程度的估计。 轮廓分数是衡量数据点与其自身群集(与其他群集相比)的相似性的度量。 轮廓分数适用于任何相似度指标。 + +对于每个数据点,使用以下公式计算轮廓分数: + +*轮廓分数=(p – q)/ max(p,q)* + +这里, *p* 是到数据点不属于的最近群集中各点的平均距离, *q* 是到所有点的平均群集内距离在其自己的群集中。 + +轮廓分数范围的值在 *-1* 和 *1* 之间。 接近 *1* 的分数表示该数据点与群集中的其他数据点非常相似,而接近 *-1* 的分数指示该数据点与其他数据不同 点在集群中。 一种思考的方法是,如果有太多带有负轮廓分数的点,那么数据中的簇可能太少或太多。 我们需要再次运行聚类算法以找到最佳数目的聚类。 理想情况下,我们希望具有较高的正值。 根据业务问题,我们不需要优化并具有尽可能高的价值,但是通常,如果我们的剪影得分接近 *1* ,则表明数据可以很好地聚类。 如果分数接近 *-1* ,则表明我们用于分类的变量有噪声,并且不包含太多信号。 + +让我们看看如何使用轮廓分数来估计聚类性能。 创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from sklearn import metrics +from sklearn.cluster import KMeans +``` + +我们将使用提供给您的`data_quality.txt`文件中的数据。 每行包含两个逗号分隔的数字: + +```py +# Load data from input file +X = np.loadtxt('data_quality.txt', delimiter=',') +``` + +初始化变量。 `values`数组将包含一个值列表,以进行迭代并找到最佳簇数: + +```py +# Initialize variables +scores = [] +values = np.arange(2, 10) +``` + +遍历所有值并在每次迭代期间构建 K-Means 模型: + +```py +# Iterate through the defined range +for num_clusters in values: + # Train the KMeans clustering model + kmeans = KMeans(init='k-means++', n_clusters=num_clusters, n_init=10) + kmeans.fit(X) +``` + +使用欧几里德距离度量来估计当前聚类模型的轮廓分数: + +```py + score = metrics.silhouette_score(X, kmeans.labels_, + metric='euclidean', sample_size=len(X)) +``` + +打印轮廓分数作为当前值: + +```py + print("\nNumber of clusters =", num_clusters) + print("Silhouette score =", score) + + scores.append(score) +``` + +可视化轮廓分数的各种值: + +```py +# Plot silhouette scores +plt.figure() +plt.bar(values, scores, width=0.7, color='black', align='center') +plt.title('Silhouette score vs number of clusters') +``` + +提取最佳分数和集群数量的相应值: + +```py +# Extract best score and optimal number of clusters +num_clusters = np.argmax(scores) + values[0] +print('\nOptimal number of clusters =', num_clusters) +``` + +可视化输入数据: + +```py +# Plot data +plt.figure() +plt.scatter(X[:,0], X[:,1], color='black', s=80, marker='o', facecolors='none') +x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1 +y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1 +plt.title('Input data') +plt.xlim(x_min, x_max) +plt.ylim(y_min, y_max) +plt.xticks(()) +plt.yticks(()) +``` + +```py +plt.show() +``` + +完整代码在文件`clustering_quality.py`中给出。 如果运行代码,您将看到两个屏幕截图。 第一个屏幕截图是输入数据: + +![](img/B15441_07_05.png) + +图 5:可视化输入数据 + +我们可以看到,数据中有六个簇。 第二张屏幕截图代表了簇数的各种值的得分: + +![](img/B15441_07_06.png) + +图 6:Silhoutte 得分与簇数的关系 + +我们可以验证轮廓分数在`0.6`的值处达到峰值,这与数据一致。 您将看到以下输出: + +![](img/B15441_07_07.png) + +图 7:最佳集群输出 + +在本节中,我们了解了轮廓分数以及它们如何帮助我们理解聚类。 现在,我们将学习高斯混合模型,这是对 简化和聚类数据的另一种无监督学习技术。 + +# 什么是高斯混合模型? + +在讨论**高斯混合模型**( **GMM** )之前,让我们首先了解什么是混合模型。 混合模型是一种概率密度模型,其中假定数据由几种成分分布控制。 如果这些分布是高斯分布,则该模型将变为高斯混合模型。 组合这些成分分布以提供多峰密度函数,该函数成为混合模型。 + +让我们看一个示例,以了解混合模型如何工作。 我们要模拟南美所有人的购物习惯。 做到这一点的一种方法是对整个大陆进行建模,然后将所有内容拟合为一个模型,但是不同国家/地区的人购物方式不同。 因此,我们需要了解各个国家/地区的人们如何购物以及他们的行为方式。 + +为了获得良好的代表性模型,我们需要考虑非洲大陆内的所有变化。 在这种情况下,我们可以使用混合模型来建模各个国家/地区的购物习惯,然后将它们全部组合成一个混合模型。 + +这样,就不会错过各个国家基本行为数据中的细微差别。 通过不在所有国家/地区实施单一模型,可以创建更准确的模型。 + +需要注意的有趣一点是,混合模型是半参数的,这意味着它们部分依赖于一组预定义的函数。 它们可以为数据的基础分布建模提供更高的精度和灵活性。 它们可以消除因稀疏数据而导致的差距。 + +定义函数后,混合模型将从半参数变为参数。 因此, GMM 是一个参数模型,表示为分量高斯函数的加权和。 我们假设数据是由一组以某种方式组合的高斯模型生成的。 GMM 非常强大,并用于许多领域。 GMM 的参数是使用算法(例如**期望最大化**( **EM** )或**最大后验身**([ **MAP** )估算。 GMM inc 的一些流行应用包括图像数据库检索,股票市场波动建模,生物特征验证等。 + +现在我们已经描述了什么是 GMM,让我们看看如何应用它们。 + +## 基于高斯混合模型构建分类器 + +让我们基于高斯混合模型构建分类器。 创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from matplotlib import patches +``` + +```py +from sklearn import datasets +from sklearn.mixture import GaussianMixture +from sklearn.model_selection import StratifiedKFold +from sklearn.model_selection import train_test_split +``` + +让我们使用 scikit-learn 中可用的 Iris 数据集进行分析: + +```py +# Load the iris dataset +iris = datasets.load_iris() +``` + +```py +X, y = datasets.load_iris(return_X_y=True) +``` + +使用 80/20 拆分将数据集拆分为训练和测试。 `n_splits`参数指定您将获得的子集数。 我们使用`5`的值,这意味着数据集将分为五个部分。 + +我们将使用四个部分来进行培训,使用一个部分来进行测试,从而得出 80/20 的比例: + +```py +# Split dataset into training and testing (80/20 split) +skf = StratifiedKFold(n_splits=5) # +skf.get_n_splits(X, y) +``` + +```py +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4, random_state=0) +``` + +提取训练数据中的班级数量: + +```py +# Extract the number of classes +num_classes = len(np.unique(y_train)) +``` + +使用相关参数构建基于 GMM 的分类器。 `n_components`参数指定基础分发中的组件数。 在这种情况下,它将是数据中不同类的数量。 我们需要指定要使用的协方差类型。 在这种情况下,将使用完全协方差。 `init_params`参数控制在训练过程中需要更新的参数。 使用`kmeans`值,这意味着权重和协方差参数将在训练期间更新。 `max_iter`参数是指训练期间将执行的 Expectation-Maximization 迭代次数: + +```py +# Build GMM +classifier = GaussianMixture(n_components=num_classes, covariance_type='full',init_params='kmeans', max_iter=20) +``` + +```py + 初始化分类器的方法: + +``` +# Initialize the GMM means +classifier.means_ = np.array([X_train[y_train == i].mean(axis=0) +for i in range(num_classes)]) +```py + + 使用训练数据训练高斯混合模型分类器: + +``` +# Train the GMM classifier +classifier.fit(X_train) +```py + + 可视化分类器的边界。 然后提取特征值和特征向量,以估计如何在簇周围绘制椭圆边界。 有关特征值和特征向量的快速更新,请参阅: [https://math.mit.edu/~gs/linearalgebra/ila0601.pdf](https://math.mit.edu/~gs/linearalgebra/ila0601.pdf) 。 让我们继续进行以下绘制: + +``` +# Draw boundaries +plt.figure() +colors = 'bgr' +for i, color in enumerate(colors): + # Extract eigenvalues and eigenvectors + eigenvalues, eigenvectors = np.linalg.eigh( + classifier.covariances_[i][:2, :2]) +```py + + 归一化第一个特征向量: + +``` + # Normalize the first eigenvector + norm_vec = eigenvectors[0] / np.linalg.norm(eigenvectors[0]) +```py + + 椭圆需要旋转以准确显示分布。 估计角度: + +``` + # Extract the angle of tilt + angle = np.arctan2(norm_vec[1], norm_vec[0]) + angle = 180 * angle / np.pi +```py + + 放大椭圆以进行可视化。 特征值控制椭圆的大小: + +``` + # Scaling factor to magnify the ellipses + # (random value chosen to suit our needs) + scaling_factor = 8 + eigenvalues *= scaling_factor +```py + + 绘制椭圆: + +``` + # Draw the ellipse + ellipse = patches.Ellipse(classifier.means_[i, :2], + eigenvalues[0], eigenvalues[1], 180 + angle, + color=color) + axis_handle = plt.subplot(1, 1, 1) + ellipse.set_clip_box(axis_handle.bbox) + ellipse.set_alpha(0.6) + axis_handle.add_artist(ellipse) +```py + + 在图上叠加输入数据: + +``` +# Plot the data +colors = 'bgr' +for i, color in enumerate(colors): + cur_data = iris.data[iris.target == i] + plt.scatter(cur_data[:,0], cur_data[:,1], marker='o', + facecolors='none', edgecolors='black', s=40, + label=iris.target_names[i]) +```py + + 在此图上叠加测试数据: + +``` + test_data = X_test[y_test == i] + plt.scatter(test_data[:,0], test_data[:,1], marker='s', + facecolors='black', edgecolors='black', s=40 , + label=iris.target_names[i]) +```py + + 计算训练和测试数据的预测输出: + +``` +# Compute predictions for training and testing data +y_train_pred = classifier.predict(X_train) +accuracy_training = np.mean(y_train_pred.ravel() == y_train.ravel()) * 100 +print('Accuracy on training data =', accuracy_training) +```py + +``` +y_test_pred = classifier.predict(X_test) +accuracy_testing = np.mean(y_test_pred.ravel() == y_test.ravel()) * 100 +print('Accuracy on testing data =', accuracy_testing) +```py + +``` +plt.title('GMM classifier') +plt.xticks(()) +plt.yticks(()) +```py + +``` +plt.show() +```py + + 完整代码在文件`gmm_classifier.py`中给出。 运行代码后,您将看到以下输出: + ![](img/B15441_07_08.png) + 图 8:高斯混合模型分类器图 + 输入的数据由三个分布组成。 不同大小和角度的三个椭圆表示输入数据中的基础分布。 您将看到以下输出: + +``` +Accuracy on training data = 87.5 +Accuracy on testing data = 86.6666666667 +```py + + 在本节中,我们学习了高斯混合模型,并使用 Python 开发了一个示例。 在下一节中,我们将学习另一种无监督学习技术,即 Affinity Propagation 模型,用于对数据进行分类,并将其用于 ,以便在股市数据中查找子组。 + 使用“相似性传播”模型在股票市场中寻找子群体 + **相似性传播**是一种聚类算法,不需要事先指定多个聚类。 由于其通用性和实现的简便性,它已在许多领域中找到了许多应用。 它使用一种称为消息传递的技术找出代表性的簇,称为样本。 它从指定需要考虑的相似性度量开始。 同时将所有训练数据点视为潜在的范例。 然后,它在数据点之间传递消息,直到找到一组示例为止。 + 消息传递发生在两个备用步骤中,分别称为**责任**和**可用性**。 责任是指从群集成员发送到候选示例的消息,指示该数据点作为该示例群集的成员的适合程度。 可用性是指从候选示例发送到集群的潜在成员的消息,表明它作为示例的适用性。 一直执行此操作,直到算法收敛到最佳样本集为止。 + 还有一个称为首选项的参数,该参数控制将发现的示例数量。 如果选择较高的值,则将导致算法找到太多的聚类。 如果选择一个较低的值,则将导致少数簇。 最佳值是点之间的中间相似度。 + 让我们使用“相似性传播”模型来查找股票市场中的子组。 我们将使用开盘价和收盘价之间的股票报价变化作为控制特征。 创建一个新的 Python 文件并导入以下软件包: + +``` +import datetime +import json +import numpy as np +import matplotlib.pyplot as plt +from sklearn import covariance, cluster +import yfinance as yf +```py + + matplotlib 中可用的股市数据将用作输入。 公司符号在文件`company_symbol_mapping.json`中映射到其全名: + +``` +# Input file containing company symbols +input_file = 'company_symbol_mapping.json' +```py + + 从文件中加载公司符号图: + +``` +# Load the company symbol map +with open(input_file, 'r') as f: + company_symbols_map = json.loads(f.read()) +```py + +``` +symbols, names = np.array(list(company_symbols_map.items())).T +```py + + 从 matplotlib 加载股票报价: + +``` +# Load the historical stock quotes +start_date = datetime.datetime(2019, 1, 1) +end_date = datetime.datetime(2019, 1, 31) +quotes = [yf.Ticker(symbol).history(start=start_date, end=end_date) + for symbol in symbols] +```py + + 计算开始和结束报价之间的差异: + +``` +# Extract opening and closing quotes +opening_quotes = np.array([quote.Open for quote in quotes]).astype(np.float) +closing_quotes = np.array([quote.Close for quote in quotes]).astype(np.float) +```py + +``` +# Compute differences between opening and closing quotes +quotes_diff = closing_quotes - opening_quotes +```py + + 规范化数据: + +``` +# Normalize the data +X = quotes_diff.copy().T +X /= X.std(axis=0) +```py + + 创建一个图形模型: + +``` +# Create a graph model +edge_model = covariance.GraphLassoCV() +```py + + 训练模型: + +``` +# Train the model +with np.errstate(invalid='ignore'): + edge_model.fit(X) +```py + + 使用我们刚刚训练的边缘模型构建相似性传播聚类模型: + +``` +# Build clustering model using Affinity Propagation model +_, labels = cluster.affinity_propagation(edge_model.covariance_) +num_labels = labels.max() +```py + + 打印输出: + +``` +# Print the results of clustering +print('\nClustering of stocks based on difference in opening and closing quotes:\n') +for i in range(num_labels + 1): + print("Cluster", i+1, "==>", ', '.join(names[labels == i])) +```py + + 完整代码在文件`stocks.py`中给出。 运行代码时,您将看到以下输出: + ![](img/B15441_07_09.png) + 图 9:基于开盘价和收盘价差异的股票聚类 + 此输出代表该时间段内股票市场中的各个子组。 请注意,运行代码时群集可能会以不同的顺序出现。 + 既然我们已经了解了“相似性传播”模型并学习了一些新概念,我们将继续本章的最后部分,在此部分中,我们将使用无监督学习技术,根据客户的购物习惯来细分市场数据。 + 根据购物模式细分市场 + 让我们看看如何运用无监督学习技术根据客户的购物习惯来细分市场。 为您提供了一个名为`sales.csv`的文件。 该文件包含来自多家零售服装店的各种上衣的销售详细信息。 目标是确定模式并根据这些商店中售出的商品数量来细分市场。 + 创建一个新的 Python 文件并导入以下软件包: + +``` +import csv +```py + +``` +import numpy as np +import matplotlib.pyplot as plt +from sklearn.cluster import MeanShift, estimate_bandwidth +```py + + 从输入文件加载数据。 由于它是 CSV 文件,因此我们可以在 Python 中使用 csv 阅读器从该文件中读取数据并将其转换为`NumPy`数组: + +``` +# Load data from input file +input_file = 'sales.csv' +file_reader = csv.reader(open(input_file, 'r'), delimiter=',') +X = [] +for count, row in enumerate(file_reader): + if not count: + names = row[1:] + continue +```py + +``` + X.append([float(x) for x in row[1:]]) +```py + +``` +# Convert to numpy array +X = np.array(X) +```py + + 让我们估计输入数据的带宽: + +``` +# Estimating the bandwidth of input data +bandwidth = estimate_bandwidth(X, quantile=0.8, n_samples=len(X)) +```py + + 根据估计的带宽训练平均漂移模型: + +``` +# Compute clustering with MeanShift +meanshift_model = MeanShift(bandwidth=bandwidth, bin_seeding=True) +meanshift_model.fit(X) +```py + + 提取标签和每个群集的中心: + +``` +labels = meanshift_model.labels_ +cluster_centers = meanshift_model.cluster_centers_ +num_clusters = len(np.unique(labels)) +```py + + 打印集群数和集群中心: + +``` +print("\nNumber of clusters in input data =", num_clusters) +```py + +``` +print("\nCenters of clusters:") +print('\t'.join([name[:3] for name in names])) +for cluster_center in cluster_centers: + print('\t'.join([str(int(x)) for x in cluster_center])) +```py + + 我们正在处理六维数​​据。 为了使数据可视化,让我们使用由第二维和第三维构成的二维数据: + +``` +# Extract two features for visualization +cluster_centers_2d = cluster_centers[:, 1:3] +```py + + 绘制群集的中心: + +``` +# Plot the cluster centers +plt.figure() +plt.scatter(cluster_centers_2d[:,0], cluster_centers_2d[:,1], + s=120, edgecolors='black', facecolors='none') +```py + +``` +offset = 0.25 +plt.xlim(cluster_centers_2d[:,0].min() - offset * cluster_centers_2d[:,0].ptp(), + cluster_centers_2d[:,0].max() + offset * cluster_centers_2d[:,0].ptp(),) +plt.ylim(cluster_centers_2d[:,1].min() - offset * cluster_centers_2d[:,1].ptp(), + cluster_centers_2d[:,1].max() + offset * cluster_centers_2d[:,1].ptp()) +plt.title('Centers of 2D clusters') +```py + +``` +plt.show() +```py + + 文件`market_segmentation.py`中提供了完整代码。 运行代码时,您将看到以下输出: + ![](img/B15441_07_10.png) + 图 10:2D 群集的中心图 + 在本章的最后一部分中,我们应用了在本章前面了解的均值漂移算法,并将其用于分析和细分客户 sh 的使用习惯。 + 您还将看到以下输出: + ![](img/B15441_07_11.png) + 图 11:集群中心输出 + 摘要 + 在本章中,我们首先讨论无监督学习及其应用。 然后,我们学习了聚类以及如何使用 K-Means 算法聚类数据。 我们讨论了如何使用均值漂移算法估计聚类数。 我们讨论了轮廓分数以及如何估计聚类的质量。 我们了解了高斯混合模型,以及如何基于它们建立分类器。 我们还讨论了“亲和力传播”模型,并使用它在股票市场中找到了子组。 然后,我们应用均值漂移算法根据购物模式细分市场。 + 在下一章中,我们将学习如何构建推荐引擎。 + +``` \ No newline at end of file diff --git a/docs/ai-py/08.md b/docs/ai-py/08.md new file mode 100644 index 0000000000000000000000000000000000000000..da8147e9dffab7965846a54b417c35eeb4c89764 --- /dev/null +++ b/docs/ai-py/08.md @@ -0,0 +1,847 @@ +# 8 + +# 建筑推荐系统 + +在本章中,我们将学习如何建立一个推荐系统来推荐人们可能喜欢看的电影。 我们将了解 K 最近邻分类器,并了解如何实现它。 我们使用这些概念来讨论协作过滤,然后使用它来构建推荐系统。 + +到本章末,您将了解以下内容: + +* 提取最近的邻居 +* 建立 *K 最近邻居*分类器 +* 计算相似度分数 +* 使用协作过滤查找相似用户 +* 建立电影推荐系统 + +# 提取最近的邻居 + +推荐人系统采用最近邻居的概念来找到好的建议。 名称*最近邻居*是指从给定数据集中查找到输入点最近的数据点的过程。 这通常用于构建分类系统,该分类系统根据输入数据点与各种类别的接近程度对数据点进行分类。 让我们看看如何找到给定数据点的最近邻居。 + +首先,创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from sklearn.neighbors import NearestNeighbors +``` + +定义样本 2D 数据点: + +```py +# Input data +X = np.array([[2.1, 1.3], [1.3, 3.2], [2.9, 2.5], [2.7, 5.4], [3.8, 0.9], + [7.3, 2.1], [4.2, 6.5], [3.8, 3.7], [2.5, 4.1], [3.4, 1.9], + [5.7, 3.5], [6.1, 4.3], [5.1, 2.2], [6.2, 1.1]]) +``` + +定义要提取的最近邻居的数量: + +```py +# Number of nearest neighbors +k = 5 +``` + +定义一个测试数据点,该数据点将用于提取最近的 K 个邻居: + +```py +# Test data point +test_data_point = [4.3, 2.7] +``` + +使用圆形黑色标记绘制输入数据: + +```py +# Plot input data +plt.figure() +plt.title('Input data') +plt.scatter(X[:,0], X[:,1], marker='o', s=75, color='black') +``` + +使用输入数据创建并训练 K 近邻模型。 使用此模型提取到测试数据点最近的邻居: + +```py +# Build K Nearest Neighbors model +knn_model = NearestNeighbors(n_neighbors=k, algorithm='ball_tree').fit(X) +distances, indices = knn_model.kneighbors(test_data_point) +``` + +打印从模型中提取的最近邻居: + +```py +# Print the 'k' nearest neighbors +print("\nK Nearest Neighbors:") +for rank, index in enumerate(indices[0][:k], start=1): + print(str(rank) + " ==>", X[index]) +``` + +可视化最近的邻居: + +```py +# Visualize the nearest neighbors along with the test datapoint +plt.figure() +plt.title('Nearest neighbors') +plt.scatter(X[:, 0], X[:, 1], marker='o', s=75, color='k') +plt.scatter(X[indices][0][:][:, 0], X[indices][0][:][:, 1], + marker='o', s=250, color='k', facecolors='none') +plt.scatter(test_data_point[0], test_data_point[1], + marker='x', s=75, color='k') +``` + +```py +plt.show() +``` + +完整代码在文件`k_nearest_neighbors.py`中给出。 如果运行代码,您将看到两个屏幕截图。 第一个屏幕截图表示输入数据: + +![](img/B15441_08_01.png) + +图 1:输入数据集的可视化 + +第二张屏幕截图代表五个最近的邻居。 使用十字显示测试数据点,并圈出最近的邻居点: + +![](img/B15441_08_02.png) + +图 2:五个最近的邻居图 + +您将看到以下输出: + +![](img/B15441_08_03.png) + +图 3:K 近邻输出 + +上图显示了最接近测试数据点的五个点。 现在我们已经学习了如何构建和运行 K 近邻模型,在下一节中,我们将基于该知识并使用它来构建 K 近邻分类器。 + +# 建立 K 最近邻分类器 + +K 近邻分类器是使用 K 近邻算法对给定数据点进行分类的分类模型。 该算法在训练数据集中找到最接近的 *K* 个数据点,以识别输入数据点的类别。 然后,它将基于多数投票为该数据点分配一个类别。 从这些 *K* 数据点的列表中,我们查看相应的类别,然后选择投票数最高的类别。 *K* 的值取决于当前的问题。 让我们看看如何使用此模型构建分类器。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.cm as cm +from sklearn import neighbors, datasets +``` + +从`data.txt`加载输入数据。 每行包含逗号分隔的值,数据包含四个类别: + +```py +# Load input data +input_file = 'data.txt' +data = np.loadtxt(input_file, delimiter=',') +X, y = data[:, :-1], data[:, -1].astype(np.int) +``` + +使用四种不同的标记形状可视化输入数据。 我们需要将标签映射到相应的标记,这是`mapper`变量进入图片的位置: + +```py +# Plot input data +plt.figure() +plt.title('Input data') +marker_shapes = 'v^os' +mapper = [marker_shapes[i] for i in y] +for i in range(X.shape[0]): + plt.scatter(X[i, 0], X[i, 1], marker=mapper[i], + s=75, edgecolors='black', facecolors='none') +``` + +定义要使用的最近邻居的数量: + +```py +# Number of nearest neighbors +num_neighbors = 12 +``` + +定义将用于可视化分类器模型边界的网格步长: + +```py +# Step size of the visualization grid +step_size = 0.01 +``` + +创建 K 最近邻分类器模型: + +```py +# Create a K Nearest Neighbors classifier model +classifier = neighbors.KNeighborsClassifier(num_neighbors, weights='distance') +``` + +使用训练数据训练模型: + +```py +# Train the K Nearest Neighbors model +classifier.fit(X, y) +``` + +创建将用于可视化网格的值的网格网格: + +```py +# Create the mesh to plot the boundaries +x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1 +y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1 +x_values, y_values = np.meshgrid(np.arange(x_min, x_max, step_size), + np.arange(y_min, y_max, step_size)) +``` + +在网格上的所有点上评估分类器,以创建边界的可视化效果: + +```py +# Evaluate the classifier on all the points on the grid +output = classifier.predict(np.c_[x_values.ravel(), y_values.ravel()]) +``` + +创建一个彩色网格以可视化输出: + +```py +# Visualize the predicted output +output = output.reshape(x_values.shape) +plt.figure() +plt.pcolormesh(x_values, y_values, output, cmap=cm.Paired) +``` + +将训练数据覆盖在此颜色网格上方,以可视化相对于边界的数据: + +```py +# Overlay the training points on the map +for i in range(X.shape[0]): + plt.scatter(X[i, 0], X[i, 1], marker=mapper[i], + s=50, edgecolors='black', facecolors='none') +``` + +设置 *X* 和 *Y* 限制以及标题: + +```py +plt.xlim(x_values.min(), x_values.max()) +plt.ylim(y_values.min(), y_values.max()) +plt.title('K Nearest Neighbors classifier model boundaries') +``` + +定义测试数据点以查看分类器的性能。 创建一个包含训练数据点和测试数据点的图形,以查看其位置: + +```py +# Test input data point +test_data_point = [5.1, 3.6] +plt.figure() +plt.title('Test data_point') +for i in range(X.shape[0]): + plt.scatter(X[i, 0], X[i, 1], marker=mapper[i], + s=75, edgecolors='black', facecolors='none') +``` + +```py +plt.scatter(test_data_point[0], test_data_point[1], marker='x', + linewidth=6, s=200, facecolors='black') +``` + +根据分类器模型,将 K 最近邻提取到测试数据点: + +```py +# Extract the K nearest neighbors +_, indices = classifier.kneighbors([test_data_point]) +indices = indices.astype(np.int)[0] +``` + +画出在上一步中获得的 K 近邻邻居: + +```py +# Plot k nearest neighbors +plt.figure() +plt.title('K Nearest Neighbors') +``` + +```py +for i in indices: + plt.scatter(X[i, 0], X[i, 1], marker=mapper[y[i]], + linewidth=3, s=100, facecolors='black') +``` + +覆盖测试数据点: + +```py +plt.scatter(test_data_point[0], test_data_point[1], marker='x', + linewidth=6, s=200, facecolors='black') +``` + +覆盖输入数据: + +```py +for i in range(X.shape[0]): + plt.scatter(X[i, 0], X[i, 1], marker=mapper[i], + s=75, edgecolors='black', facecolors='none') +``` + +打印预测的输出: + +```py +print("Predicted output:", classifier.predict([test_data_point])[0]) +``` + +```py +plt.show() +``` + +完整代码在文件`nearest_neighbors_classifier.py`中给出。 如果运行代码,您将看到四个屏幕截图。 第一个屏幕截图表示输入数据: + +![](img/B15441_08_04.png) + +图 4:可视化输入数据 + +第二张屏幕截图表示分类器边界: + +![](img/B15441_08_05.png) + +图 5:分类器模型边界 + +第三个屏幕截图显示了相对于输入数据集的测试数据点。 使用十字显示测试数据点: + +![](img/B15441_08_06.png) + +图 6:相对于输入数据集的测试数据点 + +第四个屏幕截图显示了距离测试数据点最近的 12 个邻居: + +![](img/B15441_08_07.png) + +图 7:12 个最近的邻居图 + +您将看到以下输出,这表明模型正在预测测试数据点属于`1`类: + +```py +Predicted output: 1 +``` + +像任何机器学习模型一样,输出是一个预测,可能与实际结果 lt 相匹配。 + +# 计算相似度分数 + +要构建推荐系统,重要的是要了解如何比较数据集中的各种对象。 如果数据集由人物及其不同的电影喜好组成,那么为了提出建议,我们需要了解如何将任何两个人物相互比较。 这是相似度分数很重要的地方。 相似度得分给出了两个数据点相似度的想法。 + +此领域中经常使用两个分数-欧几里得分数和皮尔森分数。 **欧几里得分数** 使用两个数据点之间的欧几里得距离来计算分数。 如果需要快速了解欧几里德距离的计算方式,则可以转到: + +[https://en.wikipedia.org/wiki/Euclidean_distance](https://en.wikipedia.org/wiki/Euclidean_distance ) + +欧几里得距离的值可以是无界的。 因此,我们采用该值并将其转换为欧几里得分数从 *0* 到 *1* 的范围。 如果两个对象之间的欧几里得距离较大,则欧几里得分数应较低,因为低分数表明对象不相似。 因此,欧几里得距离与欧几里得分数成反比。 + +**皮尔森评分** 是两个数据点之间相关性的量度。 它使用两个数据点之间的协方差以及它们各自的标准差来计算分数。 得分范围从 *-1* 到 *+1* 。 分数 *+1* 表示数据点相似,分数 *-1* 表示数据点相似。 分数 *0* 表示它们之间没有相关性。 让我们看看如何计算这些分数。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import argparse +import json +import numpy as np +``` + +构建参数解析器以处理输入参数。 它将接受两个用户以及用于计算相似性得分所需的得分类型: + +```py +def build_arg_parser(): + parser = argparse.ArgumentParser(description='Compute similarity score') + parser.add_argument('--user1', dest='user1', required=True, + help='First user') + parser.add_argument('--user2', dest='user2', required=True, + help='Second user') + parser.add_argument("--score-type", dest="score_type", required=True, + choices=['Euclidean', 'Pearson'], help='Similarity metric to be used') + return parser +``` + +定义一个函数来计算输入用户之间的欧几里得分数。 如果用户不在数据集中,则代码将引发错误: + +```py +# Compute the Euclidean distance score between user1 and user2 +def euclidean_score(dataset, user1, user2): + if user1 not in dataset: + raise TypeError('Cannot find ' + user1 + ' in the dataset') +``` + +```py + if user2 not in dataset: + raise TypeError('Cannot find ' + user2 + ' in the dataset') +``` + +定义一个变量以跟踪两个用户都评价过的电影: + +```py + # Movies rated by both user1 and user2 + common_movies = {} +``` + +提取两个用户都评价的电影: + +```py + for item in dataset[user1]: + if item in dataset[user2]: + common_movies[item] = 1 +``` + +如果没有普通电影,则无法计算相似度得分: + +```py + # If there are no common movies between the users, + # then the score is 0 + if len(common_movies) == 0: + return 0 +``` + +计算等级之间的平方差异,并使用其计算欧几里得分数: + +```py + squared_diff = [] +``` + +```py + for item in dataset[user1]: + if item in dataset[user2]: + squared_diff.append(np.square(dataset[user1][item] - dataset[user2][item])) + + return 1 / (1 + np.sqrt(np.sum(squared_diff))) +``` + +定义一个函数来计算给定数据集中用户之间的皮尔森得分。 如果在数据集中找不到用户,则会引发错误: + +```py +# Compute the Pearson correlation score between user1 and user2 +def pearson_score(dataset, user1, user2): + if user1 not in dataset: + raise TypeError('Cannot find ' + user1 + ' in the dataset') +``` + +```py + if user2 not in dataset: + raise TypeError('Cannot find ' + user2 + ' in the dataset') +``` + +定义一个变量以跟踪两个用户都评价过的电影: + +```py + # Movies rated by both user1 and user2 + common_movies = {} +``` + +提取两个用户都评价的电影: + +```py + for item in dataset[user1]: + if item in dataset[user2]: + common_movies[item] = 1 +``` + +如果没有普通电影,那么我们将无法计算相似度得分: + +```py + num_ratings = len(common_movies) + # If there are no common movies between + #user1 and user2, then the score is 0 +``` + +```py + if num_ratings == 0: + return 0 +``` + +计算两个用户都已评分的所有电影的评分总和: + +```py + # Calculate the sum of ratings of all the common movies + user1_sum = np.sum([dataset[user1][item] for item in common_movies]) + user2_sum = np.sum([dataset[user2][item] for item in common_movies]) +``` + +计算两个用户都已评分的所有电影的评分的平方和: + +```py + # Calculate the sum of squares of ratings + # of all the common movies + user1_squared_sum = np.sum([np.square(dataset[user1][item]) for item in common_movies]) + user2_squared_sum = np.sum([np.square(dataset[user2][item]) for item in common_movies]) +``` + +计算两个输入用户都评价过的所有电影的评价结果​​的总和: + +```py + # Calculate the sum of products of the + # ratings of the common movies + sum_of_products = np.sum([dataset[user1][item] * dataset[user2][item] for item in common_movies]) +``` + +使用前面的计算来计算计算皮尔森分数所需的各种参数: + +```py + # Calculate the Pearson correlation score + Sxy = sum_of_products - (user1_sum * user2_sum / num_ratings) + Sxx = user1_squared_sum - np.square(user1_sum) / num_ratings + Syy = user2_squared_sum - np.square(user2_sum) / num_ratings +``` + +如果没有偏差,则得分为`0`: + +```py + if Sxx * Syy == 0: +``` + +```py + return 0 +``` + +返回皮尔逊得分: + +```py + return Sxy / np.sqrt(Sxx * Syy) +``` + +定义`main`函数并解析输入参数: + +```py +if __name__=='__main__': + args = build_arg_parser().parse_args() + user1 = args.user1 + user2 = args.user2 + score_type = args.score_type +``` + +将文件`ratings.json`中的评级加载到字典中: + +```py + ratings_file = 'ratings.json' +``` + +```py + with open(ratings_file, 'r') as f: + data = json.loads(f.read()) +``` + +根据输入参数计算相似度得分: + +```py +if score_type == 'Euclidean': + print("\nEuclidean score:") + print(euclidean_score(data, user1, user2)) +else: + print("\nPearson score:") + print(pearson_score(data, user1, user2)) +``` + +完整代码在文件`compute_scores.py`中给出。 让我们用一些组合来运行代码。 要计算`David Smith`和`Bill Duffy`之间的欧几里得分数: + +```py +$ python3 compute_scores.py --user1 "David Smith" --user2 "Bill Duffy" --score-type Euclidean +``` + +如果运行前面的命令,将获得以下输出: + +```py +Euclidean score: +0.585786437627 +``` + +如果要计算同一对之间的皮尔逊得分,请运行以下命令: + +```py +$ python3 compute_scores.py --user1 "David Smith" --user2 "Bill Duffy" --score-type Pearson +``` + +您将看到以下输出: + +```py +Pearson score: +0.99099243041 +``` + +也可以使用其他参数组合来运行。 + +在本节中,我们学习了如何计算相似性得分,并了解了为什么在推荐系统的构建中该重要因素。 在下一节中,我们将学习如何通过协作 e 过滤来识别具有相似偏好的用户。 + +# 使用协作过滤查找相似用户 + +协作过滤是指在数据集中的对象之间标识模式以决定新对象的过程。 在推荐引擎的上下文中,协作过滤用于通过查看数据集中的相似用户来提供推荐。 + +通过收集数据集中不同用户的偏好,我们可以协作该信息来过滤用户。 因此,名称为协作过滤。 + +这里的假设是,如果两个人对一组电影的收视率相似,那么他们对一组新的未知电影的选择也将相似。 通过识别那些普通电影中的模式,可以对新电影做出预测。 在上一节中,我们学习了如何比较数据集中的不同用户。 现在将使用讨论的评分技术在数据集中查找相似的用户。 协作过滤算法可以并行化并在大数据系统(例如 AWS EMR 和 Apache Spark)中实现,从而能够处理数百 TB 的数据。 这些方法可用于各种垂直领域,例如金融,在线购物,市场营销,客户研究等。 + +让我们开始构建协作过滤系统。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import argparse +import json +import numpy as np +``` + +```py +from compute_scores import pearson_score +``` + +定义一个函数来解析输入参数。 输入参数是用户的名称: + +```py +def build_arg_parser(): + parser = argparse.ArgumentParser(description='Find users who are similar to the input user') + parser.add_argument('--user', dest='user', required=True, + help='Input user') + return parser +``` + +定义一个函数以在数据集中查找与给定用户相似的用户。 如果用户不在数据集中,则引发错误: + +```py +# Finds users in the dataset that are similar to the input user +def find_similar_users(dataset, user, num_users): + if user not in dataset: + raise TypeError('Cannot find ' + user + ' in the dataset') +``` + +计算皮尔森得分的函数已导入。 让我们使用该函数来计算输入用户与数据集中所有其他用户之间的皮尔森得分: + +```py + # Compute Pearson score between input user + # and all the users in the dataset + scores = np.array([[x, pearson_score(dataset, user, + x)] for x in dataset if x != user]) +``` + +分数按降序排列: + +```py + # Sort the scores in decreasing order + scores_sorted = np.argsort(scores[:, 1])[::-1] +``` + +提取输入参数指定的最高`num_users`个用户,并返回数组: + +```py + # Extract the top 'num_users' scores + top_users = scores_sorted[:num_users] +``` + +```py + return scores[top_users] +``` + +定义`main`函数并解析输入参数以提取用户名: + +```py +if __name__=='__main__': + args = build_arg_parser().parse_args() + user = args.user +``` + +从电影分级文件`ratings.json`加载数据。 此文件包含人物名称及其对各种电影的评分: + +```py + ratings_file = 'ratings.json' +``` + +```py + with open(ratings_file, 'r') as f: + data = json.loads(f.read()) +``` + +查找与输入参数指定的用户相似的前三个用户。 您可以根据自己的选择将其更改为任意数量的用户。 打印输出和分数: + +```py + print('\nUsers similar to ' + user + ':\n') + similar_users = find_similar_users(data, user, 3) + print('User\t\t\tSimilarity score') + print('-'*41) + for item in similar_users: + print(item[0], '\t\t', round(float(item[1]), 2)) +``` + +完整代码在文件`collaborative_filtering.py`中给出。 让我们运行代码,找到像 Bill Duffy 这样的用户: + +```py +$ python3 collaborative_filtering.py --user "Bill Duffy" +``` + +您将获得以下输出: + +![](img/B15441_08_08.png) + +图 8:用户相似度输出 + +让我们运行代码,找到像 Clarissa Jackson 这样的用户: + +```py +$ python3 collaborative_filtering.py --user "Clarissa Jackson" +``` + +您将获得以下输出: + +![](img/B15441_08_09.png) + +图 9:用户相似度输出 + +在本节中,我们学习了如何在数据集中找到彼此相似的用户,以及如何分配分数来确定用户与另一个用户的相似程度。 在下一节中,我们将把它们放在一起并构建我们的建议 终结系统。 + +# 建立电影推荐系统 + +到目前为止,我们通过了解以下内容为构建推荐系统奠定了基础: + +* 提取最近的邻居 +* 建立 K 近邻分类器 +* 计算相似度分数 +* 使用协作过滤查找相似用户 + +现在所有的构建块都已就绪,现在该构建电影推荐系统了。 我们学习了构建推荐系统所需的所有基本概念。 在本节中,我们将基于文件`ratings.json`中提供的数据构建电影推荐系统。 此文件包含一组人物及其对各种电影的评分。 要查找给定用户的电影推荐,我们需要在数据集中找到相似的用户,然后提出针对此人的推荐。 让我们开始吧。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import argparse +import json +import numpy as np +``` + +```py +from compute_scores import pearson_score +from collaborative_filtering import find_similar_users +``` + +定义一个函数来解析输入参数。 输入参数是用户的名称: + +```py +def build_arg_parser(): + parser = argparse.ArgumentParser(description='Find recommendations for the given user') + parser.add_argument('--user', dest='user', required=True, + help='Input user') + return parser +``` + +定义一个函数以获取给定用户的电影推荐。 如果数据集中不存在该用户,则代码将引发错误: + +```py +# Get movie recommendations for the input user +def get_recommendations(dataset, input_user): + if input_user not in dataset: + raise TypeError('Cannot find ' + input_user + ' in the dataset') +``` + +定义变量以跟踪分数: + +```py + overall_scores = {} + similarity_scores = {} +``` + +计算输入用户与数据集中所有其他用户之间的相似性得分: + +```py + for user in [x for x in dataset if x != input_user]: + similarity_score = pearson_score(dataset, input_user, user) +``` + +如果相似度得分小于`0`,则可以继续使用数据集中的下一个用户: + +```py + if similarity_score <= 0: + continue +``` + +提取当前用户已评级但输入用户未评级的电影列表: + +```py + filtered_list = [x for x in dataset[user] if x not in \ + dataset[input_user] or dataset[input_user][x] == 0] +``` + +对于过滤列表中的每个项目,请根据相似性分数跟踪加权等级。 同时跟踪相似度分数: + +```py + for item in filtered_list: + overall_scores.update({item: dataset[user][item] * similarity_score}) + similarity_scores.update({item: similarity_score}) +``` + +如果没有这样的电影,那么我们将不推荐任何东西: + +```py + if len(overall_scores) == 0: + return ['No recommendations possible'] +``` + +根据加权分数归一化分数: + +```py + # Generate movie ranks by normalization + movie_scores = np.array([[score/similarity_scores[item], item] + for item, score in overall_scores.items()]) +``` + +排序分数并提取电影推荐: + +```py + # Sort in decreasing order + movie_scores = movie_scores[np.argsort(movie_scores[:, 0])[::-1]] +``` + +```py + # Extract the movie recommendations + movie_recommendations = [movie for _, movie in movie_scores] +``` + +```py + return movie_recommendations +``` + +定义`main`函数并解析输入参数以提取输入用户的名称: + +```py +if __name__=='__main__': + args = build_arg_parser().parse_args() + user = args.user +``` + +从文件`ratings.json`加载电影收视率数据: + +```py + ratings_file = 'ratings.json' +``` + +```py + with open(ratings_file, 'r') as f: + data = json.loads(f.read()) +``` + +提取电影推荐并打印输出: + +```py + print("\nMovie recommendations for " + user + ":") + movies = get_recommendations(data, user) + for i, movie in enumerate(movies): + print(str(i+1) + '. ' + movie) +``` + +完整代码在文件`movie_recommender.py`中给出。 让我们找到`Chris Duncan`的电影推荐: + +```py +$ python3 movie_recommender.py --user "Chris Duncan" +``` + +您将看到以下输出: + +![](img/B15441_08_10.png) + +图 10:电影推荐 + +让我们找到`Julie Hammel`的电影推荐: + +```py +$ python3 movie_recommender.py --user "Julie Hammel" +``` + +您将看到以下输出: + +![](img/B15441_08_11.png) + +图 11:电影推荐 + +输出中的电影是系统的实际建议,基于先前对 Julie Hammel 观察到的偏好。 潜在地,仅通过观察更多和个数据点,系统就可以继续变得更好。 + +# 摘要 + +在本章中,我们学习了如何从给定数据集中提取给定数据点的 K 最近邻。 然后,我们使用此概念来构建 K 最近邻分类器。 我们在中使用了如何计算相似度分数,例如欧几里得分数和皮尔逊分数。 我们学习了如何使用协作过滤从给定的数据集中查找相似的用户,并使用它来构建电影推荐系统。 最后,我们能够测试我们的模型并针对系统以前未见过的数据点运行它。 + +在下一章中,我们将学习逻辑编程,并了解如何构建可以解决实际问题的推理引擎。 \ No newline at end of file diff --git a/docs/ai-py/09.md b/docs/ai-py/09.md new file mode 100644 index 0000000000000000000000000000000000000000..4f1cdc1d9a1e7b3465f97f3bd31f249474764f9f --- /dev/null +++ b/docs/ai-py/09.md @@ -0,0 +1,757 @@ +# 9 + +# 逻辑编程 + +在本章中,我们将学习如何使用逻辑编程编写程序。 我们将讨论各种编程范例,并查看如何使用逻辑编程构造程序。 我们将学习逻辑编程的组成部分,并了解如何解决这一领域的问题。 我们将实现 Python 程序来构建各种解决各种问题的求解器。 + +在本章结束时,您将了解以下内容: + +* 什么是逻辑编程? +* 了解逻辑编程的基础 +* 使用逻辑编程解决问题 +* 安装 Python 包 +* 匹配数学表达式 +* 验证素数 +* 解析家谱 +* 分析地理 +* 构建一个解谜器 + +# 什么是逻辑编程? + +逻辑编程是一种编程范例,基本上意味着它是一种进行编程的方法。 在我们讨论它的构成及其在**人工智能**( **AI** )中的相关性之前,让我们先讨论一下编程范例。 + +编程范例的概念源于对编程语言进行分类的需求。 它是指计算机程序通过代码解决问题的方式。 + +一些编程范例主要涉及含义或用于实现特定结果的操作顺序。 其他编程范例也关注我们如何组织代码。 + +以下是一些较流行的编程范例: + +* **命令式[​​HTG1]:使用语句更改程序的状态,从而产生副作用。** +* **功能性**:将计算视为对数学函数的评估,并且不允许更改状态或可变数据。 +* **声明性**:一种编程的方式,其中,通过描述需要完成的操作而不是如何执行来编写程序。 在不明确描述控制流程的情况下表达了底层计算的逻辑。 +* **面向对象的**:将程序中的代码分组,以使每个对象都对自己负责。 对象包含指定更改如何发生的数据和方法。 +* **过程**:将代码分组为功能,每个功能负责一系列步骤。 +* **符号**:使用样式的语法和语法,程序可通过将样式视为纯数据来修改其自身的组件。 +* **逻辑**:将计算视为对由事实和规则组成的知识数据库的自动推理。 + +逻辑编程已经存在了一段时间。 在 AI 的最后一个鼎盛时期非常流行的语言是 Prolog。 它是仅使用三种构造的语言: + +* 事实 +* 规则 +* 问题 + +但是使用这三种构造,您就可以构建一些强大的系统。 一种流行的用法是构建“专家系统”。 背后的想法是采访在特定领域工作了很长时间的人类专家,并将访谈编入 AI 系统。 构建专家系统的领域示例如下: + +* **医学** –著名的例子包括 MYCIN,INTERNIST-I 和 CADUCEUS +* **化学分析** – DENDRAL 是用于预测分子结构的分析系统 +* **财务** –协助银行家贷款的咨询计划 +* **调试程序** – SAINT,MATLAB 和 MACSYMA + +为了理解逻辑编程,有必要了解计算和演绎的概念。 为了计算某些东西,我们从一个表达式和一组规则开始。 这套规则基本上是程序。 + +表达式和规则用于生成输出。 例如,假设我们要计算 23、12 和 49 的总和: + +![](img/B15441_09_01.png) + +图 1:加法运算机制 + +完成操作的过程如下: + +1. 加 3 + 2 + 9 = 14 +2. 我们需要保留一个数字,即 4,然后携带 1 +3. 加 2 +1 + 4(加上我们携带的 1)= 8 +4. 结合 8 和 4。最终结果是:84 + +另一方面,要推断出某些东西,我们需要从一个推测开始。 证明是根据一组规则构造的。 计算过程是机械的,而演绎过程则更具创造性。 + +使用逻辑编程范例编写程序时,将基于有关问题域的事实和规则指定一组语句,然后求解器使用此信息 进行求解。 + +# 了解逻辑编程的组成部分 + +在面向对象的或命令式编程中,始终需要定义一个变量。 在逻辑编程中,工作原理有所不同。 可以将未实例化的参数传递给函数,并且解释器将通过查看用户定义的事实来实例化这些变量。 这是解决变量匹配问题的有效方法。 将变量与不同项目进行匹配的过程称为统一。 这是逻辑编程不同的方式之一。 关系也可以在逻辑编程中指定。 关系通过称为事实和规则的子句来定义。 + +事实只是陈述,是关于程序和数据的真实情况。 语法很简单。 例如,*唐纳德是艾伦的儿子*是事实,而*艾伦的儿子是谁?* 并非事实。 每个逻辑程序都需要事实,以便可以基于事实来实现给定的目标。 + +规则是我们在表达各种事实以及如何查询它们方面学到的知识。 它们是必须满足的约束,它们使您能够得出有关问题域的结论。 例如,假设您正在构建国际象棋引擎。 您需要指定有关如何在棋盘上移动的所有规则。 + +# 使用逻辑编程解决问题 + +逻辑编程使用事实和规则寻找解决方案。 必须为每个程序指定一个目标。 当逻辑程序和目标不包含任何变量时,求解器会提供一棵树,该树构成了用于解决问题和达到目标的搜索空间。 + +关于逻辑编程,最重要的事情之一就是我们如何对待规则。 规则可以视为逻辑语句。 让我们考虑以下内容: + +*凯西下令甜点= > K 阿西很高兴* + +这可以理解为:*如果 Kathy 很高兴*和*,则 Kathy 点甜点*。 当凯西开心时,也可以将其解释为订购甜品。 + +同样,让我们​​考虑以下规则和事实: + +*canfly(X):-鸟(X),不是异常(X)。* + +*异常(X):受伤(X)。* + +*鸟(约翰)。* + +*鸟(玛丽)。* + +*受伤(约翰)。* + +以下是解释规则和事实的方法: + +* 约翰受伤了 +* 玛丽是鸟 +* 约翰是鸟 +* 受伤的鸟是异常的 +* 没有异常的鸟可以飞 + +由此,我们可以得出结论,玛丽可以飞翔,而约翰不能飞翔。 + +在整个逻辑编程中,此结构以各种形式使用,以解决各种类型的问题。 让我们继续前进,看看如何解决 Python 中的这些问题。 + +# 安装 Python 软件包 + +在开始使用 Python 进行逻辑编程之前,我们需要安装几个软件包。 软件包`logpy`是一个 Python 软件包,可在 Python 中进行逻辑编程。 我们还将针对某些问题使用 SymPy。 因此,我们继续使用`pip`安装`logpy`和`sympy`: + +```py +$ pip3 install logpy +$ pip3 install sympy +``` + +如果在`logpy`的安装过程中出现错误,则可以从 [https://github.com/logpy/logpy](https://github.com/logpy/logpy) 的源代码安装它。 一旦成功安装了这些软件包,就可以继续进行下一部分的。 + +# 匹配的数学表达式 + +我们一直遇到数学运算。 逻辑编程是比较表达式并找出未知值的有效方法。 让我们看看如何做到这一点。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +from logpy import run, var, fact +import logpy.assoccomm as la +``` + +定义几个数学运算: + +```py +# Define mathematical operations +add = 'addition' +mul = 'multiplication' +``` + +加法和乘法都是交换操作(意味着可以对操作数进行翻转而不会改变结果)。 让我们指定: + +```py +# Declare that these operations are commutative +# using the facts system +fact(la.commutative, mul) +fact(la.commutative, add) +fact(la.associative, mul) +fact(la.associative, add) +``` + +让我们定义一些变量: + +```py +# Define some variables +a, b, c = var('a'), var('b'), var('c') +``` + +考虑以下表达式: + +```py +expression_orig = 3 x (-2) + (1 + 2 x 3) x (-1) +``` + +让我们用带掩码的变量生成该表达式。 第一个表达式是: + +*expression1 =(1 + 2 x a)x b + 3 x c* + +第二个表达式是: + +*expression2 = c x 3 + b x(2 x a +1)* + +第三个表达式是: + +*expression3 =((((2 x a)x b)+ b)+ 3 x c* + +如果仔细观察,所有三个表达式都代表相同的基本表达式。 目标是将这些表达式与原始表达式匹配以提取未知值: + +```py +# Generate expressions +expression_orig = (add, (mul, 3, -2), (mul, (add, 1, (mul, 2, 3)), -1)) +expression1 = (add, (mul, (add, 1, (mul, 2, a)), b), (mul, 3, c)) +expression2 = (add, (mul, c, 3), (mul, b, (add, (mul, 2, a), 1))) +expression3 = (add, (add, (mul, (mul, 2, a), b), b), (mul, 3, c)) +``` + +将表达式与原始表达式进行比较。 方法运行通常在`logpy`中使用。 此方法采用输入参数并运行表达式。 第一个参数是值的数量,第二个参数是变量,第三个参数是函数: + +```py +# Compare expressions +print(run(0, (a, b, c), la.eq_assoccomm(expression1, expression_orig))) +print(run(0, (a, b, c), la.eq_assoccomm(expression2, expression_orig))) +print(run(0, (a, b, c), la.eq_assoccomm(expression3, expression_orig))) +``` + +完整代码在`expression_matcher.py`中给出。 如果运行代码,将看到以下输出: + +```py +((3, -1, -2),) +((3, -1, -2),) +() +``` + +的前两行中的三个值表示`a`,`b`和`c`的值。 前两个表达式与原始表达式匹配,而第三个则什么也不返回。 这是因为,尽管第三个表达式在数学上是相同的,但在结构上却有所不同。 模式比较通过比较和表达式的结构来进行。 + +# 验证素数 + +让我们看看如何使用逻辑编程检查素数。 我们将使用`logpy`中可用的构造来确定给定列表中的哪些数字是质数,以及找出给定数字是否为质数。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import itertools as it +import logpy.core as lc +from sympy.ntheory.generate import prime, isprime +``` + +接下来,定义一个函数,该函数根据数据类型检查给定数字是否为质数。 如果是数字,则很简单。 如果它是一个变量,那么我们必须运行顺序操作。 为了提供一些背景知识,方法`conde`是一个目标构造函数,提供逻辑 AND 和 OR 运算。 + +方法`condeseq`类似于`conde`,但是它支持目标的通用迭代: + +```py +# Check if the elements of x are prime +def check_prime(x): + if lc.isvar(x): + return lc.condeseq([(lc.eq, x, p)] for p in map(prime, it.count(1))) + else: + return lc.success if isprime(x) else lc.fail +``` + +声明将要使用的变量`x`: + +```py +# Declate the variable +x = lc.var() +``` + +定义一组数字并检查哪些数字是质数。 方法`membero` 检查给定数字是否为输入参数中指定的数字列表的成员: + +```py +# Check if an element in the list is a prime number +list_nums = (23, 4, 27, 17, 13, 10, 21, 29, 3, 32, 11, 19) +print('\nList of primes in the list:') +print(set(lc.run(0, x, (lc.membero, x, list_nums), (check_prime, x)))) +``` + +现在,通过打印前 7 个质数,以稍微不同的方式使用该函数: + +```py +# Print first 7 prime numbers +print('\nList of first 7 prime numbers:') +print(lc.run(7, x, check_prime(x))) +``` + +完整代码为`prime.py`中提供的。 如果运行代码,将看到以下输出: + +```py +List of primes in the list: +{3, 11, 13, 17, 19, 23, 29} +List of first 7 prime numbers: (2, 3, 5, 7, 11, 13, 17) +``` + +您可以确认输出 t 值正确。 + +# 解析家谱 + +现在,我们对逻辑编程有了更多的了解,让我们使用它来解决一个有趣的问题。 考虑以下族谱: + +![](img/B15441_09_02.png) + +图 2:样本族谱 + +约翰和梅根有三个儿子-威廉,大卫和亚当。 威廉,大卫和亚当的妻子分别是艾玛,奥利维亚和莉莉。 威廉和艾玛有两个孩子-克里斯和斯蒂芬妮。 大卫和奥利维亚有五个孩子-韦恩,蒂芙尼,朱莉,尼尔和彼得。 亚当和莉莉有一个孩子-索菲娅。 基于这些事实,我们可以创建一个程序来告诉我们韦恩的祖父或索菲娅的叔叔的名字。 即使我们没有明确指定有关祖父母或叔叔关系的任何内容,逻辑编程也可以推断出它们。 + +这些关系是在为您提供的名为`relationships.json`的文件中指定的。 该文件如下所示: + +```py +{ + "father": + [ + {"John": "William"}, + {"John": "David"}, + {"John": "Adam"}, + {"William": "Chris"}, + {"William": "Stephanie"}, + {"David": "Wayne"}, + {"David": "Tiffany"}, + {"David": "Julie"}, + {"David": "Neil"}, + {"David": "Peter"}, + {"Adam": "Sophia"} + ], + "mother": + [ + {"Megan": "William"}, + {"Megan": "David"}, + {"Megan": "Adam"}, + {"Emma": "Stephanie"}, + {"Emma": "Chris"}, + {"Olivia": "Tiffany"}, + {"Olivia": "Julie"}, + {"Olivia": "Neil"}, + {"Olivia": "Peter"}, + {"Lily": "Sophia"} + ] +} +``` + +这是一个简单的 JSON 文件,用于指定父母之间的关系。 请注意,我们没有指定有关丈夫和妻子,祖父母或叔叔的任何信息。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import json +from logpy import Relation, facts, run, conde, var, eq +``` + +定义一个函数来检查`x`是否是`y`的父级。 我们将使用以下逻辑:如果`x`是`y`的父母,则`x`是父亲或母亲。 我们已经在事实基础中定义了“父亲”和“母亲”: + +```py +# Check if 'x' is the parent of 'y' +def parent(x, y): + return conde([father(x, y)], [mother(x, y)]) +``` + +定义一个函数来检查`x`是否为`y`的祖父母。 我们将使用以下逻辑:如果`x`是`y`的祖父母,则`x`的后代将是`y`的父代: + +```py +# Check if 'x' is the grandparent of 'y' +def grandparent(x, y): + temp = var() + return conde((parent(x, temp), parent(temp, y))) +``` + +定义一个函数来检查`x`是否是`y`的兄弟。 我们将使用以下逻辑:如果`x`是`y`的兄弟,则`x`和`y`将具有相同的父代。 请注意,此处需要进行一些修改,因为当我们列出`x`的所有同级时,也会列出`x`,因为`x`满足这些条件。 因此,当我们打印输出时,我们将不得不从列表中删除`x`。 我们将在`main`函数中对此进行讨论: + +```py +# Check for sibling relationship between 'a' and 'b' +def sibling(x, y): + temp = var() + return conde((parent(temp, x), parent(temp, y))) +``` + +定义一个函数以检查`x`是否是`y`的叔叔。 我们将使用以下逻辑:如果`x`是`y`的叔叔,那么`x`的祖父母将与`y`的父母相同。 请注意,此处需要进行一些修改,因为当我们列出`x`的所有叔叔时,也会列出`x`的父亲,因为`x`的父亲满足了这些条件。 因此,当我们打印输出时,我们将不得不从列表中删除`x`的父亲。 我们将在主要功能中对此进行讨论: + +```py +# Check if x is y's uncle +def uncle(x, y): + temp = var() + return conde((father(temp, x), grandparent(temp, y))) +``` + +定义`main`函数并初始化关系`father`和`mother`: + +```py +if __name__=='__main__': + father = Relation() + mother = Relation() +``` + +从`relationships.json`文件中加载数据: + +```py + with open('relationships.json') as f: + d = json.loads(f.read()) +``` + +读取数据并将其添加到事实库: + +```py + for item in d['father']: + facts(father, (list(item.keys())[0], list(item.values())[0])) +``` + +```py + for item in d['mother']: + facts(mother, (list(item.keys())[0], list(item.values())[0])) +``` + +定义变量`x`: + +```py + x = var() +``` + +现在,我们准备提出一些问题,看看求解器能否提出正确的答案。 让我们问一下约翰的孩子是谁: + +```py + # John's children + name = 'John' + output = run(0, x, father(name, x)) + print("\nList of " + name + "'s children:") + for item in output: + print(item) +``` + +威廉的母亲是谁? + +```py + # William's mother + name = 'William' + output = run(0, x, mother(x, name))[0] + print("\n" + name + "'s mother:\n" + output) +``` + +亚当的父母是谁? + +```py + # Adam's parents name = 'Adam' + output = run(0, x, parent(x, name)) + print("\nList of " + name + "'s parents:") + for item in output: + print(item) +``` + +谁是韦恩的祖父母? + +```py + # Wayne's grandparents name = 'Wayne' + output = run(0, x, grandparent(x, name)) + print("\nList of " + name + "'s grandparents:") + for item in output: + print(item) +``` + +梅根的孙子是谁? + +```py + # Megan's grandchildren + name = 'Megan' + output = run(0, x, grandparent(name, x)) + print("\nList of " + name + "'s grandchildren:") + for item in output: + print(item) +``` + +大卫的兄弟姐妹是谁? + +```py + # David's siblings + name = 'David' + output = run(0, x, sibling(x, name)) + siblings = [x for x in output if x != name] + print("\nList of " + name + "'s siblings:") + for item in siblings: + print(item) +``` + +蒂芙尼的叔叔是谁 + +```py + # Tiffany's uncles + name = 'Tiffany' + name_father = run(0, x, father(x, name))[0] + output = run(0, x, uncle(x, name)) + output = [x for x in output if x != name_father] + print("\nList of " + name + "'s uncles:") + for item in output: + print(item) +``` + +列出家庭中的所有配偶: + +```py + # All spouses + a, b, c = var(), var(), var() + output = run(0, (a, b), (father, a, c), (mother, b, c)) + print("\nList of all spouses:") + for item in output: + print('Husband:', item[0], '<==> Wife:', item[1]) +``` + +完整代码为`family.py`中提供的。 如果运行代码,您将看到一些输出。 前半部分如下所示: + +![](img/B15441_09_03.png) + +图 3:族谱示例输出 + +下半部分如下所示:: + +![](img/B15441_09_04.png) + +图 4:族谱示例输出 + +您可以将输出与族谱进行比较,以确保在位上的确实正确。 + +# 地理分析 + +让我们使用逻辑编程来建立一个求解器来分析地理。 在此问题中,我们将指定有关美国各州位置的信息,然后查询程序以根据这些事实和规则回答各种问题。 以下是美国的地图: + +![](img/B15441_09_05.png) + +图 5:相邻和沿海州示例图 + +已为您提供了两个名为`adjacent_states.txt` 和`coastal_states.txt`的文本文件。 这些文件包含有关哪些州彼此相邻以及哪些州沿岸的详细信息。 基于此,我们可以获得有趣的信息,例如“俄克拉荷马州和德克萨斯州都毗邻哪些州?” 或“哪个沿海州与新墨西哥州和路易斯安那州相邻?” + +创建一个新的 Python 文件并导入以下内容: + +```py +from logpy import run, fact, eq, Relation, var +``` + +初始化关系: + +```py +adjacent = Relation() +coastal = Relation() +``` + +定义输入文件以从以下位置加载数据: + +```py +file_coastal = 'coastal_states.txt' +file_adjacent = 'adjacent_states.txt' +``` + +加载数据: + +```py +# Read the file containing the coastal states +with open(file_coastal, 'r') as f: + line = f.read() + coastal_states = line.split(',') +``` + +将信息添加到事实库: + +```py +# Add the info to the fact base +for state in coastal_states: + fact(coastal, state) +``` + +读取相邻数据: + +```py +# Read the file containing the coastal states +with open(file_adjacent, 'r') as f: + adjlist = [line.strip().split(',') for line in f if line and line[0].isalpha()] +``` + +将邻接信息添加到事实库中: + +```py +# Add the info to the fact base +for L in adjlist: + head, tail = L[0], L[1:] + for state in tail: + fact(adjacent, head, state) +``` + +初始化变量`x`和`y`: + +```py +# Initialize the variables +x = var() +y = var() +``` + +现在,我们准备提出一些问题。 检查内华达州是否与路易斯安那州相邻: + +```py +# Is Nevada adjacent to Louisiana? +output = run(0, x, adjacent('Nevada', 'Louisiana')) +print('\nIs Nevada adjacent to Louisiana?:') +print('Yes' if len(output) else 'No') +``` + +打印出与俄勒冈州相邻的所有州: + +```py +# States adjacent to Oregon +output = run(0, x, adjacent('Oregon', x)) +print('\nList of states adjacent to Oregon:') +for item in output: + print(item) +``` + +列出与密西西比州相邻的所有沿海州: + +```py +# States adjacent to Mississippi that are coastal +output = run(0, x, adjacent('Mississippi', x), coastal(x)) +print('\nList of coastal states adjacent to Mississippi:') +for item in output: + print(item) +``` + +列出与沿海国接壤的七个州: + +```py +# List of 'n' states that border a coastal state n = 7 +output = run(n, x, coastal(y), adjacent(x, y)) +print('\nList of ' + str(n) + ' states that border a coastal state:') +for item in output: + print(item) +``` + +列出与阿肯色州和肯塔基州相邻的州: + +```py +# List of states that adjacent to the two given states +output = run(0, x, adjacent('Arkansas', x), adjacent('Kentucky', x)) +print('\nList of states that are adjacent to Arkansas and Kentucky:') +for item in output: + print(item) +``` + +完整代码在`states.py`中给出。 如果运行代码,将看到以下输出: + +![](img/B15441_09_06.png) + +图 6:相邻和沿海州示例输出 + +您可以将输出与美国地图进行交叉检查,以验证答案是否正确。 您也可以在程序中添加更多问题,以查看是否可以回答这些问题。 + +# 构建难题解决器 + +逻辑编程的另一个有趣的应用是解决难题。 我们可以指定难题的条件,程序将提供解决方案。 在本节中,我们将指定有关四个人的各种信息,并要求提供丢失的信息。 + +在逻辑程序中,我们按如下方式指定难题: + +* 史蒂夫有辆蓝色的汽车。 +* 养猫的人住在加拿大。 马修住在美国。 +* 拥有黑色汽车的人居住在澳大利亚。 +* 杰克有一只猫。 +* 阿尔弗雷德(Alfred)居住在澳大利亚。 +* 养狗的人住在法国。 +* 谁有兔子? + +目的是找到有兔子的人。 以下是有关这四个人的完整详细信息: + +![](img/B15441_09_07.png) + +图 7:解谜器输入数据 + +创建一个新的 Python 文件并导入以下软件包: + +```py +from logpy import * +from logpy.core import lall +``` + +声明变量`people`: + +```py +# Declare the variable people +people = var() +``` + +使用`lall`定义所有规则。 第一条规则是有四个人: + +```py +# Define the rules +rules = lall( + # There are 4 people + (eq, (var(), var(), var(), var()), people), +``` + +名为史蒂夫的人有一辆蓝色轿车: + +```py + # Steve's car is blue + (membero, ('Steve', var(), 'blue', var()), people), +``` + +养猫的人住在加拿大: + +```py + # Person who has a cat lives in Canada + (membero, (var(), 'cat', var(), 'Canada'), people), +``` + +名为 Matthew 的人住在美国: + +```py + # Matthew lives in USA + (membero, ('Matthew', var(), var(), 'USA'), people), +``` + +拥有黑色汽车的人居住在澳大利亚: + +```py + # The person who has a black car lives in Australia + (membero, (var(), var(), 'black', 'Australia'), people), +``` + +叫杰克的人有一只猫: + +```py + # Jack has a cat + (membero, ('Jack', 'cat', var(), var()), people), +``` + +名为 Alfred 的人居住在澳大利亚: + +```py + # Alfred lives in Australia + (membero, ('Alfred', var(), var(), 'Australia'), people), +``` + +养狗的人住在法国: + +```py + # Person who owns the dog lives in France + (membero, (var(), 'dog', var(), 'France'), people), +``` + +这一组人中有一只兔子。 那个人是谁? + +```py + # Who has a rabbit? + (membero, (var(), 'rabbit', var(), var()), people) +) +``` + +使用上述约束运行求解器: + +```py +# Run the solver +solutions = run(0, people, rules) +``` + +从解决方案中提取输出: + +```py +# Extract the output +output = [house for house in solutions[0] if 'rabbit' in house][0][0] +``` + +打印从求解器获得的完整矩阵: + +```py +# Print the output +print('\n' + output + ' is the owner of the rabbit') +print('\nHere are all the details:') +attribs = ['Name', 'Pet', 'Color', 'Country'] +print('\n' + '\t\t'.join(attribs)) +print('=' * 57) +for item in solutions[0]: + print('') + print('\t\t'.join([str(x) for x in item])) +``` + +完整代码在`puzzle.py`中给出。 如果运行代码,将看到以下输出: + +![](img/B15441_09_08.png) + +图 8:解谜器输出 + +前面的图显示了使用求解器获得的所有值。 如编号名称所示,其中一些仍然未知。 即使信息不完整,求解器仍能够回答问题。 但是为了回答每个问题,您可能需要添加更多规则。 该程序旨在演示如何用不完整的信息解决难题。 您可以尝试使用它,看看如何为各种场景构建拼图解算器。 + +# 摘要 + +在本章中,我们学习了如何使用逻辑编程编写 Python 程序。 我们讨论了各种编程范例如何处理构建程序。 我们了解了如何在逻辑编程中构建程序。 我们了解了逻辑编程的各种构建块,并讨论了如何解决此领域的问题。 我们实施了各种 Python 程序来解决有趣的问题和难题。 + +在下一章中,我们将学习启发式搜索技术,并使用这些算法来解决现实世界中的问题。 \ No newline at end of file diff --git a/docs/ai-py/10.md b/docs/ai-py/10.md new file mode 100644 index 0000000000000000000000000000000000000000..5b737fe11077c4a8dc47da59e55a1fcd88310afe --- /dev/null +++ b/docs/ai-py/10.md @@ -0,0 +1,903 @@ +# 10 + +# 启发式搜索技术 + +在本章中,我们将学习启发式搜索技术。 启发式搜索技术用于搜索解决方案空间以得出答案。 使用指导搜索算法的试探法进行搜索。 这种启发式算法可使算法加快处理速度,否则将需要很长时间才能得出解决方案。 + +在本章结束时,您将了解以下内容: + +* 什么是启发式搜索? +* 不知情还是知情搜索 +* 约束满意度问题 +* 本地搜索技术 +* 模拟退火 +* 使用贪婪搜索构造字符串 +* 解决约束问题 +* 解决区域着色问题 +* 构建 8 难题求解器 +* 构建一个迷宫求解器 + +# 启发式搜索是人工智能吗? + +在*第 2 章*和*人工智能的基本用例*中,我们了解了 Pedro Domingos 定义的五个部落。 *象征主义者*部落是最“古老”的部落之一。 至少对我来说,这一事实不足为奇。 作为人类,我们尝试在所有事物中找到规则和模式。 不幸的是,世界有时是混乱的,并非所有事物都遵循简单的规则。 + +这就是为什么当我们没有秩序的世界时,其他部落出现来帮助我们的原因。 但是,当我们的搜索空间较小且域受到限制时,使用启发式,约束满足以及本章中介绍的其他技术对于这组问题很有用。 当组合的数量相对较少且组合爆炸受到限制时,这些技术很有用。 例如,当城市数量大约为 20 时,使用这些技术解决旅行商问题很简单。如果我们尝试对 *n* = 2000 求解相同的问题,则必须使用其他方法来解决。 不要探索整个空间,而只能给出结果的近似值。 + +# 什么是启发式搜索? + +搜索和组织数据是人工智能中的重要主题。 有许多问题需要在解决方案领域内寻找答案。 对于给定的问题,有许多可能的解决方案,我们不知道哪个是正确的。 通过有效地组织数据,我们可以快速有效地寻找解决方案。 + +通常,解决给定问题的选项太多,以至于无法开发单个算法来找到确定的最佳解决方案。 同样,不可能通过所有解决方案,因为这过于昂贵。 在这种情况下,我们依靠经验法则,通过消除明显错误的选项来帮助我们缩小搜索范围。 这个经验法则称为,称为**启发式**。 使用启发式搜索指导搜索的方法称为**启发式搜索**。 + +启发式技术之所以强大,是因为它们可以加快过程。 即使启发式方法无法消除某些选项,也将有助于订购 t 软管选项,以便可能首先提出更好的解决方案。 如前所述,启发式搜索在计算上可能会很昂贵。 现在,我们将学习如何使用*快捷方式*和*修剪*搜索 tr ee。 + +## 不知情或知情的搜索 + +如果您熟悉计算机科学,则可能听说过深度优先搜索( **DFS** ),**广度优先 搜索**( **BFS** ),以及**统一成本搜索**( **UCS** )。 这些是搜索技术,其中通常用于图形上以获得解决方案。 这些是不知情的搜索示例。 他们不使用任何先验信息或规则来消除某些路径。 他们检查所有可能的路径并选择最佳路径。 + +另一方面,启发式搜索称为**通知搜索**,因为它使用先验信息或规则来消除不必要的路径。 不了解情况的搜索技术不会考虑目标。 不了解情况的搜索技术会盲目搜索,并且对最终解决方案没有先验知识。 + +在图问题中,启发式可以用来指导搜索。 例如,在每个节点处,我们可以定义一个启发式函数,该函数返回一个分数,该分数表示从当前节点到目标的路径成本估算。 通过定义此启发式函数,我们可以将正确的方向告知搜索技术以达到目标。 这将允许算法识别哪个邻居将导致目标。 + +我们需要注意,启发式搜索可能并不总是总是找到最佳解决方案。 这是因为我们不是在探索每种可能性,而是在依靠启发式方法。 搜索可以确保在合理的时间内找到一个好的解决方案,但这是我们对实际解决方案的期望。 在现实世界中,我们需要快速有效的解决方案。 启发式搜索通过快速找到合理的解决方案来提供有效的解决方案。 它们用于无法以其他任何方式解决问题或需要很长时间才能解决的问题。 *修剪*树的另一种方法是利用数据固有的约束。 在下一部分中,我们将学习更多利用这些 c 约束的修剪技术。 + +# 约束满意度问题 + +有许多必须在约束条件下解决的问题。 这些约束基本上是解决问题的过程中不能违反的条件。 + +这些问题称为**约束满意度问题**( **CSP** )。 + +为了获得一些直观的理解,让我们快速看一下 Sudoku 拼图的示例部分。 数独游戏是我们不能在水平线,垂直线或同一方块中两次拥有相同的数字。 这是数独板的示例: + +![A black and silver text on a white surface Description automatically generated](img/B15441_10_01.png) + +图 1:数独板的示例 + +使用约束的满意度和 Sudoku 的规则,我们可以快速确定尝试使用哪些数字以及不尝试解决该难题的数字。 例如,在这个方块中: + +![](img/B15441_10_02.png) + +图 2:考虑数独问题 + +如果我们不使用 CSP,则一种蛮力方法是尝试插槽中所有数字的组合,然后检查规则是否适用。 例如,我们的第一个尝试可能是用数字 1 填充所有正方形,然后检查结果。 + +使用 CSP,我们可以在尝试之前对它们进行修剪。 + +让我们来看看我们认为数字应该是红色突出显示的正方形的含义。 我们知道该数字不能为 1、6、8 或 9,因为这些数字已经存在于正方形中。 我们也知道它不能为 2 或 7,因为这些数字存在于水平线中。 我们也知道它不能是 3 或 4,因为这些数字已经在垂直线上。 这给我们留下了唯一的数字应该是– 5 的可能性。 + +CSP 是数学上的问题,定义为一组必须满足一些约束的变量。 当我们得出最终解决方案时,变量的状态必须服从所有约束。 该技术将涉及给定问题的实体表示为变量上固定数量的约束的集合。 这些变量需要通过约束满足方法来解决。 + +这些问题需要在合理的时间内解决启发式方法和其他搜索技术的问题。 在这种情况下,我们将使用约束满足技术来解决有限域上的问题。 有限域由有限数量的元素组成。 由于我们正在处理有限域,因此我们可以使用搜索技术来获得解决方案。 为了进一步了解 CSP,我们现在将学习如何使用本地搜索技术来解决 C SP 问题。 + +# 本地搜索技术 + +本地搜索是解决 CSP 的一种方法。 它会不断优化解决方案,直到满足所有约束条件为止。 迭代地不断更新变量,直到我们到达目的地。 这些算法会在过程的每个步骤中修改值,使我们更接近目标。 在解决方案空间中,更新的值比先前的值更接近目标。 因此,这被称为本地搜索。 + +局部搜索算法是一种启发式搜索算法。 这些算法使用一个函数来计算每次更新的质量。 例如,它可以计算当前更新违反的约束数量,也可以查看更新如何影响到目标的距离。 这称为分配成本。 本地搜索的总体目标是在每个步骤中找到最小的成本更新。 + +爬山是流行的本地搜索技术。 它使用一种启发式函数来衡量当前状态和目标之间的差异。 当我们开始时,它将检查状态是否是最终目标。 如果是,则停止。 如果不是,则选择更新并生成新状态。 如果它比当前状态更接近目标,那么它将成为当前状态。 如果不是,它将忽略它并继续该过程,直到检查所有可能的更新。 它基本上是爬山直到到达山顶。 + +![](img/B15441_10_03.png) + +图 3: 爬山 + +## 模拟退火 + +**模拟退火**是本地搜索的一种,也是一种随机搜索技术。 随机搜索技术广泛用于各种领域,例如机器人技术,化学,制造,医学和经济学。 随机算法用于解决许多现实问题:我们可以执行诸如优化机器人设计,确定工厂中自动控制的时序策略以及规划交通量之类的事情。 + +模拟退火是爬山技术的一种变化。 爬山的主要问题之一是,它最终会爬上错误的山麓小丘。 这意味着它被卡在局部最大值中。 因此,在做出任何攀登决定之前,最好先检查一下整个空间。 为了实现这一目标,首先对整个空间进行探索以了解其外观。 这有助于我们避免陷入高原或局部最大值。 + +在模拟退火中,我们重新构造了问题并针对最小化(而不是最大化)进行了求解。 因此,我们现在正下降到山谷中,而不是爬山。 我们几乎在做相同的事情,但是方式不同。 + +我们使用目标函数来指导搜索。 该目标函数用作试探法。 + +之所以将其称为*模拟退火*是因为它源自冶金过程。 在此过程中,我们首先加热金属,使原子扩散到金属中,然后冷却直到达到原子结构安排所需的最佳状态。 通常,这是为了改变金属的物理特性,使其变得更软,更容易加工。 + +我们冷却系统的速率称为,**退火时间表**。 冷却速度很重要,因为它会直接影响结果。 在金属的现实世界中,如果冷却速度过快,则最终会过快地陷入非理想状态(原子结构)。 例如,如果将加热的金属放入冷水中,它最终会很快沉降到不需要的结构中,从而使金属变脆。 + +如果冷却速度缓慢且可控,则金属就有机会达到最佳原子结构,从而获得所需的物理性能。 在这种情况下,迅速采取大步向任何山丘进发的机会较低。 由于冷却速度很慢,因此需要花费一些时间才能进入最佳状态。 可以用数据完成类似的操作。 + +我们首先评估当前的状态,看看它是否已达到目标。 如果有,那么我们停止。 如果不是,则将最佳状态变量设置为当前状态。 然后,我们定义一个退火计划,以控制其下降到谷底的速度。 计算当前状态和新状态之间的差异。 如果新状态不是更好,则以一定的预定义概率将其设置为当前状态。 这是使用随机数生成器并根据阈值确定的。 如果它高于阈值,那么我们将最佳状态设置为该状态。 基于此,根据节点数更新退火计划。 我们一直这样做,直到达到目标。 另一个本地搜索技术是贪婪搜索算法。 我们将在下一部分中详细了解。 + +# 使用贪婪搜索构造字符串 + +**贪婪搜索**是一种算法范式,它在每​​个阶段进行局部最优选择,以便找到全局最优值。 但是在许多问题中,贪婪算法无法产生全局最优解。 使用贪婪算法的一个优点是它们可以在合理的时间内产生一个近似解。 希望该近似解可以合理地接近全局最优解。 + +贪婪算法不会在搜索过程中基于新的信息来优化的解决方案。 例如,假设您正在计划一次公路旅行,并且想要采取最佳路线。 如果您使用贪婪算法来规划路线,则可能会要求您选择距离较短但可能会花费更多时间的路线。 在短期内,这也可能会导致您走上更快的路径,但稍后可能会导致交通拥堵。 发生这种情况是因为贪婪算法只看到下一步,而没有全局最优的最终解决方案。 + +让我们看看如何使用贪婪搜索解决问题。 在此问题中,我们将尝试根据字母重新创建输入字符串。 我们将要求算法搜索解决方案空间并构建解决方案的路径。 + +在本章中,我们将使用名为`simpleai`的软件包。 它包含各种例程,这些例程可用于使用启发式搜索技术构建解决方案。 可在 [https://github.com/simpleai-team/simpleai](https://github.com/simpleai-team/simpleai) 上获得。 为了使它在 Python 3 中工作,我们需要对源代码进行一些更改。随同本书的代码一起提供了一个名为`simpleai.zip`的文件。 将此文件解压缩到名为`simpleai`的文件夹中。 此文件夹包含对原始库的所有必要更改,以使其能够在 Python 3 中工作。将`simpleai`文件夹与您的代码位于同一文件夹中,并将置于您的代码中 顺利。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import argparse +import simpleai.search as ss +``` + +定义一个函数来解析输入参数: + +```py +def build_arg_parser(): + parser = argparse.ArgumentParser(description='Creates the input string \ + using the greedy algorithm') + parser.add_argument("--input-string", dest="input_string", required=True, + help="Input string") + parser.add_argument("--initial-state", dest="initial_state", required=False, + default='', help="Starting point for the search") + return parser +``` + +创建一个包含解决问题所需方法的类。 此类继承了库中可用的`SearchProblem`类。 需要重写一些方法来解决当前的问题。 第一种方法`set_target`是定义目标字符串的自定义方法: + +```py +class CustomProblem(ss.SearchProblem): + def set_target(self, target_string): + self.target_string = target_string +``` + +这些动作是`SearchProblem`随附的一种方法,需要重写。 它负责朝着目标采取正确的步骤。 如果当前字符串的长度小于目标字符串的长度,它将返回可能的字母列表以供选择。 如果没有,它将返回一个空字符串: + +```py + # Check the current state and take the right action + def actions(self, cur_state): + if len(cur_state) < len(self.target_string): + alphabets = 'abcdefghijklmnopqrstuvwxyz' + return list(alphabets + ' ' + alphabets.upper()) + else: + return [] +``` + +现在创建一个方法,通过将当前字符串和需要采取的措施串联起来来计算结果。 此方法随附`SearchProblem`,我们将其覆盖: + +```py + # Concatenate state and action to get the result + def result(self, cur_state, action): + return cur_state + action +``` + +方法`is_goal`是`SearchProblem`的一部分,用于检查是否已达到目标: + +```py + # Check if goal has been achieved + def is_goal(self, cur_state): + return cur_state == self.target_string +``` + +方法`heuristic`也是`SearchProblem`的一部分,我们需要重写它。 定义了一种启发式方法,即将用于解决该问题。 执行计算以查看目标有多远,并将其用作启发将其引导至目标: + +```py + # Define the heuristic that will be used + def heuristic(self, cur_state): + # Compare current string with target string + dist = sum([1 if cur_state[i] != self.target_string[i] else 0 + for i in range(len(cur_state))]) +``` + +```py + # Difference between the lengths + diff = len(self.target_string) - len(cur_state) + return dist + diff +``` + +初始化输入参数: + +```py +if __name__=='__main__': + args = build_arg_parser().parse_args() +``` + +初始化`CustomProblem`对象: + +```py + # Initialize the object + problem = CustomProblem() +``` + +设置起点和我们要实现的目标: + +```py + # Set target string and initial state + problem.set_target(args.input_string) + problem.initial_state = args.initial_state +``` + +运行求解器: + +```py + # Solve the problem + output = ss.greedy(problem) +``` + +打印解决方案的路径: + +```py + print('\nTarget string:', args.input_string) + print('\nPath to the solution:') + for item in output.path(): + print(item) +``` + +完整代码在文件`greedy_search.py`中给出。 如果您以空的初始状态运行代码: + +```py +$ python3 greedy_search.py --input-string 'Artificial Intelligence' --initial-state '' +``` + +您将获得以下输出: + +![](img/B15441_10_04.png) + +图 4:以空的初始状态运行时的代码输出 + +如果您以非空的起点运行代码: + +```py +$ python3 greedy_search.py --input-string 'Artificial Intelligence with Python' --initial-state 'Artificial Inte' +``` + +您将获得以下输出: + +![](img/B15441_10_05.png) + +图 5:以非空初始状态运行时的代码输出 + +现在我们已经涵盖了一些流行的搜索技术,我们将继续使用这些搜索算法来解决 中的一些实际问题。 + +# 解决约束问题 + +我们已经讨论了如何制定 CSP。 让我们将应用于实际问题。 在此问题中,我们有一个名称列表,每个名称可以采用一组固定的值。 这些人之间还存在一系列需要满足的约束。 让我们来看看如何做。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +from simpleai.search import CspProblem, backtrack, \ + min_conflicts, MOST_CONSTRAINED_VARIABLE, \ + HIGHEST_DEGREE_VARIABLE, LEAST_CONSTRAINING_VALUE +``` + +定义约束,该约束指定输入列表中的所有变量应具有唯一值: + +```py +# Constraint that expects all the different variables +# to have different values +def constraint_unique(variables, values): + # Check if all the values are unique + return len(values) == len(set(values)) +``` + +定义约束,该约束指定第一个变量应大于第二个变量: + +```py +# Constraint that specifies that one variable +# should be bigger than other +def constraint_bigger(variables, values): + return values[0] > values[1] +``` + +定义指定的约束,如果第一个变量为奇数,则第二个变量应为偶数,反之亦然: + +```py +# Constraint that specifies that there should be +# one odd and one even variables in the two variables +def constraint_odd_even(variables, values): + # If first variable is even, then second should + # be odd and vice versa + if values[0] % 2 == 0: + return values[1] % 2 == 1 + else: + return values[1] % 2 == 0 +``` + +定义`main`函数并定义变量: + +```py +if __name__=='__main__': + variables = ('John', 'Anna', 'Tom', 'Patricia') +``` + +定义每个变量可以采用的值列表: + +```py + domains = { + 'John': [1, 2, 3], + 'Anna': [1, 3], + 'Tom': [2, 4], + 'Patricia': [2, 3, 4], + } +``` + +定义各种方案的约束。 在这种情况下,我们指定了以下三个约束: + +* 约翰,安娜和汤姆应该有不同的价值观 +* 汤姆的价值应大于安娜的价值 +* 如果 John 的值是奇数,则 Patricia 的值应该是偶数,反之亦然 + +使用以下代码: + +```py + constraints = [ + (('John', 'Anna', 'Tom'), constraint_unique), + (('Tom', 'Anna'), constraint_bigger), + (('John', 'Patricia'), constraint_odd_even), + ] +``` + +使用前面的变量和约束来初始化`CspProblem`对象: + +```py + problem = CspProblem(variables, domains, constraints) +``` + +计算解决方案并打印: + +```py + print('\nSolutions:\n\nNormal:', backtrack(problem)) +``` + +使用和`MOST_CONSTRAINED_VARIABLE`启发式计算解决方案: + +```py + print('\nMost constrained variable:', backtrack(problem, + variable_heuristic=MOST_CONSTRAINED_VARIABLE)) +``` + +使用`HIGHEST_DEGREE_VARIABLE`启发式计算解决方案: + +```py + print('\nHighest degree variable:', backtrack(problem, + variable_heuristic=HIGHEST_DEGREE_VARIABLE)) +``` + +使用`LEAST_CONSTRAINING_VALUE`启发式计算解决方案: + +```py + print('\nLeast constraining value:', backtrack(problem, + value_heuristic=LEAST_CONSTRAINING_VALUE)) +``` + +使用`MOST_CONSTRAINED_VARIABLE`变量启发式和`LEAST_CONSTRAINING_VALUE`值启发式计算解决方案: + +```py + print('\nMost constrained variable and least constraining value:', + backtrack(problem, variable_heuristic=MOST_CONSTRAINED_VARIABLE, + value_heuristic=LEAST_CONSTRAINING_VALUE)) +``` + +使用`HIGHEST_DEGREE_VARIABLE`变量启发式和`LEAST_CONSTRAINING_VALUE`值启发式计算解决方案: + +```py + print('\nHighest degree and least constraining value:', + backtrack(problem, variable_heuristic=HIGHEST_DEGREE_VARIABLE, + value_heuristic=LEAST_CONSTRAINING_VALUE)) +``` + +使用最小冲突启发式计算解决方案: + +```py +print('\nMinimum conflicts:', min_conflicts(problem)) +``` + +完整代码为文件`constrained_problem.py`中提供的。 如果运行,则将得到以下输出: + +![](img/B15441_10_06.png) + +图 6:以最小冲突启发式计算解决方案 + +您可以检查约束条件以查看解决方案 ns 是否满足所有这些约束条件。 + +# 解决区域着色问题 + +让我们使用约束满足框架来解决区域着色问题。 考虑以下屏幕截图: + +![](img/B15441_10_07.png) + +图 7:区域着色问题的框架 + +在上图中,我们有一些区域用名称标记。 目标是用四种颜色进行着色,以使相邻区域都不具有相同的颜色。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +from simpleai.search import CspProblem, backtrack +``` + +定义指定值应该不同的约束: + +```py +# Define the function that imposes the constraint +# that neighbors should be different +def constraint_func(names, values): + return values[0] != values[1] +``` + +定义`main`函数并指定名称列表: + +```py +if __name__=='__main__': + # Specify the variables + names = ('Mark', 'Julia', 'Steve', 'Amanda', 'Brian', + 'Joanne', 'Derek', 'Allan', 'Michelle', 'Kelly') +``` + +定义可能的颜色列表: + +```py + # Define the possible colors + colors = dict((name, ['red', 'green', 'blue', 'gray']) for name in names) +``` + +我们需要将地图信息转换为该算法可以理解的信息。 让我们通过指定彼此相邻的人员列表来定义约束: + +```py + # Define the constraints + constraints = [ + (('Mark', 'Julia'), constraint_func), + (('Mark', 'Steve'), constraint_func), + (('Julia', 'Steve'), constraint_func), + (('Julia', 'Amanda'), constraint_func), + (('Julia', 'Derek'), constraint_func), + (('Julia', 'Brian'), constraint_func), + (('Steve', 'Amanda'), constraint_func), + (('Steve', 'Allan'), constraint_func), + (('Steve', 'Michelle'), constraint_func), + (('Amanda', 'Michelle'), constraint_func), + (('Amanda', 'Joanne'), constraint_func), + (('Amanda', 'Derek'), constraint_func), + (('Brian', 'Derek'), constraint_func), + (('Brian', 'Kelly'), constraint_func), + (('Joanne', 'Michelle'), constraint_func), + (('Joanne', 'Amanda'), constraint_func), + (('Joanne', 'Derek'), constraint_func), + (('Joanne', 'Kelly'), constraint_func), + (('Derek', 'Kelly'), constraint_func), + ] +``` + +使用变量和约束来初始化对象: + +```py + # Solve the problem + problem = CspProblem(names, colors, constraints) +``` + +解决问题并打印解决方案: + +```py + # Print the solution + output = backtrack(problem) + print('\nColor mapping:\n') + for k, v in output.items(): + print(k, '==>', v) +``` + +完整代码为文件`coloring.py`中提供的。 如果运行代码,将得到以下输出: + +![](img/B15441_10_08.png) + +图 8:颜色映射输出 + +如果根据此输出为区域着色,将获得以下机翼: + +![](img/B15441_10_09.png) + +图 9:解决区域着色问题 + +您可以检查没有两个相邻的 cent 区域具有相同的颜色。 + +# 构建 8 难题求解器 + +8 拼图是 15 拼图的变体。 您可以在 [https://en.wikipedia.org/wiki/15_puzzle 上进行检查。](https://en.wikipedia.org/wiki/15_puzzle.) 您将看到一个随机的网格,目标是将其恢复为原始的有序配置。 您可以在 [http://mypuzzle.org/sliding](http://mypuzzle.org/sliding) 上玩游戏以熟悉。 + +我们将使用和 **A *算法**解决此问题。 它是一种算法,用于在图中找到解决方案的路径。 该算法是 **Dijkstra 算法**和贪婪最佳优先搜索的组合。 A *算法不会盲目猜测下一步该怎么做,而是选择看起来最有前途的算法。 在每个节点上,生成所有可能性的列表,然后选择达到目标所需的最低成本的可能性。 + +让我们看看如何定义成本函数。 在每个节点上,都需要计算成本。 该成本基本上是两个成本的总和-第一个成本是到达当前节点的成本,第二个成本是从当前节点达到目标的成本。 + +我们将此求和用作启发式方法。 如我们所见,第二笔费用基本上是不理想的估计。 如果是完美的,那么 A *算法将很快到达解决方案。 但这不是通常的情况。 找到解决方案的最佳路径需要花费一些时间。 A *有效地找到最佳路径,并且是其中最流行的技术之一。 + +让我们使用 A *算法来构建 8 难题求解器。 这是`simpleai`库中提供的解决方案的变体。 创建一个新的 Python 文件并导入以下软件包: + +```py +from simpleai.search import astar, SearchProblem +``` + +定义一个类,其中包含解决 8 难题的方法: + +```py +# Class containing methods to solve the puzzle +class PuzzleSolver(SearchProblem): +``` + +覆盖`actions`方法以使与当前问题保持一致: + +```py + # Action method to get the list of the possible + # numbers that can be moved into the empty space + def actions(self, cur_state): + rows = string_to_list(cur_state) + row_empty, col_empty = get_location(rows, 'e') +``` + +检查空白区域的位置并创建新动作: + +```py + actions = [] + if row_empty > 0: + actions.append(rows[row_empty - 1][col_empty]) + if row_empty < 2: + actions.append(rows[row_empty + 1][col_empty]) + if col_empty > 0: + actions.append(rows[row_empty][col_empty - 1]) + if col_empty < 2: + actions.append(rows[row_empty][col_empty + 1]) + return actions +``` + +覆盖`result`方法。 将字符串转换为列表,然后提取空白区域的位置。 通过更新位置生成结果: + +```py + # Return the resulting state after moving + # a piece to the empty space + def result(self, state, action): + rows = string_to_list(state) + row_empty, col_empty = get_location(rows, 'e') + row_new, col_new = get_location(rows, action) + rows[row_empty][col_empty], rows[row_new][col_new] = \ + rows[row_new][col_new], rows[row_empty][col_empty] + return list_to_string(rows) +``` + +检查是否达到目标: + +```py + # Returns true if a state is the goal state + def is_goal(self, state): + return state == GOAL +``` + +定义`heuristic`方法。 我们将使用启发式算法,通过曼哈顿距离计算当前状态与目标状态之间的距离: + +```py + # Returns an estimate of the distance from a state to + # the goal using the manhattan distance + def heuristic(self, state): + rows = string_to_list(state) +``` + +```py + distance = 0 +``` + +计算距离: + +```py + for number in '12345678e': + row_new, col_new = get_location(rows, number) + row_new_goal, col_new_goal = goal_positions[number] + distance += abs(row_new - row_new_goal) + abs(col_new - col_new_goal) +``` + +```py + return distance +``` + +定义一个函数以将列表转换为字符串: + +```py +# Convert list to string +def list_to_string(input_list): + return '\n'.join(['-'.join(x) for x in input_list]) +``` + +定义一个将字符串转换为列表的函数: + +```py +# Convert string to list +def string_to_list(input_string): + return [x.split('-') for x in input_string.split('\n')] +``` + +定义一个函数以获取给定元素在网格中的位置: + +```py +# Find the 2D location of the input element +def get_location(rows, input_element): + for i, row in enumerate(rows): + for j, item in enumerate(row): + if item == input_element: + return i, j +``` + +定义我们想要实现的初始状态和最终目标: + +```py +# Final result that we want to achieve +GOAL = '''1-2-3 +4-5-6 +7-8-e''' +``` + +```py +# Starting point +INITIAL = '''1-e-2 +6-3-4 +7-5-8''' +``` + +通过创建变量来跟踪每件作品的目标位置: + +```py +# Create a cache for the goal position of each piece +goal_positions = {} +rows_goal = string_to_list(GOAL) +for number in '12345678e': + goal_positions[number] = get_location(rows_goal, number) +``` + +使用我们先前定义的初始状态创建 A *求解器对象,并提取结果: + +```py +# Create the solver object +result = astar(PuzzleSolver(INITIAL)) +``` + +打印解决方案: + +```py +# Print the results +for i, (action, state) in enumerate(result.path()): + print() + if action == None: + print('Initial configuration') + elif i == len(result.path()) - 1: + print('After moving', action, 'into the empty space. Goal achieved!') + else: + print('After moving', action, 'into the empty space') +``` + +```py + print(state) +``` + +完整代码在文件`puzzle.py`中给出。 如果运行代码,将得到很长的输出。 + +它将按以下方式启动: + +![](img/B15441_10_10.png) + +图 10:PuzzleSolver 输出 + +如果向下滚动,您将看到为解决方案而采取的步骤。 最后,您将看到以下内容: + +![](img/B15441_10_11.png) + +图 11:PuzzleSolver 输出的结尾-已实现目标! + +如您所见,果阿 l 已实现,难题得以解决。 + +# 建立迷宫求解器 + +让我们使用 A *算法来解决迷宫问题。 考虑下图: + +![](img/B15441_10_12.png) + +图 12:迷宫问题的示例 + +**#**符号表示障碍物。 **o** 代表起点, **x** 代表目标。 目的是找到从起点到终点的最短路径。 让我们看看如何在 Python 中做到这一点。 以下解决方案是`simpleai`库中提供的解决方案的变体。 创建一个新的 Python 文件并导入以下软件包: + +```py +import math +from simpleai.search import SearchProblem, astar +``` + +创建一个包含解决问题所需方法的类: + +```py +# Class containing the methods to solve the maze +class MazeSolver(SearchProblem): +``` + +定义初始化方法: + +```py + # Initialize the class + def __init__(self, board): + self.board = board + self.goal = (0, 0) +``` + +提取初始位置和最终位置: + +```py + for y in range(len(self.board)): + for x in range(len(self.board[y])): + if self.board[y][x].lower() == "o": + self.initial = (x, y) + elif self.board[y][x].lower() == "x": + self.goal = (x, y) +``` + +```py + super(MazeSolver, self).__init__(initial_state=self.initial) +``` + +覆盖`actions`方法。 在每个位置,我们需要检查前往相邻单元的成本,然后附加所有可能的操作。 如果相邻小区被阻止,则不考虑该操作: + +```py + # Define the method that takes actions + # to arrive at the solution + def actions(self, state): + actions = [] + for action in COSTS.keys(): + newx, newy = self.result(state, action) + if self.board[newy][newx] != "#": + actions.append(action) +``` + +```py + return actions +``` + +覆盖`result`方法。 根据当前状态和输入操作,更新`x`和`y`坐标: + +```py + # Update the state based on the action + def result(self, state, action): + x, y = state +``` + +```py + if action.count("up"): + y -= 1 + if action.count("down"): + y += 1 + if action.count("left"): + x -= 1 + if action.count("right"): + x += 1 +``` + +```py + new_state = (x, y) +``` + +```py + return new_state +``` + +检查我们是否已经达到目标: + +```py + # Check if we have reached the goal + def is_goal(self, state): + return state == self.goal +``` + +我们需要定义`cost`函数。 这是移动到相邻单元的成本,并且垂直/水平和对角线移动是不同的。 我们将在以后定义这些: + +```py + # Compute the cost of taking an action + def cost(self, state, action, state2): + return COSTS[action] +``` + +定义将使用的试探法。 在这种情况下,我们将使用欧几里得距离: + +```py + # Heuristic that we use to arrive at the solution + def heuristic(self, state): + x, y = state + gx, gy = self.goal +``` + +```py + return math.sqrt((x - gx) ** 2 + (y - gy) ** 2) +``` + +定义`main`函数并定义我们前面讨论的映射: + +```py +if __name__ == "__main__": + # Define the map + MAP = """ + ############################## + # # # # + # #### ######## # # + # o # # # # + # ### ##### ###### # + # # ### # # + # # # # # # ### + # ##### # # # x # + # # # # + ############################## + """ +``` + +将地图信息转换为列表: + +```py + # Convert map to a list + print(MAP) + MAP = [list(x) for x in MAP.split("\n") if x] +``` + +定义在地图上移动的成本。 对角线移动比水平或垂直移动更昂贵: + +```py + # Define cost of moving around the map + cost_regular = 1.0 + cost_diagonal = 1.7 +``` + +将成本分配给相应的移动: + +```py + # Create the cost dictionary + COSTS = { + "up": cost_regular, + "down": cost_regular, + "left": cost_regular, + "right": cost_regular, + "up left": cost_diagonal, + "up right": cost_diagonal, + "down left": cost_diagonal, + "down right": cost_diagonal, + } +``` + +使用先前定义的自定义类创建一个求解器对象: + +```py + # Create maze solver object + problem = MazeSolver(MAP) +``` + +在地图上运行求解器并提取结果: + +```py + # Run the solver + result = astar(problem, graph_search=True) +``` + +从结果中提取路径: + +```py + # Extract the path + path = [x[1] for x in result.path()] +``` + +打印输出: + +```py + # Print the result + print() + for y in range(len(MAP)): + for x in range(len(MAP[y])): + if (x, y) == problem.initial: + print('o', end='') + elif (x, y) == problem.goal: + print('x', end='') + elif (x, y) in path: + print('·', end='') + else: + print(MAP[y][x], end='') +``` + +```py + print() +``` + +完整代码在文件`maze.py`中给出。 如果运行代码,将得到以下输出: + +![](img/B15441_10_13.png) + +图 13:迷宫问题的解决方案 + +如您所见,算法留下了一点点的痕迹,并找到了从起点 **o** 到终点 **x** 的解。 至此,本章最后部分总结了 A *算法 ithm 的恶魔处理。 + +# 摘要 + +在本章中,我们学习了启发式搜索技术的工作原理。 我们讨论了不知情和知情搜索之间的区别。 我们了解了约束满足问题,以及如何使用此范式解决问题。 我们讨论了本地搜索技术如何工作以及为什么在实践中使用了模拟退火。 我们对字符串问题实施了贪婪的搜索。 我们使用 CSP 公式解决了一个问题。 + +我们使用这种方法来解决区域着色问题。 然后,我们讨论了 A *算法及其如何用于找到解决方案的最佳路径。 我们用它来构建 8 难题求解器和迷宫求解器。 在下一章中,我们将讨论遗传算法及其如何用于解决现实世界中的问题。 \ No newline at end of file diff --git a/docs/ai-py/11.md b/docs/ai-py/11.md new file mode 100644 index 0000000000000000000000000000000000000000..170da016849b4c0f6e8e88a325eb27910ebd85d9 --- /dev/null +++ b/docs/ai-py/11.md @@ -0,0 +1,1214 @@ +# 11 + +# 遗传算法和遗传编程 + +在本章中,我们将学习遗传算法。 首先,我们将描述什么是遗传算法,然后将讨论进化算法和遗传编程的概念,并了解它们与遗传算法的关系。 我们将学习遗传算法的基本构建模块,包括交叉,变异和适应度函数。 然后,我们将使用这些概念来构建各种系统。 + +在本章结束时,您将对以下内容有更好的理解: + +* 进化遗传算法 +* 遗传算法的基本概念 +* 使用预定义参数生成位模式 +* 可视化演进过程 +* 解决符号回归问题 +* 构建智能机器人控制器 + +# 进化论者部落 + +正如我们在书的开头提到的那样,研究遗传算法和遗传编程的计算机科学和数据科学研究人员是 Pedro Domingos 定义的进化论部落的一部分。 在某些方面,这个部落不在前面和中间。 联络员在阳光下度过一天,似乎在聚光灯下度过了愉快的时光。 正如 Domingos 博士所强调的那样,随着 CPU 的速度越来越快,并且在这一领域进行了更多的研究,如果在未来几年中出现新的,令人兴奋的前沿研究,不要感到惊讶。 他们已经在该领域做出了许多强大的创新性贡献,并将继续这样做。 + +# 了解进化和遗传算法 + +遗传算法是一种进化算法。 因此,为了理解遗传算法,我们需要讨论进化算法。 进化算法是一种运用启发式原理解决问题的元启发式优化算法。 进化的概念就像我们在自然界中发现的那样。 就像环境通过进化积极驱动“解决方案”一样,我们直接使用问题的函数和变量来得出解决方案。 但是在遗传算法中,任何给定的问题都以该算法操纵的位模式进行编码。 + +自主地解决问题是人工智能和机器学习的中心目标。 **遗传算法**( **GA** )是一种进化计算技术,可自动解决问题,而无需用户事先知道或指定解决方案的形式或结构。 从最抽象的层次上讲,GA 是计算机自动解决问题的一种系统的,与领域无关的方法,它从需要做什么的高级说明开始 e。 + +进化算法的基本步骤如下: + +**步骤 1** + +随机生成数据点或*个人*的初始种群。 由 GA 定义的个体是具有某些*特征*或*特征*的人群的成员。 在算法的后续步骤中,我们将确定这些特征是否使个体能够适应环境并生存足够长的时间以产生后代。 + +**步骤 2** + +循环执行以下步骤,直到终止: + +1. 评估该人群中每个人的健康状况。 +2. 选择最适合繁殖的个体。 +3. 通过交叉和变异操作育出新个体,以产生后代。 +4. 评估新个体的个体适应性。 + +1. 用新的个体代替最不适合的人群。 + +使用预定义的**适应度函数**确定个体的*适应度。 优胜劣汰*生存的短语*发挥作用。* + +然后,我们选择这些选定的个体,并通过重组和突变创建下一代个体。 我们将在下一部分中讨论重组和突变的概念。 现在,让我们将这些技术视为通过将选定的个人视为父母来创造下一代的机制。 + +一旦执行重组和突变,我们将创建一组新的个体,这些个体将与旧个体竞争下一代的位置。 通过抛弃最弱的个体并用后代代替它们,我们正在提高人口的整体适应水平。 我们继续进行迭代,直到达到所需的总体适应性。 + +遗传算法是一种进化算法,在该算法中,我们使用启发式算法来找到解决问题的字符串。 我们不断地对人口进行迭代,以找到解决方案。 + +我们通过产生包含更健康个体的新种群来做到这一点。 我们应用概率运算符,例如**选择**,**交叉**和**突变**,以便生成下一代个体。 个体用字符串表示,其中每个字符串都是潜在解决方案的编码版本。 + +使用适应性函数评估每个字符串的适应性度量,告诉我们解决问题的适用性。 该适应度函数也称为**评估函数**。 GA 会应用受 n 性质启发的运算符,这就是为什么该术语与生物生物学中发现的术语紧密相关的原因。 + +# 遗传算法的基本概念 + +为了建立 GA,我们需要了解几个关键概念和术语。 这些概念在 GA 的整个领域中得到广泛使用,以构建针对各种问题的解决方案。 GA 的最重要方面之一是*随机性*。 为了进行迭代,它依赖于对个体的随机采样。 这意味着该过程是不确定的。 因此,如果您多次运行相同的算法,则可能会得到不同的解决方案。 + +现在让我们定义术语*人口*。 总体是一组可能的候选解决方案。 在 GA 中,单个最佳解决方案不会在任何给定阶段维护,而是会保留一组潜在解决方案,其中一个可能是最佳解决方案。 但是其他解决方案在搜索过程中也起着重要作用。 由于跟踪解决方案的总体,因此不太可能陷入局部最优状态。 陷入局部最优是其他优化技术面临的经典问题。 + +现在,我们了解了 GA 的数量和随机性,下面我们来谈谈运营商。 当创建下一代个体时,算法会尝试确保它们来自当前世代中最适合的个体。 + +*突变*是实现此目的的一种方法。 遗传算法对当前的一个或多个个体进行随机更改,以产生新的候选解。 这种变化称为突变。 现在,这种变化可能会使该个人变得比现有个人更好或更糟。 + +需要定义的下一个概念是*重组*,也称为*交叉*。 这与繁殖在进化过程中的作用直接相关。 GA 试图将当前一代的个人合并起来,以创建新的解决方案。 它结合了每个父母个体的一些特征来创造这个后代。 此过程称为交叉。 目标是用人口中“钳工”个体产生的后代代替当前一代中的“较弱”个体。 + +为了应用交叉和突变,我们需要选择标准。 *选择的概念*受自然选择理论的启发。 在每次迭代期间,GA 都会执行选择过程。 优胜劣汰的个人使用此选择过程进行选择,较弱的个体被终止。 优胜劣汰概念的生存就在这里发挥作用。 使用计算 i 个个人适应性的函数进行选择过程。 + +# 生成具有预定义参数的位模式 + +现在我们知道了 GA 的基本概念,下面让我们看看如何使用它来解决一些问题。 我们将使用一个名为`DEAP`的 Python 包。 您可以在 [http://deap.readthedocs.io/en/master](http://deap.readthedocs.io/en/master) 中找到的所有详细信息。 让我们继续运行以下命令来安装它: + +```py +$ pip3 install deap +``` + +现在已经安装了该软件包,让我们快速对其进行测试。 通过键入以下命令进入 Python shell: + +```py +$ python3 +``` + +进入内部后,键入以下内容: + +```py +>>> import deap +``` + +如果您没有看到错误消息,那就很好了。 + +在本节中,我们将使用, **One Max 算法**的变体。 One Max 算法尝试生成包含最大个数的位字符串。 这是一种简单的算法,但熟悉该库有助于更好地了解如何使用 GA 实现解决方案,这对您有所帮助。 在这种情况下,可能会生成包含预定义数量的 1 的位串。 您将看到底层结构和部分代码类似于`DEAP`库中使用的示例。 + +创建一个新的 Python 文件并导入以下内容: + +```py +import random +``` + +```py +from deap import base, creator, tools +``` + +假设我们要生成长度为 75 的位模式,并且希望它包含`45`个位模式。 我们需要定义一个评估函数,可用于实现此目标: + +```py +# Evaluation function +def eval_func(individual): + target_sum = 45 + return len(individual) - abs(sum(individual) - target_sum), +``` + +当个数等于`45`时,前一个函数中使用的公式将达到最大值。 所有个体的长度为 75。当个数等于`45`时,返回值为`75`。 + +现在让我们定义一个函数来创建`toolbox`。 首先,为健身功能定义`creator`对象并跟踪个人。 这里使用的`Fitness`类是一个抽象类,它需要定义`weights`属性。 我们正在使用正权重建立最大适应度: + +```py +# Create the toolbox with the right parameters def +def create_toolbox(num_bits): + creator.create("FitnessMax", base.Fitness, weights=(1.0,)) + creator.create("Individual", list, fitness=creator.FitnessMax) +``` + +第一行创建一个最大化适应性的单一目标,名为`FitnessMax`。 第二行涉及产生个体。 创建的第一个个人是浮点列表。 为了产生这个个体,我们必须使用`creator`创建一个`Individual` 类。 适应性属性将使用之前定义的`FitnessMax`。 + +`toolbox`是`DEAP`中常用的对象。 它用于存储各种函数及其参数。 让我们创建这个对象: + +```py + # Initialize the toolbox + toolbox = base.Toolbox() +``` + +现在我们将开始向`toolbox`注册各种功能。 让我们从生成`0`和`1`之间的随机整数的随机数生成器开始。 这基本上是生成位字符串: + +```py + # Generate attributes + toolbox.register("attr_bool", random.randint, 0, 1) +``` + +让我们注册`individual`功能。 方法`initRepeat`带有三个参数–个人的容器类,用于填充容器的函数以及我们希望函数重复自身的次数: + +```py + # Initialize structures + toolbox.register("individual", tools.initRepeat, creator.Individual, + toolbox.attr_bool, num_bits) +``` + +我们需要注册`population`功能。 我们希望人口成为个人列表: + +```py + # Define the population to be a list of individuals + toolbox.register("population", tools.initRepeat, list, toolbox.individual) +``` + +现在,我们需要注册遗传算子。 注册我们之前定义的`evaluation`函数,它将用作适应函数。 我们希望个人(有点模式)有`45`个: + +```py + # Register the evaluation operator + toolbox.register("evaluate", eval_func) +``` + +使用`cxTwoPoint`方法注册名为`mate`的交叉运算符: + +```py + # Register the crossover operator + toolbox.register("mate", tools.cxTwoPoint) +``` + +使用`mutFlipBit`注册名为`mutate`的变异算子。 我们需要使用`indpb`指定每个属性发生突变的概率: + +```py + # Register a mutation operator + toolbox.register("mutate", tools.mutFlipBit, indpb=0.05) +``` + +使用`selTournament`注册选择运算符。 它指定将选择哪些个体进行育种: + +```py + # Operator for selecting individuals for breeding + toolbox.register("select", tools.selTournament, tournsize=3) +``` + +```py + return toolbox +``` + +这是上一节中讨论的所有概念的实现。 `DEAP`中常见的是`toolbox`生成器功能,我们将在本章通篇使用它。 因此,花一些时间来了解`toolbox`是如何产生的,这一点很重要。 + +从位模式的长度开始,通过定义`main`功能: + +```py +if __name__ == "__main__": + # Define the number of bits + num_bits = 75 +``` + +使用我们之前定义的函数创建一个`toolbox`: + +```py + # Create a toolbox using the above parameter + toolbox = create_toolbox(num_bits) +``` + +播种随机数生成器以获得可重复的结果: + +```py + # Seed the random number generator + random.seed(7) +``` + +使用`toolbox`对象中可用的方法来创建一个初始种群,例如`500`个人。 随时更改此数字并进行试验: + +```py + # Create an initial population of 500 individuals + population = toolbox.population(n=500) +``` + +定义交叉和变异的概率。 同样,这些是用户定义的参数。 因此,您可以更改这些参数并查看它们如何影响结果: + +```py + # Define probabilities of crossing and mutating + probab_crossing, probab_mutating = 0.5, 0.2 +``` + +定义迭代直到终止过程所需的代数。 如果增加世代数,则会给它更多的周期以提高种群的适应性: + +```py + # Define the number of generations + num_generations = 60 +``` + +使用适应度函数评估人口中的所有个体: + +```py + print('\nStarting the evolution process') + + # Evaluate the entire population + fitnesses = list(map(toolbox.evaluate, population)) + for ind, fit in zip(population, fitnesses): + ind.fitness.values = fit +``` + +开始几代人迭代: + +```py + print('\nEvaluated', len(population), 'individuals') + + # Iterate through generations + for g in range(num_generations): + print("\n===== Generation", g) +``` + +在每一代中,使用我们之前注册到`toolbox`的选择运算符选择下一代个体: + +```py + # Select the next generation individuals + offspring = toolbox.select(population, len(population)) +``` + +克隆选定的个人: + +```py + # Clone the selected individuals + offspring = list(map(toolbox.clone, offspring)) +``` + +使用先前定义的概率值,对下一代个人应用交叉和突变。 完成后,重置适应性值: + +```py + # Apply crossover and mutation on the offspring + for child1, child2 in zip(offspring[::2], offspring[1::2]): + # Cross two individuals + if random.random() < probab_crossing: + toolbox.mate(child1, child2) +``` + +```py + # "Forget" the fitness values of the children + del child1.fitness.values + del child2.fitness.values +``` + +使用先前定义的相应概率值将变异应用于下一代个体。 完成后,重置适应性值: + +```py + # Apply mutation + for mutant in offspring: + # Mutate an individual + if random.random() < probab_mutating: + toolbox.mutate(mutant) + del mutant.fitness.values +``` + +评估具有无效适应度值的个人: + +```py + # Evaluate the individuals with an invalid fitness + invalid_ind = [ind for ind in offspring if not ind.fitness.valid] + fitnesses = map(toolbox.evaluate, invalid_ind) + for ind, fit in zip(invalid_ind, fitnesses): + ind.fitness.values = fit + + print('Evaluated', len(invalid_ind), 'individuals') +``` + +用下一代个人代替人口: + +```py + # The population is entirely replaced by the offspring + population[:] = offspring +``` + +打印当前代的统计信息,以查看其的进度: + +```py + # Gather all the fitnesses in one list and print the stats + fits = [ind.fitness.values[0] for ind in population] + + length = len(population) + mean = sum(fits) / length + sum2 = sum(x*x for x in fits) + std = abs(sum2 / length - mean**2)**0.5 + + print('Min =', min(fits), ', Max =', max(fits)) + print('Average =', round(mean, 2), ', Standard deviation =', + round(std, 2)) + + print("\n==== End of evolution") +``` + +打印最终输出: + +```py + best_ind = tools.selBest(population, 1)[0] + print('\nBest individual:\n', best_ind) + print('\nNumber of ones:', sum(best_ind)) +``` + +完整代码在文件`bit_counter.py`中给出。 如果运行代码,将看到打印出的迭代。 开始时,您将看到类似以下内容: + +![](img/B15441_11_01.png) + +图 1:初始演化输出(第 0 到第 3 代) + +最后,您将看到类似于以下,它指示演进的结束: + +![](img/B15441_11_02.png) + +图 2:Evolution 最终输出 + +如上图所示,进化过程在 60 代(零索引)之后结束。 完成后,将挑选最佳个人,并将其打印在输出中。 最佳个人中有 45 个,这是对的结果的确认,因为目标总和是评估函数中的`45`。 + +# 可视化演变 + +让我们看看如何可视化演变过程。 在`DEAP`中,有一种称为**协方差矩阵适应演化策略**( **CMA-ES** )的方法可以可视化演化。 是一种进化算法,用于解决连续域中的非线性问题。 CMA-ES 技术是可靠的,经过充分研究的,并且在进化算法中被认为是“最新技术”。 通过深入研究源代码来看看它是如何工作的。 以下代码是`DEAP`库中显示的示例的微小变化。 + +创建一个新的 Python 文件并导入以下内容: + +```py +import numpy as np +import matplotlib.pyplot as plt +from deap import algorithms, base, benchmarks, \ + cma, creator, tools +``` + +定义一个函数来创建`toolbox`。 我们将使用负权重定义`FitnessMin`函数: + +```py +# Function to create a toolbox +def create_toolbox(strategy): + creator.create("FitnessMin", base.Fitness, weights=(-1.0,)) + creator.create("Individual", list, fitness=creator.FitnessMin) +``` + +创建`toolbox`并注册评估功能,如下所示: + +```py + toolbox = base.Toolbox() + toolbox.register("evaluate", benchmarks.rastrigin) +``` + +```py + # Seed the random number generator + np.random.seed(7) +``` + +注册`generate`和`update`方法。 这将使用 generate-update 范式并从策略生成总体,然后根据总体更新策略: + +```py + toolbox.register("generate", strategy.generate, creator.Individual) + toolbox.register("update", strategy.update) +``` + +```py + return toolbox +``` + +定义`main`功能。 首先定义个人数量和世代数量: + +```py +if __name__ == "__main__": + # Problem size + num_individuals = 10 + num_generations = 125 +``` + +在开始该过程之前,定义一个`strategy`: + +```py + # Create a strategy using CMA-ES algorithm + strategy = cma.Strategy(centroid=[5.0]*num_individuals, sigma=5.0, + lambda_=20*num_individuals) +``` + +根据策略创建`toolbox`: + +```py + # Create toolbox based on the above strategy + toolbox = create_toolbox(strategy) +``` + +创建一个`HallOfFame`对象。 `HallOfFame`对象包含群体中存在的最佳个体。 该对象始终保持排序格式。 这样,此对象中的第一个元素就是在进化过程中具有最佳适应性值的个人: + +```py + # Create hall of fame object + hall_of_fame = tools.HallOfFame(1) +``` + +使用`Statistics`方法注册统计信息: + +```py + # Register the relevant stats + stats = tools.Statistics(lambda x: x.fitness.values) + stats.register("avg", np.mean) + stats.register("std", np.std) + stats.register("min", np.min) + stats.register("max", np.max) +``` + +定义`logbook`以跟踪演变记录。 它基本上是按时间顺序排列的词典列表: + +```py + logbook = tools.Logbook() + logbook.header = "gen", "evals", "std", "min", "avg", "max" +``` + +定义对象以编译所有数据: + +```py + # Objects that will compile the data + sigma = np.ndarray((num_generations, 1)) + axis_ratio = np.ndarray((num_generations, 1)) + diagD = np.ndarray((num_generations, num_individuals)) + fbest = np.ndarray((num_generations,1)) + best = np.ndarray((num_generations, num_individuals)) + std = np.ndarray((num_generations, num_individuals)) +``` + +遍历几代人: + +```py + for gen in range(num_generations): + # Generate a new population + population = toolbox.generate() +``` + +使用适应度函数评估个人: + +```py + # Evaluate the individuals + fitnesses = toolbox.map(toolbox.evaluate, population) + for ind, fit in zip(population, fitnesses): + ind.fitness.values = fit +``` + +根据总体更新策略: + +```py + # Update the strategy with the evaluated individuals + toolbox.update(population) +``` + +与当前这一代人一起更新名人堂和统计数据: + +```py + # Update the hall of fame and the statistics with the + # currently evaluated population + hall_of_fame.update(population) + record = stats.compile(population) + logbook.record(evals=len(population), gen=gen, **record) + + print(logbook.stream) +``` + +保存绘图的数据: + +```py + # Save more data along the evolution for plotting + sigma[gen] = strategy.sigma + axis_ratio[gen] = max(strategy.diagD)**2/min(strategy.diagD)**2 + diagD[gen, :num_individuals] = strategy.diagD**2 + fbest[gen] = hall_of_fame[0].fitness.values + best[gen, :num_individuals] = hall_of_fame[0] + std[gen, :num_individuals] = np.std(population, axis=0) +``` + +定义`x`轴并绘制统计数据: + +```py + # The x-axis will be the number of evaluations + x = list(range(0, strategy.lambda_ * num_generations, strategy.lambda_)) + avg, max_, min_ = logbook.select("avg", "max", "min") + plt.figure() + plt.semilogy(x, avg, "--b") + plt.semilogy(x, max_, "--b") + plt.semilogy(x, min_, "-b") + plt.semilogy(x, fbest, "-c") + plt.semilogy(x, sigma, "-g") + plt.semilogy(x, axis_ratio, "-r") + plt.grid(True) + plt.title("blue: f-values, green: sigma, red: axis ratio") +``` + +绘制进度: + +```py + plt.figure() + plt.plot(x, best) + plt.grid(True) + plt.title("Object Variables") +``` + +```py + plt.figure() + plt.semilogy(x, diagD) + plt.grid(True) + plt.title("Scaling (All Main Axes)") +``` + +```py + plt.figure() + plt.semilogy(x, std) + plt.grid(True) + plt.title("Standard Deviations in All Coordinates") + + plt.show() +``` + +完整代码在文件`visualization.py`中给出。 如果运行代码,您将看到四个屏幕截图。 第一个屏幕截图显示了各种参数: + +![](img/B15441_11_03.png) + +图 3:演变过程中绘制的参数 + +第二张屏幕截图显示了对象变量: + +![](img/B15441_11_04.png) + +图 4:绘制演化过程中的对象变量 + +第三张屏幕快照显示缩放: + +![](img/B15441_11_05.png) + +图 5:演变过程中的标绘比例 + +第四个屏幕截图显示了标准偏差: + +![](img/B15441_11_06.png) + +图 6:从进化过程中绘制的标准偏差 + +您将看到打印出来的进度。 首先,您会看到类似以下的内容: + +![](img/B15441_11_07.png) + +图 7:演化过程的初始输出 + +最后,您将看到以下内容: + +![](img/B15441_11_08.png) + +图 8:演进进度最终输出 + +从上图可以看出,随着我们的前进,所有值都使 d 不断增加。 中的内容表明它正在收敛。 + +# 解决符号回归问题 + +在本章的最后,我们将看到 GA 在众多行业和领域中的许多应用。 从财务到流量优化,GA 的应用几乎是无止境的。 不过,到目前为止,我们继续另一个简单的示例。 让我们看看如何使用遗传编程来解决符号回归问题。 重要的是要了解遗传程序设计与 GA 并不相同。 遗传编程是一种进化算法,其中解决方案以计算机程序的形式出现。 每代人都是计算机程序,他们的适应水平与其解决问题的能力相对应。 每次迭代时,都使用 GA 修改这些程序。 遗传程序设计是 GA 的应用。 + +关于符号回归问题,我们有一个多项式表达式,在这里需要近似。 这是一个经典的回归问题,我们尝试估算基本功能。 在此示例中,我们将使用表达式: *f(x)= 2x ^ 3 – 3x ^ 2 + 4x – 1* + +此处讨论的代码是`DEAP`库中给出的符号回归问题的变体。 创建一个新的 Python 文件并导入以下内容: + +```py +import operator +import math +import random +``` + +```py +import numpy as np +from deap import algorithms, base, creator, tools, gp +``` + +创建一个除法运算符,可以优雅地处理被零除的错误: + +```py +# Define new functions +def division_operator(numerator, denominator): + if denominator == 0: + return 1 +``` + +```py + return numerator / denominator +``` + +定义将用于适应度计算的评估函数。 我们需要定义一个可调用函数以对输入个体进行计算: + +```py +# Define the evaluation function +def eval_func(individual, points): + # Transform the tree expression in a callable function + func = toolbox.compile(expr=individual) +``` + +计算先前定义的函数和原始表达式之间的**均方误差**( **MSE** ): + +```py + # Evaluate the mean squared error + mse = ((func(x) - (2 * x**3 - 3 * x**2 + 4 * x - 1))**2 for x in points) +``` + +```py + return math.fsum(mse) / len(points), +``` + +定义一个函数来创建 `toolbox`。 为了在此处创建`toolbox`,需要创建一组原语。 这些原语是将在演化过程中使用的运算符。 它们是个人的基石。 这些原语将是基本的算术函数: + +```py +# Function to create the toolbox +def create_toolbox(): + pset = gp.PrimitiveSet("MAIN", 1) + pset.addPrimitive(operator.add, 2) + pset.addPrimitive(operator.sub, 2) + pset.addPrimitive(operator.mul, 2) + pset.addPrimitive(division_operator, 2) + pset.addPrimitive(operator.neg, 1) + pset.addPrimitive(math.cos, 1) + pset.addPrimitive(math.sin, 1) +``` + +接下来,声明一个临时常量。 临时常数是一种特殊的终端类型,没有固定值。 当给定程序将此类临时常量附加到树上时,该函数将被执行。 然后将结果作为常数终端插入树中。 + +这些常量端子可以采用`-1`,`0`或`1`值: + +```py + pset.addEphemeralConstant("rand101", lambda: random.randint(-1,1)) +``` + +参数的默认名称为`ARGx`。 让我们将其重命名为`x`: + +```py + pset.renameArguments(ARG0='x') +``` + +我们需要定义两种对象类型–适应度和个人。 让我们用`creator`来做: + +```py + creator.create("FitnessMin", base.Fitness, weights=(-1.0,)) + creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMin) +``` + +创建`toolbox`和`register`功能。 注册过程的完成类似于前面的部分: + +```py + toolbox = base.Toolbox() +``` + +```py + toolbox.register("expr", gp.genHalfAndHalf, pset=pset, min_=1, max_=2) + toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.expr) + toolbox.register("population", tools.initRepeat, list, toolbox.individual) + toolbox.register("compile", gp.compile, pset=pset) + toolbox.register("evaluate", eval_func, points=[x/10\. for x in range(-10,10)]) + toolbox.register("select", tools.selTournament, tournsize=3) + toolbox.register("mate", gp.cxOnePoint) + toolbox.register("expr_mut", gp.genFull, min_=0, max_=2) + toolbox.register("mutate", gp.mutUniform, expr=toolbox.expr_mut, pset=pset) +``` + +```py + toolbox.decorate("mate", gp.staticLimit(key=operator.attrgetter("height"), max_value=17)) + toolbox.decorate("mutate", gp.staticLimit(key=operator.attrgetter("height"), max_value=17)) +``` + +```py + return toolbox +``` + +定义`main`函数并从播种随机数生成器开始: + +```py +if __name__ == "__main__": + random.seed(7) +``` + +创建`toolbox`对象: + +```py + toolbox = create_toolbox() +``` + +使用`450`个人使用`toolbox`对象中可用的方法定义初始种群。 人数可以更改。 随时尝试。 还定义`hall_of_fame`对象: + +```py + population = toolbox.population(n=450) + hall_of_fame = tools.HallOfFame(1) +``` + +建立 GA 时,统计资料非常有用。 定义统计对象: + +```py + stats_fit = tools.Statistics(lambda x: x.fitness.values) + stats_size = tools.Statistics(len) +``` + +使用先前定义的对象注册统计信息: + +```py + mstats = tools.MultiStatistics(fitness=stats_fit, size=stats_size) + mstats.register("avg", np.mean) + mstats.register("std", np.std) + mstats.register("min", np.min) + mstats.register("max", np.max) +``` + +定义交叉概率,突变概率和世代数: + +```py + probab_crossover = 0.4 + probab_mutate = 0.2 + num_generations = 60 +``` + +使用以上参数运行进化算法: + +```py + population, log = algorithms.eaSimple(population, toolbox, + probab_crossover, probab_mutate, num_generations, + stats=mstats, halloffame=hall_of_fame, verbose=True) +``` + +完整代码在文件`symbol_regression.py`中给出。 如果运行代码,则在演化开始时将看到以下内容: + +![](img/B15441_11_09.png) + +图 9:演化过程的初始输出 + +最后,您将看到以下内容: + +![](img/B15441_11_10.png) + +图 10:演进进度最终输出 + +我们可以看到 min 列中的值越来越小,这表明方程式 的近似解的误差越来越小。 + +# 构建智能机器人控制器 + +让我们看看如何使用 GA 构建机器人控制器。 我们得到了一张地图,上面洒满了目标。 + +地图看起来像这样。 哈希表示机器人要击中所需的目标: + +![](img/B15441_11_11.png) + +图 11:带有 AI 机器人需要击中的目标的地图,目标以哈希表示 + +前面的地图中有 124 个目标。 机器人控制器的目标是自动遍历地图并消耗所有这些目标。 该程序是`deap`库中提供的人工蚂蚁程序的变体。 + +创建一个新的 Python 文件并导入以下内容: + +```py +import copy +import random +from functools import partial +import numpy as np +from deap import algorithms, base, creator, tools, gp +``` + +创建用于控制机器人的类: + +```py +class RobotController(object): + def __init__(self, max_moves): + self.max_moves = max_moves + self.moves = 0 + self.consumed = 0 + self.routine = None +``` + +定义方向和运动: + +```py + self.direction = ["north", "east", "south", "west"] + self.direction_row = [1, 0, -1, 0] + self.direction_col = [0, 1, 0, -1] +``` + +定义重置功能: + +```py + def _reset(self): + self.row = self.row_start + self.col = self.col_start + self.direction = 1 + self.moves = 0 + self.consumed = 0 + self.matrix_exc = copy.deepcopy(self.matrix) +``` + +定义条件运算符: + +```py + def _conditional(self, condition, out1, out2): + out1() if condition() else out2() +``` + +定义左转弯算子: + +```py + def turn_left(self): + if self.moves < self.max_moves: + self.moves += 1 + self.direction = (self.direction - 1) % 4 +``` + +定义正确的车削算子: + +```py + def turn_right(self): + if self.moves < self.max_moves: + self.moves += 1 + self.direction = (self.direction + 1) % 4 +``` + +定义控制机器人前进方式的方法: + +```py + def move_forward(self): + if self.moves < self.max_moves: + self.moves += 1 + self.row = (self.row + self.direction_row[self.direction]) % self.matrix_row + self.col = (self.col + self.direction_col[self.direction]) % self.matrix_col + if self.matrix_exc[self.row][self.col] == "target": + self.consumed += 1 +``` + +```py + self.matrix_exc[self.row][self.col] = "passed" +``` + +定义一种检测目标的方法。 如果您看到前方的目标,请相应地更新矩阵: + +```py + def sense_target(self): + ahead_row = (self.row + self.direction_row[self.direction]) % self.matrix_row + ahead_col = (self.col + self.direction_col[self.direction]) % self.matrix_col + return self.matrix_exc[ahead_row][ahead_col] == "target" +``` + +如果看到前面的目标,则创建相关函数并返回它: + +```py + def if_target_ahead(self, out1, out2): + return partial(self._conditional, self.sense_target, out1, out2) +``` + +定义运行它的方法: + +```py + def run(self,routine): + self._reset() + while self.moves < self.max_moves: + routine() +``` + +定义一个函数以遍历输入图。 符号`#`指示地图上的所有目标,符号`S`指示起点。 符号`.`表示空白单元格: + +```py + def traverse_map(self, matrix): + self.matrix = list() + for i, line in enumerate(matrix): + self.matrix.append(list()) +``` + +```py + for j, col in enumerate(line): + if col == "#": + self.matrix[-1].append("target") +``` + +```py + elif col == ".": + self.matrix[-1].append("empty") +``` + +```py + elif col == "S": + self.matrix[-1].append("empty") + self.row_start = self.row = i + self.col_start = self.col = j + self.direction = 1 +``` + +```py + self.matrix_row = len(self.matrix) + self.matrix_col = len(self.matrix[0]) + self.matrix_exc = copy.deepcopy(self.matrix) +``` + +定义一个类以根据输入参数的数量生成函数: + +```py +class Prog(object): + def _progn(self, *args): + for arg in args: + arg() +``` + +```py + def prog2(self, out1, out2): + return partial(self._progn, out1, out2) +``` + +```py + def prog3(self, out1, out2, out3): + return partial(self._progn, out1, out2, out3) +``` + +为个人定义评估功能: + +```py +def eval_func(individual): + global robot, pset +``` + +```py + # Transform the tree expression to functional Python code + routine = gp.compile(individual, pset) +``` + +运行程序: + +```py + # Run the generated routine + robot.run(routine) + return robot.consumed, +``` + +定义一个函数来创建`toolbox`并添加原语: + +```py +def create_toolbox(): + global robot, pset + pset = gp.PrimitiveSet("MAIN", 0) + pset.addPrimitive(robot.if_target_ahead, 2) + pset.addPrimitive(Prog().prog2, 2) + pset.addPrimitive(Prog().prog3, 3) + pset.addTerminal(robot.move_forward) + pset.addTerminal(robot.turn_left) + pset.addTerminal(robot.turn_right) +``` + +使用适应度函数创建对象类型: + +```py + creator.create("FitnessMax", base.Fitness, weights=(1.0,)) + creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMax) +``` + +创建`toolbox`并注册所有运算符: + +```py + toolbox = base.Toolbox() +``` + +```py + # Attribute generator + toolbox.register("expr_init", gp.genFull, pset=pset, min_=1, max_=2) +``` + +```py + # Structure initializers + toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.expr_init) + toolbox.register("population", tools.initRepeat, list, toolbox.individual) +``` + +```py + toolbox.register("evaluate", eval_func) + toolbox.register("select", tools.selTournament, tournsize=7) + toolbox.register("mate", gp.cxOnePoint) +``` + +```py + toolbox.register("expr_mut", gp.genFull, min_=0, max_=2) + toolbox.register("mutate", gp.mutUniform, expr=toolbox.expr_mut, pset=pset) +``` + +```py + return toolbox +``` + +定义`main`函数,并从播种随机数生成器开始: + +```py +if __name__ == "__main__": + global robot +``` + +```py + # Seed the random number generator + random.seed(7) +``` + +使用初始化参数创建机械手控制器对象: + +```py + # Define the maximum number of moves + max_moves = 750 +``` + +```py + # Create the robot object + robot = RobotController(max_moves) +``` + +使用我们之前定义的函数创建`toolbox`: + +```py + # Create the toolbox + toolbox = create_toolbox() +``` + +从输入文件中读取地图数据: + +```py + # Read the map data + with open('target_map.txt', 'r') as f: + robot.traverse_map(f) +``` + +用`400`个人定义总体并定义`hall_of_fame`对象: + +```py + # Define population and hall of fame variables + population = toolbox.population(n=400) + hall_of_fame = tools.HallOfFame(1) +``` + +注册`stats`: + +```py + # Register the stats + stats = tools.Statistics(lambda x: x.fitness.values) + stats.register("avg", np.mean) + stats.register("std", np.std) + stats.register("min", np.min) + stats.register("max", np.max) +``` + +定义交叉概率,突变概率和世代数: + +```py + # Define parameters + probab_crossover = 0.4 + probab_mutate = 0.3 + num_generations = 50 +``` + +使用前面定义的参数运行进化算法: + +```py + # Run the algorithm to solve the problem + algorithms.eaSimple(population, toolbox, probab_crossover, + probab_mutate, num_generations, stats, + halloffame=hall_of_fame) +``` + +完整代码在文件`robot.py`中给出。 如果运行代码,您将获得以下: + +![](img/B15441_11_12.png) + +图 12:演化过程的初始输出 + +在结束时,您将看到以下内容: + +![](img/B15441_11_13.png) + +图 13:演进过程的最终输出 + +从上图中可以看出,随着我们的发展,标准偏差不断减小。 这表明它正在收敛。 在此输出中,我们仅显示 50 代。 如果运行更多的代,您可以期望这些值甚至会进一步收敛。 + +# 遗传编程用例 + +正如早期章节之一所讨论的那样,遗传算法(GA)和遗传编程(GP)是机器学习的“五个部落”之一。 + +![](img/B15441_11_14.png) + +图 14:五个部落(佩德罗·多明戈斯) + +从一开始,GP 就取得了各种各样的进步。 涵盖 GP 数千种应用程序的文献包含许多成功应用 GP 的用例。 详尽地涵盖该列表将超出本书的范围,但是我们在此处列出了一些更重要的列表。 + +在这里,我们开始讨论 GP 已成功应用的一般问题的讨论,然后回顾 GP 每个主要应用领域的代​​表性子集。 根据多年来不同研究人员的经验,GP 做得好的领域包括: + +**域名理解不佳** + +这是相关变量之间的相互关系未知或了解不充分的地方(或者怀疑当前的理解可能是错误的)。 GP(和其他进化算法)的好处之一是探索了人们不了解的领域。 如果对问题领域有充分的​​了解,则可以使用其他分析工具和方法来提供高质量的解决方案,而不会在 GP 的随机搜索过程中固有的不确定性。 + +另一方面,当对该域的了解不充分时,GP 会产生结果。 GP 可以帮助确定哪些属性和维度是相关的,提供新颖和创新的解决方案,揭示属性之间的意外关系,并发现可以应用于其他领域的新概念。 + +找到最终解决方案的大小和形状是问题的主要部分。 如果知道解决方案的形式,那么用于固定大小表示形式的替代搜索机制(例如 GA)可能会更有效,因为它们不必发现解决方案的大小和形状。 + +**数据可用并且很多** + +特别是 GP,通常是机器学习和搜索技术,通常需要执行大量测试数据。 寻找问题的相关数据集可能是一大障碍。 但是,在大数据集随时可用的情况下,这可能是数据科学家的梦想,提出仅仅因为数据可用而提出的问题可能是一个好主意。 + +如果测试数据尽可能干净和准确,这也很有帮助。 但是,GP 算法可以很好地处理数据中的一定数量的噪声,尤其是在采取措施将过拟合最小化的情况下。 + +**可接受近似解决方案** + +GP 在近似解决方案可以接受或最佳解决方案的情况下效果很好。 总体而言,尤其是 GP 的演变通常意味着“足够好”而不是“最好”。 如果一只熊在树林中追赶您,您不必成为世界上最快的人。 您只需要比熊或奔跑的人快。 结果,进化算法往往在可能且可接受的近似值接近最佳的领域中工作最佳。 + +**小但有价值的改进** + +已发现 GP 在技术努力往往集中在经济重要性较高的领域中效果很好。 在这些领域中,以前的研究人员可能花费了大量时间和精力,并且“最新技术”趋于先进。 在这种情况下,很难改善当前的解决方案。 但是,在这些相同的领域中,小的改进可能非常有价值。 在这种情况下,GP 有时会做出很小但有价值的贡献。 例如石油勘探,材料管理和财务应用。 + +现在,让我们看一下 GA 和 GP 的一些特定于行业的应用程序: + +**电影** + +和其他职业一样,电影特技演员的日子也数不清了。 一家名为 *NaturalMotion* 的初创公司使用 GP 产生了令人难以置信的逼真的效果,从而使人动起来。 这些虚拟演员以真实的精度跌倒,跳跃并表演其他特技。 这些虚拟角色可以像真实的人类一样对施加到其上的力做出反应,并表现出各种各样逼真的动作。 所有这些,仅需要台式机的功能。 电影只是开始。 在接下来的几年中,NaturalMotion 计划在下一代视频游戏中释放这些逼真的人物。 + +NaturalMotion 是由前牛津研究人员 Torsten Reil 和 Colm Massey 创立的一家新公司。 目前,该公司只有一种产品,称为 Endorphin,它利用神经网络和人工进化技术来产生可以像人一样精确地行走,奔跑,跌倒和飞行的软件自动机。 + +Endorphin 在电影*《王者归来》* 中首次亮相,这是用来使特别棘手的特技栩栩如生的电影。 但这仅仅是开始。 几个月后,该公司的机器人在 Ilium 平原上战斗致死,摔倒了沃尔夫冈·彼得森(Wolfgang Petersen)的电影 *Troy* 并摔倒了匕首。 + +来源: [https://www.naturalmotion.com/](https://www.naturalmotion.com/ ) + +**电脑游戏** + +今天,每个人都对深度学习算法着迷。 他们肯定在许多领域和许多基准下都取得了令人印象深刻的结果。 但是 GP 并不懈怠。 由于丹尼斯·威尔逊(Dennis Wilson)和法国图卢兹大学的一些同事的努力,已经观察到了令人印象深刻的结果。 他们在 GP 上所做的工作在许多经典游戏中都能够胜过人类。 威尔逊和他的研究人员团队展示了 GP 如何在具有象征意义的任务上与深度学习算法的性能相媲美,这项任务使深度学习在 2013 年成名-在 Pong,Breakout 和 Space Invaders 等街机视频游戏中,人类的表现优于人类。 + +威尔逊令人信服地证明,GP 可以产生可比的令人印象深刻的结果,甚至可能比深度学习更好。 + +来源: [https://github.com/d9w](https://github.com/d9w ) + +**文件压缩** + +最早的无损压缩技术之一是使用 GP 演变非线性图像预测器。 该算法根据相邻像素子集的灰度值预测像素可以采用的灰度级。 结合模型描述的预测误差可以表示图像的压缩版本。 使用霍夫曼编码对图像进行压缩。 使用 GP 压缩,各种图像上的结果都显示出令人鼓舞的结果。 在某些情况下,GP 算法的性能优于某些人为设计的最佳无损压缩算法。 + +资料来源:福永和 Stechert,1998 [1] + +**金融交易** + +有效市场的假设是经济学的基本原理。 它基于这样的思想:每个市场参与者都具有*完美的信息*,并且他们理性地发挥*的作用*。 如果有效市场假说是正确的,那么每个人都应该为市场中的所有资产分配相同的价格,并就价格达成一致。 如果不存在价格差异,就没有办法击败市场。 无论是商品市场,货币市场还是股票市场,没有任何一个市场参与者是平等的,并且存在很大的疑问,即*有效市场*是否确实存在。 市场流动性越弱,市场效率就越低。 因此,人们继续研究股票市场并试图找到击败股票市场的方法。 有一些人和公司基于他们的往绩证明了市场是可战胜的。 一些示例包括: + +* 沃伦·巴菲特(Warren Buffet)和伯克希尔·哈撒韦(Berkshire Hathaway) +* 彼得·林奇和富达麦哲伦基金 +* 雷·达里奥(Ray Dalio)和 Bridgewater Associates +* 吉姆·西蒙斯(Jim Simons)和 Renaissance Technologies + +后两个示例严重依赖计算机算法来获得市场领先的结果。 + +博弈论一直是经济学家试图了解市场的一种标准工​​具,但是越来越多的人为和计算机化的代理人进行了模拟。 GP 被越来越多地用作这些社会系统模拟的一部分。 + +GP 算法广泛用于金融交易,时间序列预测和经济建模领域; 一整本书要列出它的所有应用。 + +在本节中,我们将从头开始,并访问一些示例。 在这一领域特别杰出的研究人员是陈胜雄。 Chen 写了 60 多篇关于在金融和经济学中使用 GP 的论文。 他最近的一些论文研究了股票市场中的代理人建模(Chen 和 Liao,2005),博弈论(Chen,Duffy 和 Yeh,2002),标准普尔 500 指数交易规则的演变(Yu 和 Chen,2004)。 并预测了恒生指数(Chen,Wang 和 Zhang,1999 年)。 + +**其他应用程序** + +**优化**-GA 和 GP 通常用于优化问题,在给定目标函数的情况下,必须在一组约束条件下将值最大化或最小化。 + +**并行化**-GA 也具有并行处理功能,并且被证明是解决需要并行处理的问题的有效方法。 并行化是 GA 和 GP 研究的活跃领域。 + +**神经网络**-GA 用于训练神经网络,尤其是递归神经网络(RNN)。 + +**经济学**-GA 通常用于对经济系统进行建模,例如: + +* 蜘蛛网模型 +* 博弈论均衡解 +* 资产定价 + +**图像处理**-GA 也用于各种数字图像处理(DIP)任务,例如密集像素匹配。 + +**调度应用程序**-GA 可用于解决许多调度问题,尤其是时间表问题。 简而言之,当我们拥有一组资源,一组活动以及活动与资源之间的依赖关系时,就会发生时间表问题。 一个例子是在我们有教室,教授和学生的大学中的课程表,并且在练习结束时,希望很大比例的学生能够参加他们想参加的所有课程。 + +**参数化设计**-GA 已用于通过更改参数和发展更好的解决方案来设计车辆,机械和飞机。 + +**DNA 分析**-GA 可以并且已经用于使用样品光谱数据确定 DNA 结构。 + +**多峰优化**-GA 是解决寻求多个最优解的多峰优化问题的好方法。 + +**旅行商问题(TSP)**-遗传算法已用于解决 TSP 及其所有相关应用,例如车辆路线和机器人轨迹问题,这是一种使用新颖交叉法和 包装策略。 + +希望 GP 和 GA 的广泛而多样的应用为您所熟悉。 也许您将能够提出自己独特的应用程序,并利用获得的知识来推动该领域的发展。 + +# 摘要 + +在本章中,我们了解了 GA 及其基本概念。 我们讨论了进化算法和遗传规划。 我们了解了它们与 GA 之间的关系。 我们讨论了 GA 的基本构建模块,包括种群,交叉,突变,选择和适应度功能的概念。 我们学习了如何使用预定义的参数生成位模式。 我们讨论了如何使用 CMA-ES 可视化演变过程。 我们学习了如何在此范例中解决符号回归问题。 然后,我们使用这些概念来构建机器人控制器,以遍历地图并消耗所有目标。 在下一章中,我们将学习强化学习,并了解如何构建智能代理。 + +# 参考 + +1. A.福永和 A. Stechert。 进化的非线性预测模型,可通过遗传编程进行无损图像压缩。 在 1998 年 7 月 22 日至 25 日于美国威斯康星州麦迪逊市的威斯康星大学,于 1998 年 7 月 22 日至 25 日在 JR Koza 等人的著作中, *Genetic Programming 1998:第三届年会的论文集*,第 95-102 页。 。 ISBN 1-55860-548-7。 \ No newline at end of file diff --git a/docs/ai-py/12.md b/docs/ai-py/12.md new file mode 100644 index 0000000000000000000000000000000000000000..ca6ae0c77aad63c447156ce0acd798be5417a085 --- /dev/null +++ b/docs/ai-py/12.md @@ -0,0 +1,602 @@ +# 12 + +# 云上的人工智能 + +在本章中,我们将学习有关云和云上的人工智能工作负载的信息。 我们将讨论将 AI 项目迁移到云的好处和风险。 我们还将了解主要云提供商所提供的产品。 我们将了解他们提供的服务和功能,并希望了解为什么这些提供商是市场领导者。 + +在本章结束时,您将对以下内容有更好的理解: + +* 迁移到云的好处,风险和成本 +* 基本云概念(例如弹性) +* 顶级云提供商 +* 亚马逊网络服务: + * 亚马逊 SageMaker + * Alexa,Lex 和 Polly –对话代理 + * Amazon Comprehend –自然语言处理 + * Amazon Rekognition –图片和视频 + * 亚马逊翻译 + * 亚马逊机器学习 + * 亚马逊转录–转录 + * Amazon Textract –文档分析 +* Microsoft Azure: + * 机器学习工作室 + * Azure 机器学习交互式工作区 + * Azure 认知服务 +* Google AI 及其机器学习产品: + * AI 中心 + * 人工智能构建块 + +# 为什么公司要迁移到云? + +如今,很难在不被“云”一词影响的情况下转向任何地方。 我们当今的社会已经达到了一个临界点,无论大小企业都看到将工作负载转移到云中所带来的好处超过了成本和风险。 例如,截至 2019 年,美国国防部正在选择一家云提供商并授予 10 年 100 亿美元的合同。 将您的系统迁移到云具有许多优势,但是公司迁移到云的主要原因之一是其弹性功能。 + +在本地环境中部署新项目时,我们总是从容量规划开始。 容量规划是企业进行的一项练习,以确定为使新系统有效运行所需的硬件数量。 根据项目的规模,这种硬件的成本可能高达数百万美元。 因此,可能需要几个月的时间才能完成该过程。 可能需要很长时间的原因之一是,可能需要许多批准才能完成购买。 我们不能责怪企业对此类决策如此迟钝和明智。 + +尽管可能需要进行周密的计划和考虑,但购买少于所需数量的设备或购买动力不足的设备并不少见。 也许就像经常那样,购买了过多的设备或对眼前的项目而言过高的设备。 发生这种情况的原因是,在许多情况下,很难先验确定需求。 + +此外,即使我们在一开始就获得了适当的容量要求,需求可能仍会继续增长,并迫使我们重新进行供应流程。 否则需求可能会变化。 例如,我们的网站白天可能会吸引大量流量,但晚上的需求却下降了。 在这种情况下,当使用本地环境时,我们别无选择,只能考虑最坏的情况并购买足够的资源,以便我们可以处理需求高峰期,但是当需求在缓慢时期减少时,资源将被浪费。 + +所有这些问题在云环境中都不存在。 所有主要的云提供商都以不同的方式提供弹性的环境。 我们不仅可以轻松扩展,而且可以轻松扩展。 + +如果我们有一个流量可变的网站,则可以将处理流量的服务器放在负载均衡器后面,并设置警报,以自动添加更多服务器以处理流量高峰,并在风暴过后自动添加其他警报以终止服务器。 + +# 顶级云提供商 + +鉴于云是海啸,许多供应商都在争相降低对云服务的需求。 但是,就像在技术市场中经常发生的那样,只有极少数公司冒顶并占据主导地位。 在本节中,我们将分析排名靠前的参与者。 + +**亚马逊网络服务(AWS)** + +Amazon Web Services 是云计算的先驱之一。 自 2006 年推出以来,AWS 在愿景和执行力方面一直在备受推崇的 Gartner 的 Magic 象限中排名很高。 自成立以来,AWS 占据了很大的云市场份额。 对于传统参与者和初创企业而言,AWS 都是一个有吸引力的选择。 根据 Gartner: + +> “ AWS 是战略性,全组织范围采用的最常用的提供商” + +AWS 还拥有一支由顾问组成的大军,致力于帮助其客户部署 AWS 服务,并教他们如何最佳利用可用服务。 综上所述,可以肯定地说,AWS 是最成熟,最先进的云提供商,在客户成功方面拥有良好的往绩,并且在 AWS Marketplace 中拥有强大的合作伙伴。 + +另一方面,由于 AWS 是领导者并且他们知道,因此它们并非总是最便宜的选择。 AWS 的另一个问题是,由于他们非常珍视将新服务和功能首先推向市场,因此他们似乎愿意迅速推出可能尚未完全成熟和功能完善的服务,并在发布后立即解决问题 。 公平地说,这不是 AWS 独有的策略,其他云提供商也发布了其服务的 Beta 版本。 此外,由于亚马逊在云计算以外的市场竞争,因此一些潜在客户与其他提供商合作以免“喂食野兽”并不少见。 例如,沃尔玛以避免不惜一切代价使用 AWS 而闻名,因为它们在电子商务领域竞争激烈。 + +**Microsoft Azure** + +在过去的几年中,Microsoft Azure 在 Gartner 魔力象限中排名第二,仅次于 AWS,其执行能力要远远优于 AWS。 但是好消息是,它们仅落后于 AWS,而且排名第二。 + +微软的解决方案吸引托管旧式工作负载以及全新的云部署的客户,但出于不同的原因。 + +传统的工作负载通常由传统上是 Microsoft 客户的客户在 Azure 上运行,并试图利用他们以前在该技术堆栈中的投资。 + +对于新的云部署,由于 Microsoft 为应用程序开发提供了强大的产品,专业的**平台即服务**( **PaaS** )功能,数据存储,机器,Azure 云服务之所以吸引人 ]学习和**物联网**( **IoT** )服务。 + +在战略上致力于 Microsoft 技术堆栈的企业已经能够在生产中部署许多大型应用程序。 当开发人员完全致力于 Microsoft 产品套件(例如.NET 应用程序),然后将其部署在 Azure 上时,Azure 尤其有用。 微软之所以能够深入市场,是因为其经验丰富的销售人员和广泛的合作伙伴网络。 + +此外,微软意识到,下一轮技术战将不会围绕操作系统展开,而是会围绕云进行,因此它们已越来越多地接受采用非微软操作系统。 为了证明这一点,到目前为止,大约一半的 Azure 工作负载运行在 Linux 或其他开源操作系统和技术堆栈上。 + +Gartner 的一份报告指出:“ *微软对未来具有独特的愿景,涉及通过本机的第一方产品(例如来自 VMware,NetApp,Red Hat,Cray 和 Databricks 的产品)引入技术合作伙伴。*” + +不利的一面是,有一些关于可靠性,停机时间和服务中断的报告,还有一些客户对 Microsoft 技术支持的质量表示怀疑。 + +**Google 云平台(GCP)** + +在 2018 年,Google 通过其 GCP 产品将打破了享誉全球的 Gartner 领导者象限,仅加入了 AWS 和 Azure 的独家俱乐部。 在 2019 年,GCP 与两个激烈的竞争对手保持在同一象限。 但是,就市场份额而言,GCP 仅排在第三位。 + +他们最近加强了销售人员,他们有足够的财力,并且有强烈的动机要不落伍,所以不要打折扣。 + +Google 作为机器学习领导者的声誉无可争议,因此 GCP 拥有强大的大数据和机器学习产品也就不足为奇了。 但是 GCP 也在取得进展,吸引了较大的企业,这些企业希望托管诸如 SAP 和其他传统**客户关系管理**( **CRM** )系统之类的传统工作负载。 + +Google 在机器学习,自动化,容器和网络方面的内部创新以及 TensorFlow 和 Kubernetes 等产品具有先进的云开发能力。 GPS 的技术围绕着对开源的贡献。 + +但是,请注意将云策略专门围绕 GCP 集中。 Gartner 在最近的一份报告中宣称: + +> “谷歌在处理企业账户时表现出不成熟的流程和程序,这有时会使公司难以交易。” + +和: + +> “与本魔力象限中的其他供应商相比,谷歌拥有的经验丰富的托管服务提供商(MSP)和以基础架构为中心的专业服务合作伙伴要少得多。” + +但是,Gartner 还指出: + +> “ Google 正积极针对这些缺点。” + +Gartner 还指出,谷歌的渠道需要发展。 + +**阿里云** + +阿里云于 2017 年首次出现在 Gartner 的魔力象限中,而于 2019 年首次出现在 Gartner 的魔力象限中。 + +Gartner 仅评估了总部位于新加坡的公司的国际服务。 + +阿里云是中国市场的领导者,使用阿里巴巴作为云提供商,为许多中国企业以及中国政府提供了良好的服务。 但是,如果中国决定取消对其他国际云供应商的某些限制,则可能会放弃这一市场份额领导地位的很大一部分。 + +该公司在中国为构建混合云提供支持。 但是,在中国以外,它主要用于以云为中心的工作负载。 2018 年,它与 VMware 和 SAP 建立了合作伙伴关系。 + +阿里巴巴拥有一套服务,其范围可与其他全球提供商的服务组合相媲美。 + +该公司与阿里巴巴集团的紧密关系帮助云服务成为希望在中国开展业务的国际公司和中国公司在中国以外的公司的桥梁。 + +阿里巴巴似乎还没有拥有竞争对手[AWS],Azure 和 GCP 等等竞争对手的服务和功能。 在许多地区,服务仅可用于特定的计算实例。 他们还需要加强其 MSP 生态系统,第三方企业软件集成和操作工具。 + +**Oracle 云基础架构(OCI)** + +在 2017 年,Oracle 的云产品在 Gartner 的 Magic 象限象限中首次亮相。 但是在 2018 年,由于 Gartner 评估标准的变更,甲骨文被提升为 Niche Player 地位。 截至 2019 年,它一直在那里。 + +Oracle 云基础架构(OCI)是于 2016 年推出的第二代服务,旨在淘汰旧版产品(现称为 Oracle Cloud Infrastructure Classic)。 + +OCI 同时提供虚拟服务器和裸机服务器,并一键式安装和配置 Oracle 数据库和容器服务。 + +OCI 吸引具有 Oracle 工作负载的客户,这些工作负载只需要基本的**基础架构即服务**( **IaaS** )功能。 + +Oracle 的云战略依赖于其应用程序,数据库和中间件。 + +Oracle 在吸引其他云提供商的人才以增强其产品方面取得了一些进展。 它还在赢得新业务和使现有的 Oracle 客户转移到 OCI 云方面取得了一些进展。 但是,Oracle 在赶上三巨头之前还有很长的路要走。 + +**IBM Cloud** + +在大型机时代,IBM 是无可争议的计算之王。 当我们开始脱离大型机,而个人计算机无处不在时,它就失去了这个头衔。 IBM 再次试图在这一新的范式转变中重新占据领导地位。 IBM Cloud 是 IBM 应对这一挑战的答案。 + +该公司的多元化云服务包括容器平台,无服务器服务和 PaaS 产品。 IBM Cloud Private 为混合架构提供了补充。 + +像其他一些较低层的云提供商一样,IBM 吸引了其现有客户,这些客户非常愿意从 Big Blue(IBM 的昵称)购买大部分技术。 + +这些现有客户通常具有传统的工作负载。 IBM 还利用这些长期的合作关系,将这些客户转变为新兴的 IBM 解决方案,例如 Watson 的人工智能。 + +IBM 从运行关键生产服务的大量现有客户中受益,而这些客户刚刚开始对采用云感到满意。 现有的客户群使 IBM 处于有利位置,可以在这些客户拥抱云并开始其转型之旅时为其提供协助。 + +像甲骨文一样,IBM 也在艰难地争取从 AWS,Azure 和 Google 获得市场份额。 + +# 亚马逊网络服务(AWS) + +现在,我们将重点关注前三名云提供商。 您可能已经知道,云提供商提供的不仅仅是人工服务,还包括准系统计算和存储服务,一直到非常复杂的高级服务。 与本书中的所有其他内容一样,我们将从 AWS 开始专门研究云提供商提供的人工智能和机器学习服务。 + +## Amazon SageMaker + +Amazon SageMaker 在 2017 年在内华达州拉斯维加斯举行的 Amazon 年度 re:Invent 会议上启动。SageMaker 是一个机器学习平台,使开发人员和数据科学家可以在云中创建,训练和部署机器学习(ML)模型。 。 + +数据科学家在日常工作中使用的通用工具是 Jupyter Notebook。 这些笔记本是包含计算机代码(例如 Python)和富文本元素(例如段落,方程式,图形和 URL)的组合的文档。 Jupyter 笔记本很容易为人类所理解,因为它们包含分析,描述和结果(图,图形,表格等),它们也是可以在线或在笔记本电脑上处理的可执行程序。 + +您可以将 Amazon SageMaker 视为*类固醇*上的 Jupyter 笔记本。 与传统的 Jupyter 笔记本相比,这些是 SageMaker 的一些优势。 换句话说,这些是不同的类固醇口味: + +* 像 Amazon 提供的许多机器学习服务一样,SageMaker 是一项完全托管的机器学习服务,因此您不必担心升级操作系统或安装驱动程序。 +* Amazon SageMaker 提供了一些最常见的机器学习模型的实现,但是这些实现是经过高度优化的,在某些情况下,其运行速度是同一算法的其他实现的 10 倍。 此外,如果 SageMaker 没有开箱即用来提供机器学习模型*,则可以引入自己的算法。* +* Amazon SageMaker 可为各种工作负载提供适量的肌肉。 可以从 Amazon 提供的多种机器类型中选择可以用来训练或部署算法的机器类型。 如果您只是在尝试使用 SageMaker,则可能会决定使用 *ml.t2.medium* 计算机,这是可与 SageMaker 一起使用的最小计算机之一。 如果需要一些有功功率,则可以加速其计算机实例,例如 *ml.p3dn.24xlarge* 计算机。 这样的实例所提供的功能相当于几年前被认为是超级计算机的功能,将花费数百万美元来购买。 + +Amazon SageMaker 使开发人员可以在整个机器学习管道中提高生产力,包括: + +**数据准备** – Amazon SageMaker 可以与许多其他 AWS 服务无缝集成,包括 S3,RDS,DynamoDB 和 Lambda,从而使其易于提取和准备数据以供机器学习算法使用。 + +**算法选择和培训** –开箱即用,Amazon SageMaker 具有各种针对速度和准确性进行了优化的高性能,可扩展机器学习算法。 这些算法可以对 PB 级数据集执行训练,并且可以将性能提高多达类似实现的 10 倍。 这些是 SageMaker 随附的一些算法: + +* 炽热的文字 +* DeepAR 预测 +* 分解机 +* K 均值 +* 随机砍伐森林(RCF) +* 物体检测 +* 图片分类 +* 神经主题模型(NTM) +* IP 洞察 +* K 最近邻居(k-NN) +* 潜在狄利克雷分配(LDA) +* 线性学习者 +* Object2Vec +* 主成分分析(PCA) +* 语义分割 +* 序列到序列 +* XGBoost + +**算法调整和优化** – Amazon SageMaker 提供自动模型调整,也称为超参数调整。 调整通过在指定的超参数范围内使用相同的输入数据集和相同的算法运行多个迭代来找到模型的最佳参数集。 随着培训工作的进行,计分卡将保留该模型的最佳性能版本。 “最佳”的定义基于预定义的指标。 + +例如,假设我们正在尝试解决二进制分类问题。 目标是通过训练 XGBoost 算法模型,最大化算法的曲线( **AUC** )度量下的面积。 我们可以为该算法调整以下超参数: + +* `alpha` +* `eta` +* `min_child_weight` +* `max_depth` + +为了找到这些超参数的最佳值,我们可以为超参数调整指定一个值范围。 将开始一系列培训工作,并且将根据提供最高 AUC 的版本存储最佳的超参数集。 + +Amazon SageMaker 的自动模型调整可以与 SageMaker 的内置算法以及自定义算法一起使用。 + +**算法部署** –在 Amazon SageMaker 中部署模型是一个两步过程: + +1. 创建一个端点配置,指定用于部署模型的 ML 计算实例。 + +1. 启动一个或多个 ML 计算实例以部署模型,并公开 URI 进行调用,这将允许用户进行预测。 + +端点配置 API 接受 ML 实例类型和实例的初始计数。 在神经网络的情况下,配置可以包括 GPU 支持的实例的类型。 端点 API 提供了上一步中定义的基础结构。 + +SageMaker 部署支持一次性和批量预测。 批次预测对可以存储在 Amazon S3 或其他 AWS 存储解决方案中的数据集进行预测。 + +**集成和调用** – Amazon SageMaker 提供了多种与服务交互的方式和界面: + +* **Web API** – Sagemaker 具有 Web API,可用于控制和调用 SageMaker 服务器实例。 +* **SageMaker API** –与其他服务一样,Amazon 具有适用于 SageMaker 的 API,该 API 支持以下编程语言列表: + * 走 + * C ++ + * 爪哇 + * 的 JavaScript + * 蟒蛇 + * 的 PHP + * 红宝石 + * 爪哇 +* **Web 界面** –如果您对 Jupyter Notebook 熟悉,,由于与 SageMaker 进行交互的 Web 界面是 Jupyter Notebook,您将对 Amazon SageMaker 感到宾至如归。 +* **AWS CLI** -AWS 命令行界面(CLI)。 + +## Alexa,Lex 和 Polly –会话绅士 + +在前面的章节中,我们讨论了 Alexa 及其在家庭中越来越普遍的存在。 现在,我们将深入研究为 Alexa 提供支持的技术,并允许您创建自己的对话机器人。 + +Amazon Lex 是用于建立对话代理的服务。 Amazon Lex 和其他聊天机器人是我们这一代人的尝试通过图灵测试,我们在前面的章节中已经进行了讨论。 任何人将与 Alexa 的对话与人类对话混淆都需要一段时间。 但是,亚马逊和其他公司在使这些对话越来越自然的过程中不断取得进步。 Amazon Lex,使用与 Amazon Alexa 相同的技术,使开发人员可以快速构建复杂的自然语言,会话代理或*聊天机器人*。 对于简单的情况,无需任何编程就可以构建其中的一些聊天机器人。 但是,可以使用 AWS Lambda 作为集成技术将 Lex 与 AWS 堆栈中的其他服务集成。 + +稍后,我们将整整一章专门介绍如何创建聊天机器人,因此我们将在本节中简短介绍。 + +## Amazon Comprehend –自然语言处理 + +Amazon Comprehend 是 AWS 提供的**自然语言处理**( **NLP** )服务。 它使用机器学习来分析内容,执行实体识别以及发现隐式和显式关系。 公司开始意识到他们每天产生的大量数据中都有有价值的信息。 可以从客户的电子邮件,支持通知单,产品评论,呼叫中心对话和社交媒体互动中确定有价值的见解。 直到最近,尝试获得这些见解都在成本上处于禁止状态,但是 Amazon Comprehend 之类的工具使对大量数据进行分析具有成本效益。 + +该服务的另一个优点是,它是另一项完全受管的 AWS 服务,因此无需置备服务器,安装驱动程序和升级软件。 它使用简单,不需要 NLP 的丰富经验即可快速提高生产力。 + +与其他 AWS AI / ML 服务一样,Amazon Comprehend 与其他 AWS 服务(例如 AWS Lambda 和 AWS Glue)集成。 + +**用例** – Amazon Comprehend 可用于扫描文档和识别这些文档中的模式。 此功能可以应用于一系列用例,例如情感分析,实体提取和按主题组织文档。 + +例如,Amazon Comprehend 可以分析来自与客户的社交媒体互动中的文本,识别关键短语,并确定客户的体验是正面还是负面。 + +**控制台访问** –可以从 AWS 管理控制台访问 Amazon Comprehend 。 将数据提取到服务中的最简单方法之一是使用 Amazon S3。 然后,我们可以调用 Comprehend 服务以分析文本中的关键短语和关系。 理解可以为每个用户请求返回一个置信度分数,以确定准确性的置信度; 百分比越高,服务越有信心。 Comprehend 可以轻松地批量处理单个请求或多个请求。 + +可用的**应用程序编程接口**( **API** )–截至为止,Comprehend 提供了六个不同的 API 来提供见解。 他们是: + +* **关键字提取 API** –标识关键字和术语。 +* **情绪分析 API** –返回文本的整体含义和感觉,无论是是肯定,否定,中立还是混合。 +* **语法 API** –允许用户标记化文本以定义单词边界,并在其不同词性(例如名词和动词)中标记单词。 +* **实体识别 API** –标识并标记文本中的不同实体,例如人物,地点和公司。 +* **语言检测 API** –标识用于编写文本的主要语言。 服务可以识别一百多种语言。 +* **自定义分类 API** –使用户能够构建自定义文本分类模型。 + +**行业特定的服务** – Amazon Comprehend Medical 于 2018 年在 AWS re:Invent 上发布。它专为医疗行业构建,可以识别行业特定的术语。 Comprehend 还提供了特定的医学命名实体和关系提取 API。 AWS 不会存储或使用 Amazon Comprehend Medical 的任何文本输入来进行未来的机器学习培训。 + +## Amazon Rekognition –图片和视频 + +不,不是错字。 亚马逊用 k 而不是 c 命名其识别服务。 Amazon Rekognition 可以执行图像和视频分析,并使用户可以将此功能添加到其应用程序中。 Amazon Rekognition 已经接受了数百万张带有标签的图像的预训练。 因此,该服务可以快速识别: + +* **对象类型** –椅子,桌子,汽车等 +* **名人** –演员,政客,运动员等 +* **人员** –面部分析,面部表情,面部质量,用户验证等 +* **文本** –将图像识别为文本并将其转换为文本 +* **场景** –跳舞,庆祝,吃饭等 +* **不当内容**-成人,暴力或视觉干扰的内容 + +Amazon Rekognition 已经识别出数十亿张图像和视频,并使用它们不断变得越来越好。 深度学习在图像识别领域的应用可以说是过去几年中最成功的机器学习应用程序,而 Amazon Rekognition 利用深度学习来提供令人印象深刻的结果。 要使用它,不需要具有高水平的机器学习专业知识。 Amazon Rekognition 提供了一个简单的 API。 要使用它,将图像和一些参数一起传递到服务,就是这样。 Amazon Rekognition 只会继续变得更好。 它使用得越多,收到的输入就越多,并且从这些输入中学到的越多。 此外,Amazon 继续增强服务并向该服务添加新功能。 + +Amazon Rekognition 最受欢迎的一些用例和应用程序包括: + +**对象,场景和活动检测** –使用 Amazon Rekognition,您可以识别成千上万种不同类型的对象(例如,汽车,房屋,椅子等)和场景(例如, 城市,购物中心,海滩等)。 分析视频时,可以识别帧中正在发生的特定活动,例如“清空后备箱”或“孩子们玩耍”。 + +**性别识别** – Amazon Rekognition 可用于进行有根据的猜测,以确定图像中的人是男性还是女性。 该功能不应用作一个人的性别的唯一决定因素。 它并不意味着以这种方式使用。 例如,如果男演员戴着长发假发和耳环饰演角色,则可能被识别为女性。 + +**面部识别和分析** –面部识别系统的用途之一是从图像或视频中识别和验证人。 这项技术已经存在了几十年,但是直到最近,它的应用才变得更加流行,便宜和可用,这在很大程度上要归功于深度学习技术和 Rekognition 等服务的普遍存在。 面部识别技术支持当今的许多应用程序,例如照片共享和存储服务,并且是智能手机身份验证工作流中的第二个因素。 + +一旦我们认识到物体是一张脸,就可能要执行进一步的脸部分析。 Amazon Rekognition 可以帮助确定的一些属性包括: + +* 睁眼或闭眼 +* 心情: + * 快乐 + * 现在 + * 愤怒 + * 很惊讶 + * 恶心的 + * 冷静 + * 困惑 + * 恐惧 +* 发色 +* 眼睛的颜色 +* 胡须或胡须 +* 眼镜 +* 年龄范围 +* 性别 +* 人脸的视觉几何 + +当需要在几秒钟内搜索并组织数百万个图像,生成诸如人的情绪之类的元数据标签或识别一个人时,这些检测到的属性很有用。 + +**路径** –可以使用视频文件通过 Amazon Rekognition 在场景中跟踪人的路径。 例如,如果我们看到一个图像,其中包含一个人,他的行李箱周围有行李箱,那么我们可能不知道该人是否正在将行李箱从行李箱中取出并到达,或者他们是否正在将行李箱放入行李箱中并离开。 通过使用路径分析视频,我们将能够做出此确定。 + +**不安全的内容检测** –亚马逊 Rekognition 可以帮助识别图像和视频内容中潜在的不安全或不适当的内容,并且可以提供详细的标签,以根据先前确定的标准准确控制对这些资产的访问。 + +**名人识别** –可以在图像和视频库中快速识别名人和名人,以将照片和镜头分类。 此功能可用于市场营销,广告和媒体行业用例。 + +**图像中的文本** –一旦我们识别出图像中包含文本,就自然要将该图像中的字母和单词转换为文本。 例如,如果 Rekognition 不仅能够识别物体是车牌,而且还可以将图像转换为文本,则可以很容易地根据机动车部门的记录对其进行索引,并跟踪个人及其下落。 + +## 亚马逊翻译 + +Amazon Translate 是另一项 Amazon 服务,可用于将以一种语言编写的大量文本翻译成另一种语言。 Amazon Translate 是按使用付费的,因此仅在您提交需要翻译的内容时才需要付费。 作为 2019 年 10 月的,Amazon Translate 支持 32 种语言: + + +| **语言** | **语言代码** | +| 阿拉伯 | 用 | +| 简体中文) | zh | +| 中国传统的) | zh-TW | +| 捷克文 | cs | +| 丹麦文 | da | +| 荷兰语 | NL | +| 英语 | 在 | +| 芬兰 | 是 | +| 法文 | fr | +| 德语 | de | +| 希腊语 | 他 | +| 希伯来语 | he | +| 没有。 | 你好 | +| 匈牙利 | hu | +| 印度尼西亚 | ID | +| 义大利文 | 它 | +| 日本 | 和 | +| 韩语 | KO | +| 马来语 | 多发性硬化症 | +| 挪威 | 没有 | +| 波斯语 | fa | +| 抛光 | PL | +| 葡萄牙语 | 对于 | +| 罗马尼亚语 | ro | +| 俄语 | ru | +| 西班牙文 | 它是 | +| 瑞典 | sv | +| 泰国 | 日 | +| 土耳其 | tr | +| 乌克兰 | 英国 | +| 乌尔都语 | 从 | +| 越南文 | 我们 | + +除少数例外,大多数这些语言都可以从一种翻译为另一种。 用户还可以向字典中添加项目以自定义术语,并包括特定于其组织或用例的术语,例如品牌和产品名称。 + +Amazon Translate 使用机器学习和连续学习模型来改善其翻译的性能。 + +可以通过三种不同的方式访问该服务,就像可以访问许多 AWS 服务一样: + +* 在 AWS 控制台中,翻译少量文本片段并对该服务进行采样。 +* 使用 AWS API(支持的语言为 C ++,Go,Java,JavaScript,.NET,Node.js,PHP,Python 和 Ruby)。 +* 可以通过 AWS CLI 访问 Amazon Translate。 + +**用于 Amazon Translate** + +许多公司将 Amazon Translate 与其他外部服务一起使用。 此外,Amazon Translate 可以与其他 AWS 服务集成。 例如,翻译可与 Amazon Comprehend 结合使用,以从社交媒体源中提取预定的实体,情感或关键字,然后翻译提取的术语。 在另一个示例中,该服务可以与 Amazon S3 配对以翻译文档存储库并使用 Amazon Polly 讲翻译语言。 + +但是,使用 Amazon Translate 并不意味着人工翻译不再起作用。 一些公司将 Amazon Translate 与人工翻译配对,以提高翻译过程的速度。 + +## 亚马逊机器学习 + +在出现 Amazon SageMaker 之前,就有了 Amazon Machine Learning 或 Amazon ML。 Amazon ML 是一项更简单的服务,在某些情况下仍可以是功能强大的工具。 Amazon ML 最初于 2015 年 4 月在旧金山的 AWS 峰会上发布。 Amazon ML 使所有技能水平的开发人员都可以轻松使用机器学习技术。 Amazon ML 提供了可视化工具和向导,可以指导用户完成创建机器学习模型的过程,而无需学习复杂的 ML 算法和技术。 一旦模型准备就绪,Amazon ML 即可轻松获得预测。 应用程序可以使用简单的 API,而不必在完全托管的服务中实现自定义预测代码。 + +## Amazon Transcribe –转录 + +在 2017 年 re:Invent 会议上发布的另一项服务是 Amazon Transcribe。 您可以将 Amazon Transcribe 当作您的私人秘书,在讲话时做笔记。 + +Amazon Transcribe 是**自动语音识别**( **ASR** )服务,允许开发人员向各种应用程序添加语音到文本功能。 Amazon Transcribe API 可用于分析存储的音频文件。 该服务返回一个包含转录语音的文本文件。 Amazon Transcribe 也可以实时使用。 它可以接收实时音频流,并将生成包含转录文本的实时流。 + +Amazon Transcribe 可用于转录客户服务呼叫并生成音频和视频内容的字幕。 该服务支持常见的音频格式,例如 WAV 和 MP3。 它可以为每个单词生成一个时间戳。 这有助于使用生成的文本快速找到原始音频源。 像其他 Amazon 机器学习服务一样,Amazon Transcribe 不断从文本中学习其正在处理以不断改进服务。 + +## Amazon Textract –文档分析 + +机器学习中最困难的问题之一是识别笔迹。 每个人的笔迹都不一样,我们中有些人的笔迹很糟糕,有时甚至在写完几分钟后我们还是听不懂。 不可以,Amazon 尚未掌握解密鸡抓痕的方法,但是 Amazon Textract 是一项服务,可以将包含文本的图像转换为等效的文本。 如果我们能够扫描那些文档,将它们转换为文本,对其进行索引,并使用户能够搜索这些文档的内容,那么有很多扫描和传真的文档都放在抽屉里,可以为所有者带来很多价值。 + +Amazon Textract 使用户可以从文档,表单和表格中提取文本。 Amazon Textract 可以自动检测文档和关键页面元素的布局。 它可以识别嵌入形式或表中的数据,并在页面上下文中提取该数据。 然后,该信息可以与其他 AWS 服务集成,并用作 AWS Lambda 调用的输入或用作 Amazon Kinesis 的流。 + +# Microsoft Azure + +在介绍了 AWS 之后,让我们看一下 Microsoft 在云服务领域提供的功能:Microsoft Azure。 + +## Microsoft Azure 机器学习 Studio + +Microsoft Azure Machine Learning Studio 是 Microsoft 对 Amazon SageMaker 的回答。 Machine Learning Studio 是一个协作工具,具有简单的拖放界面,允许用户构建,测试和部署机器学习模型。 Machine Learning Studio 支持模型发布,该模型发布可被其他应用程序使用,并可轻松与 BI 工具(例如 Excel)集成。 + +**Machine Learning Studio 交互式工作区** –在*第 3 章*,*机器学习管道*中,我们学习了关于机器学习管道的信息。 Machine Learning Studio 交互式工作区通过允许用户轻松地将数据摄取到工作区,转换数据,然后通过各种数据操作和统计功能分析数据并最终生成预测,从而简化了管道开发。 开发机器学习管道通常是一个迭代过程,而工作区使执行此迭代开发变得简单。 在修改各种功能及其参数时,您将能够可视化和分析模型的性能,直到对结果满意为止。 + +Azure Machine Learning Studio 提供了一个交互式的可视化工作区,可以轻松地构建,测试和迭代预测分析模型。 要将数据集带入工作区,可以将其拖放。 您还可以将分析模块拖到交互式画布上,并将它们连接在一起以形成初始*实验*,然后可以在 Machine Learning Studio 中运行。 如果结果不令人满意,可以修改实验参数并一次又一次地运行直到结果令人满意。 一旦性能令人满意,就可以将*训练实验*转换为*预测性实验*,并且可以将其发布为网络服务,以便用户和其他服务可以访问该模型 。 + +Learning Studio 不需要任何编程。 通过直观地连接数据集和模块以构建预测分析模型来构建实验。 + +**Machine Learning Studio** 入门–要开始使用,您可以使用 Azure 创建免费层帐户。 在撰写本文时,免费帐户的好处是: + +* 12 个月的免费产品,例如虚拟机,存储和数据库 +* 不符合免费套餐资格的服务可获得$ 200 的赠送金额 +* 除非您专门升级到付费帐户,否则不会自动收费 +* 此外,Azure 有超过 25 种始终免费的产品,包括无服务器产品和 AI 服务 + +创建帐户后,您可以访问 Azure Machine Learning Studio。 + +登录后,您会在左侧看到以下标签: + +* **项目** –项目是实验,数据集,笔记本和其他资源的集合 +* **实验**-可以创建,编辑,运行和保存实验 +* **Web 服务** –实验可以作为 Web 服务进行部署和公开 +* **笔记本电脑** – Studio 还支持 Jupyter 笔记本电脑 +* **数据集**-已上传到 Studio 的数据集 +* **训练模型**-经过训练并保存在实验中的模型 +* **设置** –设置可用于配置帐户和资源。 + +**Azure 机器学习库** –该库是数据科学社区可以共享以前使用 Cortana Intelligence Suite 中的组件创建的解决方案的地方。 + +**实验的组成部分** –实验由数据集和分析模块组成,可以将其连接以构建预测分析模型。 有效的实验具有以下特征: + +* 实验至少有一个数据集和一个模块 +* 数据集只能连接到模块 +* 模块可以连接到数据集或其他模块 +* 模块的所有输入端口必须与数据流有某些连接 +* 必须设置每个模块的所有必需参数 + +可以从头开始创建实验,也可以使用现有实验作为模板来创建实验。 + +**数据集** –数据集是已上传到 Machine Learning Studio 的数据,因此可以在实验中使用。 Machine Learning Studio 包含几个示例数据集,并且可以根据需要上载更多数据集。 + +**模块** –模块是可以对数据执行的算法。 Machine Learning Studio 具有各种模块,包括: + +* 数据提取过程 +* 训练功能 +* 计分功能 +* 验证程序 + +更具体的示例: + +* **ARFF 转换模块** –将.NET 序列化的数据集转换为属性关系文件格式(ARFF) +* **计算基本统计模块** –计算基本统计数据,例如均值,标准差等 +* **线性回归模型** –创建基于在线梯度下降的线性回归模型 +* **评分模型**-为训练有素的分类或回归模型评分 + +模块可能具有一组参数,可用于配置模块的内部算法。 + +**模型部署** –预测分析模型准备就绪后,您可以直接从 Machine Learning Studio 将其部署为 Web 服务。 + +## Azure 机器学习服务 + +**Azure 机器学习**( **AML** )服务是一个平台,可让数据科学家和数据工程师大规模地在云中训练,部署,自动化和管理机器学习模型 。 服务的用户可以使用基于 Python 的库来创建功能强大的应用程序和工作流。 AML 服务是一个框架,允许开发人员使用预定义的数据集训练模型,然后将其模型作为 Web 服务包装在 Docker 容器中,并使用各种容器协调器进行部署。 + +可以通过以下两种方式之一访问和使用 Azure 机器学习服务: + +* 通过软件开发工具包(SDK) +* 使用服务可视界面 + +如果您认为这听起来很像 Azure Machine Learning Studio,那么您会认为是正确的。 这些是类似的服务,并且在某个时候,Microsoft 可能会决定将它们合并在一起或弃用其中之一。 如果它们不推荐使用其中之一,则可以高度肯定地假定 Microsoft 将提供一种将其中一项服务中开发的工作流和应用程序迁移到另一项中的方法。 + +机器学习 Studio 与 Azure 机器学习服务有何不同? 这些是主要区别,这使您可以决定使用哪个: + + +| **Azure 机器学习服务** | **Azure 机器学习 Studio** | +| + +* 训练和评分模型的混合部署。 可以在本地训练模型并将其部署在云上,反之亦然。 +* 自由使用不同的框架和机器实例类型 +* 支持自动 ML 和自动超参数调整 + + | + +* 非常适合初学者 +* 可以快速创建标准实验,但更难以自定义 +* 全面托管的服务 +* 在本地 + + | + +在这里,我们提供一个图表,突出显示每种服务所支持的差异和各种功能: + + +| **功能** | **Azure 机器学习 Studio** | **Azure 机器学习服务 SDK** | **Azure 机器学习服务可视界面** | +| 发行年份 | 2015 | 2018 | 2019(预览) | +| 用户界面 | 基于网络 | 基于 API | 基于网络 | +| 云端支援 | 是 | 是 | 是 | +| 本地 | 没有 | 是 | 没有 | +| 工具支援 | 基于网络 | + +* 视觉工作室 +* Azure 笔记本 +* Python 接口 + + | 基于网络 | +| 支持 GPU | 没有 | 是 | 是 | +| 内置算法 | + +* 分类 +* 回归 +* 聚类 +* 时间序列 +* 文本分析 +* 异常检测 + + | 外部包可以导入 | + +* 分类 +* 回归 +* 聚类 + + | +| 自动超参数调整 | 没有 | 是 | 没有 | +| 自动 ML | 没有 | 是 | 没有 | +| 易于扩展 | 不简单 | 可以通过 pip 轻松安装 Python 软件包 | 不简单 | +| Python 支持 | 是 | 是 | 是 | +| R 支持 | 是 | 没有 | 还没 | +| 内置容器 | 没有 | 是 | 没有 | + +## Azure 认知服务 + +**决策服务** –允许用户构建可提供建议并支持有效决策的应用程序。 + +**视觉服务** –启用应用程序,这些应用程序可以识别,标识,字幕,索引以及适度的图像和视频。 + +**语音服务** –此服务将语音转换为文本,并将文本转换为自然声音。 它还可以执行从一种语言到另一种语言的翻译。 此外,它还支持说话人验证和识别。 + +**搜索服务** – Bing 可以向应用程序添加搜索支持,并使用户可以通过单个 API 调用来搜索数十亿个网页,图像,视频和新闻文章。 + +**语言服务** –使应用程序能够使用预先构建的脚本处理自然语言,以评估文本情感并确定文本的整体实体。 + +# Google Cloud Platform(GCP) + +看了 Microsoft Azure 提供的服务之后,让我们继续讨论另一个替代的云平台:GCP。 首先,我们将讨论 GCP 的 AI Hub 服务。 + +## AI 集线器 + +AI Hub 是 Google Cloud Platform 中可用的服务之一。 AI Hub 是即插即用 AI 组件的完全托管库,可用于创建端到端机器学习管道。 AI Hub 提供了多种现成的机器学习算法。 AI Hub 提供企业级协作功能,使公司可以私下托管其机器学习工作流程并促进重用和共享。 您还可以轻松地将模型部署到 Google Cloud 以及其他环境和云提供商的生产环境中。 AI Hub 于 2018 年发布,目前尚处于早期阶段。 考虑到 Google 对 AI 研究的重视,我们希望 AI Hub 能够迅速成熟并继续以更快的速度提供更多功能。 + +**组件和代码发现** – AI Hub 是一个内容存储库,可让用户快速发现高质量的内容。 可通过 AI Hub 访问的一些发布者是: + +* Google AI +* Google Cloud AI +* Google Cloud 合作伙伴 + +如果在企业内使用集线器,则用户还可以找到公司内其他团队构建的其他组件。 + +**协作** – AI 集线器提高了用户生产力,并使他们避免了重复劳动。 AI Hub 提供了高度精细的控件,以仅与组织中应该有权访问组件的用户共享组件。 它还使用户可以访问由 Google 工程师和研究人员创建的预定义机器学习算法,以及 Azure 合作伙伴和其他发布者共享的其他代码。 + +**部署** – AI Hub 可以针对特定业务需求修改和定制算法和管道。 它还提供了用于部署经过训练的模型的直观机制。 这些模型可以部署在 Google Cloud 或其他环境和云提供商中。 + +## Google Cloud AI 构建基块 + +除了 AI Hub 的(可以与 Amazon SageMaker 和 Azure 机器学习 Studio 相比)之外,Google Cloud 在完全托管服务方面还提供了与 AWS 和 Azure 类似的产品,这些服务简化了机器学习在文本中的应用, 语言,图像和视频。 Google 在 *Google Cloud AI 构件*框架下组织了许多此类托管服务。 对于许多此类托管服务,有两种与之交互的方式– AutoML 和 API。 AutoML 用于自定义模型,API 用于预训练模型。 AutoML 和 API 可以单独使用,也可以一起使用。 + +**Google Cloud AutoML 自定义模型** – AutoML 服务使用 Google 的最新转移学习和神经体系结构搜索技术,允许用户为各种用例创建特定于领域的自定义模型。 + +**Google Cloud 预训练的 API** –在处理常见用例时,使用预先训练的 API 的 Google 服务用户可以立即变得富有成效,而无需事先训练模型。 预训练的 API 不断透明地升级,以提高这些模型的速度和准确性。 + +**Vision AI 和 AutoML Vision** –该服务允许用户使用 AutoML Vision 或使用预先训练的 Vision API 模型从图像中获取见解。 此服务可以检测情绪,理解文字等。 + +要使用该服务,可以使用自定义图像模型上传和分析图像。 该服务具有易于使用的可视界面。 该服务使您可以优化模型的准确性,延迟和大小。 结果可以导出到云中的其他应用程序或边缘的一系列设备。 + +Google Cloud 的 Vision API 提供了功能强大的经过预先训练的机器学习模型,可以使用 RESTful 和 RPC API 调用进行访问。 该服务可以快速标记图像并对其进行分类。 该服务已经过预培训,已经包含数百万个类别。 它也可以用于面部识别和分析,以及识别图像中的标题并将其转换为文本。 + +**AutoML 视频智能和视频智能 API** – AutoML 视频智能服务具有一个简单的界面,该界面可以使用自定义模型识别,跟踪和分类视频中的对象。 该服务不需要编程或人工智能方面的广泛背景。 该服务用于需要自定义标签的应用程序,而这些标签不能由经过预先培训的 Video Intelligence API 生成。 + +Video Intelligence API 具有经过预先训练的模型,可以识别各种常见的对象,场景和活动。 除了存储的视频外,它还支持流视频。 随着处理更多图像,它会随着时间的推移自动透明地改善。 + +**AutoML Translation and Translation API** –很少或没有编程经验的开发人员和翻译人员都可以创建生产质量的模型。 Translation API 使用预训练的神经网络算法来提供世界一流的机器翻译,在某些情况下,这种翻译已开始与人类水平的表现相抗衡。 + +**AutoML 自然语言和自然语言 API** –该服务可用于对文本分类,执行实体提取和情感检测,所有这些都使用简单易用的 API。 用户可以利用 AutoML 自然语言界面来提供数据集并确定将使用哪些自定义模型。 + +Natural 语言 API 具有预先训练的模型,该模型使 API 的用户可以访问**自然语言理解**( **NLU** )功能,包括: + +* 实体分析 +* 情绪分析 +* 内容分类 +* 实体情感分析 +* 语法分析 + +**Dialogflow** – Dialogflow 是开发服务,允许用户创建对话代理。 它可以用来构建聊天机器人,以实现自然而丰富的交互。 它允许服务的用户一次开发代理,然后将它们部署到各种平台,包括: + +* 谷歌助手 +* Facebook Messenger +* 松弛 +* Alexa 语音服务 + +**文字转语音** – Google Cloud 文字转语音可以将文字转换为类似人类的语音,并具有 30 多种语言和口音的 180 多种语音。 例如,它可以模仿美国的口音或英国的口音。 它使用语音合成(WaveNet)和 Google 开发的神经网络来提供高保真音频。 用户可以调用 API 并创建逼真的交互。 不难想象,我们很快就会看到这种技术已嵌入到各种客户服务应用程序中。 + +**语音转文本** –您可以将视为该服务与先前的服务相反。 如果*文本到语音*是声音,则*语音到文本*提供了耳朵。 Google Cloud 语音转文本功能使服务的用户可以利用神经网络模型将音频文件转换为文本。 这些模型的复杂性对服务的用户完全隐藏了,他们可以调用一个易于使用的 API 来调用它。 撰写本文时,API 支持 120 多种语言和变体。 它可以用于: + +* 在应用程序中启用语音命令 +* 转录呼叫中心对话 +* 与工作流程中的其他 Google 和非 Google 服务集成 +* 实时处理音频以及预先录制的版本 + +**AutoML 表** –该服务使分析人员,开发人员和数据科学家可以在结构化数据上构建和部署机器学习模型。 在许多用例中,它几乎不需要编码,因此可以大大提高部署速度。 在这些情况下,可通过类似向导的界面进行配置。 当需要编码时,AutoML Tables 支持 *Colab 笔记本*。 这些笔记本是功能强大的笔记本,类似于 Jupyter 笔记本,并具有许多使其易于使用并与其他用户协作的附加功能。 该服务是域无关的,因此可以用来解决各种各样的问题。 截至 2019 年 10 月,该服务仍未普遍可用,但可以通过 Beta 版访问。 + +**推荐 AI** –此 Google 服务可以大规模提供高度个性化的产品推荐。 二十多年来,Google 一直在其旗舰产品(例如 Google Ads,Google 搜索和 YouTube)中提供建议。 建议 AI 利用该经验,使服务的用户能够在各种应用程序和用例中提供个性化的建议,以满足个人客户的需求和偏好。 在撰写本文时,该产品也处于 beta 版本,因此通常不可用。 + +# 摘要 + +在本章中,我们看到所有主要的技术公司都参与了高风险的军备竞赛,成为云计算的领军人物。 在计算的历史中,随着不同技术的出现,最常见的结果是让一个玩家主导整个空间,而其他所有竞争者都被放任其职。 云可能是计算历史上出现的最重要的技术。 当客户决定他们首选的云提供商是谁时,即使他们现在可能尚未意识到,他们正在做出决定,将他们锁定在该云提供商的生态系统中,并且很难从中摆脱出来并跳到另一个云中 提供商。 + +云供应商意识到了这一点的重要性,并争相与竞争对手的能力相提并论。 当我们分析来自前三名云供应商的机器学习产品时,我们在本章中清楚地看到了这一点。 他们正在努力相互区分,同时在每种服务和功能上都力求彼此匹敌。 在未来几年内,这些云产品将如何发展以及这些供应商将提供哪些伟大的新服务,尤其是在人工智能和机器学习领域,将是令人兴奋的。 + +作为技术专家,对我们而言,一个缺点是很难跟上所有有趣的玩具和技术,但是如果没有别的,那么探索它们就应该是激动人心的旅程。 + +谈到玩具,在下一章中,我们将探讨如何使用人工智能构建游戏,并将我们学到的一些概念加以利用。 \ No newline at end of file diff --git a/docs/ai-py/13.md b/docs/ai-py/13.md new file mode 100644 index 0000000000000000000000000000000000000000..c2cb1bcb75ee8d6988a6c4cf3a1e6e6b23693a62 --- /dev/null +++ b/docs/ai-py/13.md @@ -0,0 +1,684 @@ +# 13 + +# 使用人工智能构建游戏 + +在本章中,我们将学习如何使用称为组合搜索的人工智能技术来构建游戏。 在其最基本的形式中,可以将其视为*蛮力方法*。 我们探索每种可能的解决方案。 在本章的后面,我们将变得更加聪明,找到一种使搜索短路的方法,而不必尝试所有可能的方法。 我们将学习如何使用搜索算法有效地提出赢得一系列游戏的策略。 然后,我们将使用这些算法为不同的游戏构建智能机器人。 + +在本章结束时,您将对以下概念有更好的理解: + +* 游戏中的搜索算法 +* 组合搜索 +* Minimax 算法 +* Alpha-Beta 修剪 +* Negamax 算法 +* 构建一个机器人来玩最后的硬币站立 +* 构建一个玩井字游戏的机器人 +* 构建两个机器人来相互玩四连环 +* 构建两个机器人来对抗 Hexapawn + +# 在游戏中使用搜索算法 + +搜索算法通常在游戏中用于确定策略的。 该算法搜索可能的游戏动作并选择最佳的动作。 实施这些搜索时需要考虑各种参数-速度,准确性,复杂性等。 这些算法考虑了给定当前游戏状态的所有可能的游戏动作,然后评估每个可能的动作以确定最佳动作。 这些算法的目标是找到最终导致游戏获胜的最佳动作。 而且,每个游戏都有不同的规则和约束集。 这些算法在探索最佳动作时会考虑这些规则和约束。 + +没有对手的游戏比拥有对手的游戏更容易优化。 具有多个玩家的游戏,游戏玩法变得更加复杂。 让我们考虑一个两人游戏。 玩家为赢得比赛而进行的每一个举动,对方玩家都会采取行动以阻止该玩家获胜。 因此,当搜索算法从当前状态中找到最佳移动方式集时,它就不能不考虑对方玩家的反向移动而仅仅进行移动。 这意味着每次移动后都需要不断重新评估搜索算法。 + +让我们讨论一下计算机如何感知任何给定的游戏。 我们可以将游戏视为搜索树。 该树中的每个节点代表一个未来状态。 例如,如果您正在演奏 **Tic–Tac–Toe** (无与交叉),则可以构造一棵树来表示所有可能的移动。 我们从树的根开始,这是游戏的起点。 该节点将具有几个代表各种可能动作的子代。 反过来,在对手进行更多移动之后,这些孩子将拥有更多代表游戏状态的孩子。 树的终端节点代表游戏的最终动作。 游戏将以平局结束,或者其中一名玩家将赢得比赛。 s 搜索算法搜索该树以在游戏的每个步骤做出决策。 现在,我们将学习各种搜索技术,包括如何进行详尽的组合搜索,以帮助我们在井字游戏中永不丢失,并解决许多其他问题。 + +# 组合搜索 + +搜索算法似乎可以解决为游戏添加智能的问题,但是存在一个缺点。 这些算法采用一种称为穷举搜索的搜索类型,也称为*蛮力*搜索。 它基本上探索了整个搜索空间并测试了每种可能的解决方案。 这意味着该算法将必须先探索所有可能的解决方案,然后才能获得最佳解决方案。 + +随着游戏变得越来越复杂,蛮力搜索可能不是最好的方法,因为可能性越来越多。 搜索很快变得难以处理。 为了解决该问题,可以使用组合搜索来解决问题。 组合搜索是指一个研究领域,其中搜索算法使用启发式方法有效地探索解决方案空间,以减小搜索空间的大小。 这在象棋或围棋之类的游戏中很有用。 + +组合搜索通过使用修剪策略有效地工作。 这些策略通过消除显然是 g 的解决方案来避免测试所有可能的解决方案。 这有助于节省时间和精力。 现在,我们已经了解了详尽的组合搜索及其局限性,我们将开始探索捷径,“修剪”搜索树并避免测试每个组合的方法。 在以下各节中,我们将探索一些特定的算法,这些算法使我们能够执行组合的 earch。 + +## Minimax 算法 + +现在,我们已经简要地讨论了组合搜索,下面我们来讨论组合搜索算法所采用的启发式方法。 这些启发式方法可用于加快搜索策略,而 Minimax 算法就是组合搜索使用的此类策略之一。 当两个玩家互相对抗时,他们的目标是截然相反的。 每个玩家都试图赢得胜利。 因此,每一方都需要预测对方球员要做什么才能赢得比赛。 牢记这一点,Minimax 试图通过战略来实现这一目标。 它将尝试最小化对手试图最大化的功能。 + +如前所述,蛮力仅在具有少量可能动作的简单游戏中起作用。 在更复杂的情况下,计算机无法通过所有可能的状态来找到最佳游戏玩法。 在这种情况下,计算机可以尝试使用启发式方法基于当前状态计算最佳移动。 计算机构造一棵树,它从底部开始。 它评估哪些举动会对自己的对手有利。 该算法基于以下前提:对手将做出哪些动作,前提是对手将做出最有利于他们的动作,从而使计算机受益最少。 此结果是树的终端节点之一,计算机使用此位置向后工作。 可以为计算机可用的每个选项分配一个值,然后可以选择最高的值来执行操作。 + +## Alpha-Beta 修剪 + +Minimax 搜索是一种有效的策略,但最终仍会探索树上不相关的部分。 当在节点上找到指示符,表明该子树中不存在解决方案时,则无需评估该子树。 但是 Minimax 搜索过于保守,因此最终探索了其中的一些 b 树。 + +Alpha-Beta 算法更智能,可以避免搜索发现的树中没有解决方案的部分。 此过程称为,称为**修剪**,Alpha-Beta 修剪是一种策略,用于避免搜索树中不包含解决方案的部分。 + +Alpha-Beta 修剪中的 Alpha 和 Beta 参数是指计算过程中使用的两个边界。 这些参数是指限制可能的解决方案集的值。 这是基于已经探索过的树的部分的。 Alpha 是可能解的最大上限,而 Beta 是可能解的最小上限。 + +如前所述,可以根据当前状态为每个节点分配一个值。 当算法将任何新节点视为解决方案的潜在路径时,如果该节点值的当前估计值介于 Alpha 和 Beta 之间,则可以进行计算。 这是的修剪方式。 + +## Negamax 算法 + +**Negamax** 算法是 Minimax 的一种变体,在现实世界的实现中经常使用。 两人游戏通常是零和游戏,这意味着一个玩家的损失等于另一个玩家的收益,反之亦然。 Negamax 广泛使用此属性来提出增加其赢得游戏机会的策略。 + +就游戏而言,给第一位玩家的给定位置的价值是给第二位玩家的价值的否定。 每个玩家都在寻找能够最大程度地伤害对手的举动。 此举所产生的价值应使对手获得最小价值。 这两种方法都可以无缝地工作,这意味着可以使用一种方法来评估头寸。 就简单性而言,这是它比 Minimax 更具优势的地方。 Minimax 要求第一个玩家选择具有最大值的移动,而第二个玩家必须选择具有最小值的移动。 这里也使用 Alpha-Beta 修剪。 现在,我们已经研究了几种最流行的组合搜索算法,下面我们安装一个库,以便我们可以构建一些 AI 并查看这些算法的实际应用。 + +# 安装 easyAI 库 + +在本章中,我们将使用名为`easyAI`的库。 这是一个人工智能框架,它提供了构建两人游戏所需的所有功能。 您可以在此处了解的更多信息: + +[http://zulko.github.io/easyAI](http://zulko.github.io/easyAI ) + +通过运行以下命令进行安装: + +```py +$ pip3 install easyAI +``` + +为了使用某些预建例程,需要访问某些文件。 为了易于使用,本书随附的代码包含一个名为`easyAI`的文件夹。 确保将此文件夹放置在与代码文件相同的文件夹中。 此文件夹基本上是`easyAI` GitHub 存储库的子集,可在此处找到: + +[https://github.com/Zulko/easyAI](https://github.com/Zulko/easyAI ) + +您可以遍历源代码以使自己更加熟悉。 + +# 建立一个玩最后代币站立的机器人 + +在此游戏中,有一堆硬币,每个玩家轮流从该堆中取出许多硬币。 可从堆中取出的硬币数量有上限和下限。 游戏的目的是避免拿最后一枚硬币。 该配方是`easyAI`库中给出的“骨头游戏”配方的一种变体。 让我们看看如何构建一个可以与用户对战的游戏。 + +创建一个新的 Python 文件,并通过以下软件包导入 foll : + +```py +from easyAI import TwoPlayersGame, id_solve, Human_Player, AI_Player +from easyAI.AI import TT +``` + +创建一个类来处理游戏的所有操作。 该代码继承自`easyAI`库中提供的基类`TwoPlayersGame`。 为了使代码正常运行,必须定义几个参数。 第一个是`players`变量。 稍后将讨论`player`对象。 使用以下代码创建类: + +```py +class LastCoinStanding(TwoPlayersGame): + def __init__(self, players): + # Define the players. Necessary parameter. + self.players = players +``` + +定义要开始游戏的玩家。 球员从一开始编号。 因此,在这种情况下,玩家一开始游戏: + +```py + # Define who starts the game. Necessary parameter. + self.nplayer = 1 +``` + +定义堆中的硬币数量。 您可以在这里自由选择任何数字。 在这种情况下,让我们选择`25`: + +```py + # Overall number of coins in the pile + self.num_coins = 25 +``` + +定义任何动作中可以取出的最大硬币数。 您也可以自由选择此参数的任何数字。 在我们的情况下,我们选择`4`: + +```py + # Define max number of coins per move + self.max_coins = 4 +``` + +定义所有可能的动作。 在这种情况下,玩家每一步可以拿 1、2、3 或 4 个硬币: + +```py + # Define possible moves + def possible_moves(self): + return [str(x) for x in range(1, self.max_coins + 1)] +``` + +定义一种方法移除硬币并跟踪堆中剩余的硬币数量: + +```py + # Remove coins + def make_move(self, move): + self.num_coins -= int(move) +``` + +通过检查剩余的硬币数量来检查是否有人赢得了比赛: + +```py + # Did the opponent take the last coin? + def win(self): + return self.num_coins <= 0 +``` + +在有人赢得比赛后停止游戏: + +```py + # Stop the game when somebody wins + def is_over(self): + return self.win() +``` + +根据`win`方法计算分数。 有必要定义此方法: + +```py + # Compute score + def scoring(self): + return 100 if self.win() else 0 +``` + +定义一种方法来显示桩的当前状态: + +```py + # Show number of coins remaining in the pile + def show(self): + print(self.num_coins, 'coins left in the pile') +``` + +定义`main`功能并从定义转置表开始。 换位表用于游戏中以存储位置和运动,以加快算法的速度。 + +输入以下代码: + +```py +if __name__ == "__main__": + # Define the transposition table + tt = TT() +``` + +定义方法`ttentry`以获取硬币数。 这是一个可选方法,用于创建用于描述游戏的字符串: + +```py + # Define the method + LastCoinStanding.ttentry = lambda self: self.num_coins +``` + +让我们使用 AI 解决游戏。 函数`id_solve`用于使用迭代加深来解决给定的游戏。 它基本上确定了谁可以使用所有途径赢得比赛。 它看起来可以回答以下问题: + +* 第一个玩家能否通过完美玩法来赢得胜利? +* 电脑会永远输给一个完美的对手吗? + +方法`id_solve`多次探索了游戏的 Negamax 算法中的各种选项。 它总是从游戏的初始状态开始,并且需要不断增加深度才能继续进行。 它将一直这样做,直到分数表明有人将赢或输。 中的第二个参数采用该方法尝试使用的深度列表。 在这种情况下,它将尝试从`2`到`20`的所有值: + +```py + # Solve the game + result, depth, move = id_solve(LastCoinStanding, + range(2, 20), win_score=100, tt=tt) + print(result, depth, move) +``` + +在计算机上启动游戏: + +```py + # Start the game + game = LastCoinStanding([AI_Player(tt), Human_Player()]) + game.play() +``` + +完整代码在文件`coins.py`中给出。 这是一个交互式程序,因此它将期望用户输入。 如果您运行代码,则基本上可以与计算机对抗。 您的目标是迫使计算机拿走最后的硬币,以便您赢得比赛。 如果运行代码,最初将获得以下输出: + +![](img/B15441_13_01.png) + +图 1:最后一次站立硬币游戏的初始输出 + +如果向下滚动,将在结尾处看到以下内容: + +![](img/B15441_13_02.png) + +图 2:最后一枚硬币站立式游戏的最终输出 + +如我们所见,计算机赢得了游戏,因为用户拿起了最后一个硬币。 + +让我们来看看为另一个游戏[tf-tac-toe]构建机器人。 + +# 建立一个玩井字游戏的机器人 + +井字游戏(Nights and Crosss)是,也许是世界上最著名的游戏之一。 让我们看看如何构建一个可以与用户对战的游戏。 这是`easyAI`库中给出的井字游戏食谱的一个较小变体。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +from easyAI import TwoPlayersGame, AI_Player, Negamax +from easyAI.Player import Human_Player +``` + +定义一个包含所有玩游戏方法的类。 首先定义玩家和谁开始游戏: + +```py +class GameController(TwoPlayersGame): + def __init__(self, players): + # Define the players + self.players = players +``` + +```py + # Define who starts the game + self.nplayer = 1 +``` + +我们将使用 3×3 的木板,编号从 1 到 9: + +```py + # Define the board + self.board = [0] * 9 +``` + +定义一种方法来计算所有可能的移动: + +```py + # Define possible moves + def possible_moves(self): + return [a + 1 for a, b in enumerate(self.board) if b == 0] +``` + +定义一种在移动后更新板的方法: + +```py + # Make a move + def make_move(self, move): + self.board[int(move) - 1] = self.nplayer +``` + +定义一种方法来查看是否有人输了游戏。 我们将检查某人是否连续三个: + +```py + # Does the opponent have three in a line? + def loss_condition(self): + possible_combinations = [[1,2,3], [4,5,6], [7,8,9], + [1,4,7], [2,5,8], [3,6,9], [1,5,9], [3,5,7]] +``` + +```py + return any([all([(self.board[i-1] == self.nopponent) + for i in combination]) for combination in possible_combinations]) +``` + +使用`loss_condition`方法检查游戏是否完成: + +```py + # Check if the game is over + def is_over(self): + return (self.possible_moves() == []) or self.loss_condition() +``` + +定义一种方法来显示当前进度: + +```py + # Show current position + def show(self): + print('\n'+'\n'.join([' '.join([['.', 'O', 'X'][self.board[3*j + i]] + for i in range(3)]) for j in range(3)])) +``` + +使用`loss_condition`方法计算分数: + +```py + # Compute the score + def scoring(self): + return -100 if self.loss_condition() else 0 +``` + +定义`main`功能并从定义算法开始。 Negamax 将用作此游戏的 AI 算法。 可以预先指定算法应预先考虑的步骤数。 在这种情况下,让我们选择`7`: + +```py +if __name__ == "__main__": + # Define the algorithm + algorithm = Negamax(7) +``` + +开始游戏: + +```py + # Start the game + GameController([Human_Player(), AI_Player(algorithm)]).play() +``` + +完整代码在文件`tic_tac_toe.py`中给出。 这是一个交互式游戏,您可以在计算机上玩。 如果运行代码,最初将获得以下输出: + +![](img/B15441_13_03.png) + +图 3:井字游戏初始输出 + +如果向下滚动,将看到以下输出: + +![](img/B15441_13_04.png) + +图 4:井字游戏最终输出 + +如我们所见,游戏以平局结束。 我们已经研究了可以与用户对抗的机器人。 现在让我们建立两个机器人来对抗。 这次是 Connect Four™中的 。 + +# 建立两个机器人来互相玩 Connect Four™ + +Connect Four™是,这是一款流行的两人游戏,以 Milton Bradley 商标出售。 它也以其他名称(例如“连续四个”或“四个向上”)而闻名。 在此游戏中,玩家轮流将光盘放入由六行七列组成的垂直网格中。 目标是连续获取四张光盘。 这是`easyAI`库中提供的“连接四个”配方的变体。 让我们看看如何构建它。 在此配方中,我们将创建两个可以相互对抗的机器人,而不是与计算机对抗。 每个人将使用不同的算法来查看哪个获胜。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +from easyAI import TwoPlayersGame, Human_Player, AI_Player, \ + Negamax, SSS +``` + +定义一个类,其中包含玩游戏所需的所有方法: + +```py +class GameController(TwoPlayersGame): + def __init__(self, players, board = None): + # Define the players + self.players = players +``` + +用六行七列定义板: + +```py + # Define the configuration of the board + self.board = board if (board != None) else ( + np.array([[0 for i in range(7)] for j in range(6)])) +``` + +定义谁将开始游戏。 在这种情况下,让玩家一个开​​始游戏: + +```py + # Define who starts the game + self.nplayer = 1 +``` + +定义位置: + +```py + # Define the positions + self.pos_dir = np.array([[[i, 0], [0, 1]] for i in range(6)] + + [[[0, i], [1, 0]] for i in range(7)] + + [[[i, 0], [1, 1]] for i in range(1, 3)] + + [[[0, i], [1, 1]] for i in range(4)] + + [[[i, 6], [1, -1]] for i in range(1, 3)] + + [[[0, i], [1, -1]] for i in range(3, 7)]) +``` + +定义方法以获取所有可能的移动: + +```py + # Define possible moves + def possible_moves(self): + return [i for i in range(7) if (self.board[:, i].min() == 0)] +``` + +定义一种方法来控制移动: + +```py + # Define how to make the move + def make_move(self, column): + line = np.argmin(self.board[:, column] != 0) + self.board[line, column] = self.nplayer +``` + +定义一种显示当前状态的方法: + +```py + # Show the current status + def show(self): + print('\n' + '\n'.join( + ['0 1 2 3 4 5 6', 13 * '-'] + + [' '.join([['.', 'O', 'X'][self.board[5 - j][i]] + for i in range(7)]) for j in range(6)])) +``` + +定义一种方法来计算损失的外观。 每当有人连续赢得四个时,该玩家就会赢得比赛: + +```py + # Define what a loss_condition looks like + def loss_condition(self): + for pos, direction in self.pos_dir: + streak = 0 + while (0 <= pos[0] <= 5) and (0 <= pos[1] <= 6): + if self.board[pos[0], pos[1]] == self.nopponent: + streak += 1 + if streak == 4: + return True + else: + streak = 0 +``` + +```py + pos = pos + direction +``` + +```py + return False +``` + +使用`loss_condition`方法检查游戏是否结束: + +```py + # Check if the game is over + def is_over(self): + return (self.board.min() > 0) or self.loss_condition() +``` + +计算分数: + +```py + # Compute the score + def scoring(self): + return -100 if self.loss_condition() else 0 +``` + +定义`main`功能并从定义算法开始。 然后,这两种算法将相互竞争。 Negamax 将用于第一个计算机播放器, **SSS *** 算法将用于第二个计算机播放器。 SSS *是一种搜索算法,它通过以最佳优先方式遍历树来进行状态空间搜索。 两种方法都将事先考虑的匝数作为输入参数。 在这种情况下,让我们同时使用`5`: + +```py +if __name__ == '__main__': + # Define the algorithms that will be used + algo_neg = Negamax(5) + algo_sss = SSS(5) +``` + +开始玩游戏: + +```py + # Start the game + game = GameController([AI_Player(algo_neg), AI_Player(algo_sss)]) + game.play() +``` + +打印结果: + +```py + # Print the result + if game.loss_condition(): + print('\nPlayer', game.nopponent, 'wins.') + else: + print("\nIt's a draw.") +``` + +完整代码在文件`connect_four.py`中给出。 这不是一个互动游戏。 该代码将一种算法与另一种算法进行比较。 Negamax 算法是玩家 1,SSS *算法是玩家 2。 + +如果运行代码,最初将获得以下输出: + +![](img/B15441_13_05.png) + +图 5:连接四个游戏的初始输出 + +如果向下滚动,则会在结尾处看到以下内容: + +![](img/B15441_13_06.png) + +图 6:连接四个游戏的最终输出 + +如我们所见,第二名玩家赢得了比赛。 让我们再尝试一个 游戏:Hexapawn。 + +# 建立两个机器人来互相对抗 Hexapawn + +**Hexapawn** 是一款两人游戏,尺寸为 *N* *×* *M* 。 棋子存在于棋盘的两侧,目标是将棋子一直推进到棋盘的另一端。 国际象棋的标准典当规则适用。 这是`easyAI`库中提供的 Hexapawn 配方的变体。 将创建两个机器人,并使其相互对峙。 让我们创建代码。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +from easyAI import TwoPlayersGame, AI_Player, \ + Human_Player, Negamax +``` + +定义一个类,其中包含控制游戏所需的所有方法。 首先定义两侧的棋子数和棋盘的长度。 创建一个包含位置的元组列表: + +```py +class GameController(TwoPlayersGame): + def __init__(self, players, size = (4, 4)): + self.size = size + num_pawns, len_board = size + p = [[(i, j) for j in range(len_board)] \ + for i in [0, num_pawns - 1]] +``` + +为每个玩家分配方向,目标和棋子: + +```py + for i, d, goal, pawns in [(0, 1, num_pawns - 1, + p[0]), (1, -1, 0, p[1])]: + players[i].direction = d + players[i].goal_line = goal + players[i].pawns = pawns +``` + +定义玩家并指定谁先开始: + +```py + # Define the players + self.players = players +``` + +```py + # Define who starts first + self.nplayer = 1 +``` + +定义用于识别棋盘上的位置(例如 B6 或 C7)的字母: + +```py + # Define the alphabets + self.alphabets = 'ABCDEFGHIJ' +``` + +定义一个`lambda`函数将字符串转换为元组: + +```py + # Convert B4 to (1, 3) + self.to_tuple = lambda s: (self.alphabets.index(s[0]), + int(s[1:]) - 1) +``` + +定义`lambda`函数以将元组转换为字符串: + +```py + # Convert (1, 3) to B4 + self.to_string = lambda move: ' '.join([self.alphabets[ + move[i][0]] + str(move[i][1] + 1) + for i in (0, 1)]) +``` + +定义一种方法来计算可能的移动: + +```py + # Define the possible moves + def possible_moves(self): + moves = [] + opponent_pawns = self.opponent.pawns + d = self.player.direction +``` + +如果在某个位置没有找到对手的棋子,那么这是一个有效的举动: + +```py + for i, j in self.player.pawns: + if (i + d, j) not in opponent_pawns: + moves.append(((i, j), (i + d, j))) +``` + +```py + if (i + d, j + 1) in opponent_pawns: + moves.append(((i, j), (i + d, j + 1))) +``` + +```py + if (i + d, j - 1) in opponent_pawns: + moves.append(((i, j), (i + d, j - 1))) +``` + +```py + return list(map(self.to_string, [(i, j) for i, j in moves])) +``` + +定义如何进行移动并基于此更新棋子: + +```py + # Define how to make a move + def make_move(self, move): + move = list(map(self.to_tuple, move.split(' '))) + ind = self.player.pawns.index(move[0]) + self.player.pawns[ind] = move[1] +``` + +```py + if move[1] in self.opponent.pawns: + self.opponent.pawns.remove(move[1]) +``` + +定义损失条件。 如果一名玩家在一行中获得 4,则对手输了: + +```py + # Define what a loss looks like + def loss_condition(self): + return (any([i == self.opponent.goal_line + for i, j in self.opponent.pawns]) + or (self.possible_moves() == []) ) +``` + +使用`loss_condition`方法检查游戏是否完成: + +```py + # Check if the game is over + def is_over(self): + return self.loss_condition() +``` + +打印当前状态: + +```py + # Show the current status + def show(self): + f = lambda x: '1' if x in self.players[0].pawns else ( + '2' if x in self.players[1].pawns else '.') +``` + +```py + print("\n".join([" ".join([f((i, j)) for j in + range(self.size[1])]) for i in range(self.size[0])])) +``` + +定义`main`函数并从定义`scoring` lambda 函数开始: + +```py +if __name__=='__main__': + # Compute the score + scoring = lambda game: -100 if game.loss_condition() else 0 +``` + +定义要使用的算法。 在这种情况下,我们将使用 Negamax,它可以预先计算`12`移动并为策略使用`scoring` lambda 函数: + +```py + # Define the algorithm + algorithm = Negamax(12, scoring) +``` + +开始玩游戏: + +```py + # Start the game + game = GameController([AI_Player(algorithm), + AI_Player(algorithm)]) + game.play() + print('\nPlayer', game.nopponent, 'wins after', game.nmove, 'turns') +``` + +完整代码为文件`hexapawn.py`中提供的。 这不是互动游戏。 创建了两个机器人并使其相互对接。 如果运行代码,最初将获得以下输出: + +![](img/B15441_13_07.png) + +图 7:Hexapawn 游戏的初始输出 + +如果您向下滚动,则会在结尾处看到以下内容: + +![](img/B15441_13_08.png) + +图 8:Hexapawn 游戏最终输出 + +如我们所见,一名玩家赢得了比赛。 + +# 摘要 + +在本章中,我们讨论了如何使用一种称为组合搜索的特殊类型的人工智能技术来构建游戏。 我们学习了如何使用这些类型的搜索算法来有效地提出赢得比赛的策略。 这些算法可用于为更复杂的游戏构建游戏机,并解决各种问题。 我们讨论了组合搜索以及如何使用组合搜索来加快搜索过程。 我们了解了 Minimax 和 Alpha-Beta 修剪。 我们了解了 Negamax 算法是如何在实践中使用的。 然后,我们使用这些算法来构建用于玩“最后的硬币站立”和“井字游戏”的机器人。 + +我们学习了如何在 Connect Four 和 Hexapawn 中构建两个相互竞争的机器人。 在下一章中,我们将学习语音识别并构建一个自动识别语音的系统。 \ No newline at end of file diff --git a/docs/ai-py/14.md b/docs/ai-py/14.md new file mode 100644 index 0000000000000000000000000000000000000000..b67113fe7b8ff49e035b63f9924b05c222940802 --- /dev/null +++ b/docs/ai-py/14.md @@ -0,0 +1,871 @@ +# 14 + +# 构建语音识别器 + +在本章中,我们将学习语音识别。 我们将讨论如何处理语音信号,并学习如何可视化各种音频信号。 通过利用各种技术来处理语音信号,我们将学习如何构建语音识别系统。 + +在本章结束时,您将了解更多有关: + +* 处理语音信号 +* 可视化音频信号 +* 将音频信号转换到频域 +* 产生音频信号 +* 合成音 +* 提取语音特征 +* 识别口语 + +我们将从讨论如何使用语音信号开始。 + +# 处理语音信号 + +语音识别是理解人类说出的单词的过程。 使用麦克风捕获语音信号,系统尝试理解正在捕获的单词。 语音识别广泛用于人机交互,智能手机,语音转录,生物识别系统,安全性等。 + +在分析语音信号之前,了解其本质非常重要。 这些信号恰好是各种信号的复杂混合。 语音的许多不同方面都会导致其复杂性。 它们包括情感,口音,语言和噪音。 + +由于这种复杂性,很难定义一套可靠的规则来分析语音信号。 相反,即使语音可以有很多变化,人类在理解语音方面也很出色。 人类似乎相对容易做到这一点。 为了使机器做到相同,我们需要帮助他们以与人类相同的方式理解语音。 + +研究人员致力于语音的各个方面和应用,例如理解口语,识别说话者是谁,识别情绪以及识别口音。 在本章中,我们将着重于理解口语。 语音识别代表了人机交互领域的重要一步。 如果我们想构建可以与人类互动的认知机器人,那么他们需要以自然语言与我们对话。 这就是近年来自动语音识别成为许多研究人员关注的焦点的原因。 让我们继续,看看如何处理第个语音信号并构建语音识别 r。 + +# 可视化音频信号 + +让我们看看如何可视化音频信号。 我们将学习如何从文件中读取音频信号并进行处理。 这将帮助我们了解音频信号的结构。 使用麦克风录制音频文件时,它们会采样实际的音频信号并存储数字化版本。 真实的音频信号是连续的值波,这意味着我们无法按原样存储它们。 我们需要以一定频率对信号进行采样并将其转换为离散的数值形式。 + +最常见的是,语音信号以 44,100 Hz 采样。 这意味着语音信号的每一秒被分解成 44,100 个部分,并且这些时间戳中每个时间戳的值都存储在输出文件中。 我们每 1 / 44,100 秒保存一次音频信号的值。 在这种情况下,我们说音频信号的采样频率为 44,100 Hz。 通过选择高采样频率,当人们听音频信号时,它似乎是连续的。 让我们继续进行可视化音频标记 al。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from scipy.io import wavfile +``` + +使用`wavefile.read`方法读取输入的音频文件。 它返回两个值–采样频率和音频信号: + +```py +# Read the audio file +sampling_freq, signal = wavfile.read('random_sound.wav') +``` + +打印信号的形状,数据类型和音频信号的持续时间: + +```py +# Display the params +print('\nSignal shape:', signal.shape) +print('Datatype:', signal.dtype) +print('Signal duration:', round(signal.shape[0] / float(sampling_freq), 2), 'seconds') +``` + +标准化信号: + +```py +# Normalize the signal +signal = signal / np.power(2, 15) +``` + +从`numpy`数组中提取第一个`50`值进行绘图: + +```py +# Extract the first 50 values +signal = signal[:50] +``` + +以秒为单位构造时间轴: + +```py +# Construct the time axis in milliseconds +time_axis = 1000 * np.arange(0, len(signal), 1) / float(sampling_freq) +``` + +绘制音频信号: + +```py +# Plot the audio signal +plt.plot(time_axis, signal, color='black') +plt.xlabel('Time (milliseconds)') +plt.ylabel('Amplitude') +plt.title('Input audio signal') +plt.show() +``` + +完整代码在文件`audio_plotter.py`中给出。 如果运行代码,您将看到以下屏幕截图: + +![](img/B15441_14_01.png) + +图 1:输入音频信号的可视化 + +前面的屏幕截图显示了输入音频信号的前 50 个样本。 您将看到以下输出: + +![](img/B15441_14_02.png) + +图 2:输入音频信号输出 + +上图中打印的输出显示了我们从符号 al 中提取的信息。 + +# 将音频信号转换到频域 + +为了分析音频信号,我们需要了解基本的频率成分。 这使我们能够洞悉如何从该信号中提取有意义的信息。 音频信号由频率,相位和幅度不同的正弦波混合而成。 + +如果我们剖析频率分量,我们可以识别很多特征。 任何给定的音频信号的特征在于其在频谱中的分布。 为了将时域信号转换为频域,我们需要使用数学工具,例如**傅里叶变换**。 如果您需要快速学习傅立叶变换,请查看以下链接: [http://www.thefouriertransform.com](http://www.thefouriertransform.com) 。 让我们看看如何将音频信号从时域转换到频域。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from scipy.io import wavfile +``` + +使用`wavefile.read`方法读取输入的音频文件。 它返回两个值–采样频率和音频信号: + +```py +# Read the audio file +sampling_freq, signal = wavfile.read('spoken_word.wav') +``` + +标准化音频信号: + +```py +# Normalize the values +signal = signal / np.power(2, 15) +``` + +提取信号的长度和一半长度: + +```py +# Extract the length of the audio signal +len_signal = len(signal) +``` + +```py +# Extract the half length +len_half = np.ceil((len_signal + 1) / 2.0).astype(np.int) +``` + +对信号应用傅立叶变换: + +```py +# Apply Fourier transform +freq_signal = np.fft.fft(signal) +``` + +归一化频域信号并取平方: + +```py +# Normalization +freq_signal = abs(freq_signal[0:len_half]) / len_signal +``` + +```py +# Take the square +freq_signal **= 2 +``` + +调整偶数和奇数情况下的傅立叶变换信号: + +```py +# Extract the length of the frequency transformed signal +len_fts = len(freq_signal) +``` + +```py +# Adjust the signal for even and odd cases +if len_signal % 2: + freq_signal[1:len_fts] *= 2 +``` + +```py +else: + freq_signal[1:len_fts-1] *= 2 +``` + +在`dB`中提取电源信号: + +```py +# Extract the power value in dB +signal_power = 10 * np.log10(freq_signal) +``` + +构建 *X* 轴,在这种情况下,是在`kHz`中测量的频率: + +```py +# Build the X axis +x_axis = np.arange(0, len_half, 1) * (sampling_freq / len_signal) / 1000.0 +``` + +绘制图: + +```py +# Plot the figure +plt.figure() +plt.plot(x_axis, signal_power, color='black') +plt.xlabel('Frequency (kHz)') +plt.ylabel('Signal power (dB)') +plt.show() +``` + +完整代码在文件`frequency_transformer.py`中给出。 如果运行代码,您将看到以下屏幕截图: + +![](img/B15441_14_03.png) + +图 3:音频信号转换的可视化 + +前面的屏幕截图显示了信号在整个频谱上的强大程度。 在这种情况下,信号的功率会以较高的频率下降。 + +# 生成音频信号 + +现在我们知道音频信号是如何工作的,让我们看看如何生成一个这样的信号。 我们可以使用 NumPy 包生成各种音频信号。 由于音频信号是**正弦波**的混合,我们可以使用它来生成具有一些预定义参数的音频信号。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from scipy.io.wavfile import write +``` + +定义输出音频文件的名称: + +```py +# Output file where the audio will be saved +output_file = 'generated_audio.wav' +``` + +指定音频参数,例如持续时间,采样频率,音频频率,最小值和最大值: + +```py +# Specify audio parameters +duration = 4 # in seconds +sampling_freq = 44100 # in Hz +tone_freq = 784 +min_val = -4 * np.pi +max_val = 4 * np.pi +``` + +使用定义的参数生成音频信号: + +```py +# Generate the audio signal +t = np.linspace(min_val, max_val, duration * sampling_freq) +signal = np.sin(2 * np.pi * tone_freq * t) +``` + +给信号增加一些噪声: + +```py +# Add some noise to the signal +noise = 0.5 * np.random.rand(duration * sampling_freq) +signal += noise +``` + +归一化并缩放信号: + +```py +# Scale it to 16-bit integer values +scaling_factor = np.power(2, 15) - 1 +signal_normalized = signal / np.max(np.abs(signal)) +signal_scaled = np.int16(signal_normalized * scaling_factor) +``` + +将生成的音频信号保存在输出文件中: + +```py +# Save the audio signal in the output file +write(output_file, sampling_freq, signal_scaled) +``` + +提取第一个`200`值进行绘图: + +```py +# Extract the first 200 values from the audio signal +signal = signal[:200] +``` + +构造时间轴(以毫秒为单位): + +```py +# Construct the time axis in milliseconds +time_axis = 1000 * np.arange(0, len(signal), 1) / float(sampling_freq) +``` + +绘制音频信号: + +```py +# Plot the audio signal +plt.plot(time_axis, signal, color='black') +plt.xlabel('Time (milliseconds)') +plt.ylabel('Amplitude') +plt.title('Generated audio signal') +plt.show() +``` + +完整代码在文件`audio_generator.py`中给出。 如果运行代码,您将看到以下屏幕截图: + +![](img/B15441_14_04.png) + +图 4:音频信号生成的可视化 + +使用媒体播放器播放文件`generated_audio.wav`,以查看的外观。 这将是 *784 Hz* 信号和 n oise 信号的混合信号。 + +# 合成音调以产生音乐 + +前面的部分介绍了如何生成简单的单调,但意义不大。 它只是信号中的单个频率。 让我们使用该原理通过将不同的音调拼接在一起来合成音乐。 我们将使用诸如 *A* , *C* , *G* 和 *F* 之类的标准音来生成音乐。 为了查看这些标准音调的频率映射,请查看以下链接: [http://www.phy.mtu.edu/~suits/notefreqs.html](http://www.phy.mtu.edu/~suits/notefreqs.html) 。 + +让我们使用此信息来生成音乐信号。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import json +import numpy as np +import matplotlib.pyplot as plt +from scipy.io.wavfile import write +``` + +定义一个函数以根据输入参数生成音调: + +```py +# Synthesize the tone based on the input parameters +def tone_synthesizer(freq, duration, amplitude=1.0, sampling_freq=44100): + # Construct the time axis + time_axis = np.linspace(0, duration, duration * sampling_freq) +``` + +使用指定的参数构造音频信号并返回: + +```py + # Construct the audio signal + + signal = amplitude * np.sin(2 * np.pi * freq * time_axis) + return signal.astype(np.int16) +``` + +定义`main`功能。 让我们定义输出音频文件名: + +```py +if __name__=='__main__': + # Names of output files + file_tone_single = 'generated_tone_single.wav' + file_tone_sequence = 'generated_tone_sequence.wav' +``` + +我们将使用一个音调映射文件,其中包含从音调名称(例如 *A* , *C* 和 *G* )到相应频率的映射: + +```py + # Source: http://www.phy.mtu.edu/~suits/notefreqs.html + mapping_file = 'tone_mapping.json' +``` + +```py + # Load the tone to frequency map from the mapping file + with open(mapping_file, 'r') as f: + tone_map = json.loads(f.read()) +``` + +让我们生成持续时间为`3`秒的`F`音调: + +```py + # Set input parameters to generate 'F' tone + tone_name = 'F' + # seconds + duration = 3 + # amplitude + amplitude = 12000 + # Hz + sampling_freq = 44100 +``` + +提取相应的音频频率: + +```py + # Extract the tone frequency + tone_freq = tone_map[tone_name] +``` + +使用前面定义的音调合成器功能生成音调: + +```py + # Generate the tone using the above parameters + synthesized_tone = tone_synthesizer(tone_freq, duration, amplitude, sampling_freq) +``` + +将生成的音频信号写入输出文件: + +```py + # Write the audio signal to the output file + write(file_tone_single, sampling_freq, synthesized_tone) +``` + +让我们生成一个音调序列,使其听起来像音乐。 让我们定义一个音调序列,其持续时间以秒为单位: + +```py + # Define the tone sequence along with corresponding + # durations in seconds + tone_sequence = [('G', 0.4), ('D', 0.5), ('F', 0.3), ('C', 0.6), ('A', 0.4)] +``` + +根据音调序列构造音频信号: + +```py + # Construct the audio signal based on the above sequence + signal = np.array([]) + for item in tone_sequence: + # Get the name of the tone + tone_name = item[0] +``` + +对于每个音调,提取相应的频率: + +```py + # Extract the corresponding frequency of the tone + freq = tone_map[tone_name] +``` + +提取相应的持续时间: + +```py + # Extract the duration + duration = item[1] +``` + +使用`tone_synthesizer`功能合成音色: + +```py + # Synthesize the tone + synthesized_tone = tone_synthesizer(freq, duration, amplitude, sampling_freq) +``` + +将其附加到主输出信号: + +```py + # Append the output signal + signal = np.append(signal, synthesized_tone, axis=0) +``` + +将主输出信号保存到输出文件: + +```py + # Save the audio in the output file + write(file_tone_sequence, sampling_freq, signal) +``` + +完整代码为文件`synthesizer.py`中提供的。 如果运行代码,它将生成两个输出文件-`generated_tone_single.wav`和`generated_tone_sequence.wav`。 + +您可以使用媒体播放器播放音频文件,以听一下的声音。 + +# 提取语音特征 + +我们学习了如何将时域信号转换为频域。 频域功能已在所有语音识别系统中广泛使用。 我们之前讨论的概念是对该概念的介绍,但实际的频域功能要复杂一些。 将信号转换到频域后,我们需要确保以特征向量的形式使用该信号。 这就是 **Mel 频率倒谱系数**( **MFCC** )的概念。 MFCC 是工具,用于从 g 偶数音频信号中提取频域特征。 + +为了从音频信号中提取频率特征,MFCC 首先提取功率谱。 然后,它使用滤波器组和**离散余弦变换**( **DCT** )提取特征。 如果您有兴趣进一步研究 MFCC,请查看以下链接: + +[http://practicalcryptography.com/miscellaneous/machine-learning/guide-mel-frequency-cepstral-coefficients-mfccs](http://practicalcryptography.com/miscellaneous/machine-learning/guide-mel-frequency-cepstral-coefficients-mfccs ) + +我们将使用一个名为`python_speech_features`的程序包来提取 MFCC 功能。 该软件包在这里可用: + +[http://python-speech-features.readthedocs.org/en/latest](http://python-speech-features.readthedocs.org/en/latest ) + +为了易于使用,相关的文件夹已包含在代码包中。 您将在代码包中看到一个名为`features`的文件夹,其中包含使用此软件包所需的文件。 让我们看看如何提取 MFCC 功能。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +from scipy.io import wavfile +from python_speech_features import mfcc, logfbank +``` + +读取输入的音频文件并提取第一个`10,000`样本进行分析: + +```py +# Read the input audio file +sampling_freq, signal = wavfile.read('random_sound.wav') +``` + +```py +# Take the first 10,000 samples for analysis +signal = signal[:10000] +``` + +提取 MFCC: + +```py +# Extract the MFCC features +features_mfcc = mfcc(signal, sampling_freq) +``` + +打印 MFCC 参数: + +```py +# Print the parameters for MFCC +print('\nMFCC:\nNumber of windows =', features_mfcc.shape[0]) +print('Length of each feature =', features_mfcc.shape[1]) +``` + +绘制 MFCC 功能: + +```py +# Plot the features +features_mfcc = features_mfcc.T +plt.matshow(features_mfcc) +plt.title('MFCC') +``` + +提取滤波器组特征: + +```py +# Extract the Filter Bank features +features_fb = logfbank(signal, sampling_freq) +``` + +打印滤波器组的参数: + +```py +# Print the parameters for Filter Bank +print('\nFilter bank:\nNumber of windows =', features_fb.shape[0]) +print('Length of each feature =', features_fb.shape[1]) +``` + +绘制特征: + +```py +# Plot the features +features_fb = features_fb.T +plt.matshow(features_fb) +plt.title('Filter bank') +``` + +```py +plt.show() +``` + +完整代码在文件`feature_extractor.py`中给出。 如果运行代码,您将看到两个屏幕截图。 第一个屏幕截图显示了 MFCC 功能: + +![](img/B15441_14_05.png) + +图 5:MFCC 功能图 + +第二张屏幕截图显示了滤波器组功能: + +![](img/B15441_14_06.png) + +图 6:滤波器组功能图 + +您将看到以下打印出来: + +![](img/B15441_14_07.png) + +图 7:MFCC 和滤波器组功能输出 + +正如我们在上一张图表中所看到的以及在本章中学到的那样,将声音转换为图片可能非常有用,并且它可以让我们以不同的方式分析声音并得出我们有见识的见解 否则会错过 e。 + +# 识别口语 + +现在已经学习了分析语音信号的所有技术,让我们继续学习如何识别语音。 语音识别系统将音频信号作为输入并识别正在说的单词。 **隐藏的马尔可夫模型**( **HMM** )将用于此任务。 + +正如我们在上一章中讨论的那样,HMM 非常适合分析顺序数据。 音频信号是时间序列信号,是顺序数据的体现。 假定输出是由系统经过一系列隐藏状态生成的。 我们的目标是找出这些隐藏状态是什么,以便我们可以识别信号中的单词。 如果您有兴趣深入研究,请查看以下链接: [https://web.stanford.edu/~jurafsky/slp3/A.pdf](https://web.stanford.edu/~jurafsky/slp3/A.pdf) 。 + +我们将使用名为`hmmlearn`的程序包来构建我们的语音识别系统。 您可以在此处了解更多信息: [http://hmmlearn.readthedocs.org/en/latest](http://hmmlearn.readthedocs.org/en/latest) 。 + +您可以通过运行以下命令来安装软件包: + +```py +$ pip3 install hmmlearn +``` + +为了训练我们的语音识别系统,我们需要每个单词的音频文件数据集。 我们将使用 [https://code.google.com/archive/p/hmm-speech-recognition/downloads](https://code.google.com/archive/p/hmm-speech-recognition/downloads) 上可用的数据库。 + +为了易于使用,在代码包中为您提供了一个名为`data`的文件夹,其中包含所有这些文件。 该数据集包含七个不同的单词。 每个词都有一个与之关联的文件夹,每个文件夹有 15 个音频文件。 + +在每个文件夹中,我们将使用 14 个进行培训,使用 1 个进行测试。 请注意,这实际上是一个非常小的数据集。 在现实世界中,您将使用更大的数据集来构建语音识别系统。 我们正在使用该数据集来熟悉语音识别,并了解如何构建一个系统来识别语音。 + +我们将为每个单词建立一个 HMM 模型,并存储所有模型以供参考。 当我们想识别未知音频文件中的单词时,我们将在所有这些模型中运行该单词,并选择得分最高的单词。 让我们看看如何建立这个系统。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import os +import argparse +import warnings +``` + +```py +import numpy as np +from scipy.io import wavfile +``` + +```py +from hmmlearn import hmm +from python_speech_features import mfcc +``` + +定义函数解析输入参数。 我们需要指定输入文件夹,其中包含训练语音识别系统所需的音频文件: + +```py +# Define a function to parse the input arguments +def build_arg_parser(): + parser = argparse.ArgumentParser(description='Trains the HMM-based speech recognition system') + parser.add_argument("--input-folder", dest="input_folder", required=True, help="Input folder containing the audio files for training") + return parser +``` + +定义一个训练 HMM 的类: + +```py +# Define a class to train the HMM +class ModelHMM(object): + def __init__(self, num_components=4, num_iter=1000): + self.n_components = num_components + self.n_iter = num_iter +``` + +定义协方差类型和 HMM 类型: + +```py + self.cov_type = 'diag' + self.model_name = 'GaussianHMM' +``` + +初始化变量,我们将在其中存储每个单词的模型: + +```py + self.models = [] +``` + +使用指定的参数定义模型: + +```py + self.model = hmm.GaussianHMM(n_components=self.n_components, + covariance_type=self.cov_type, n_iter=self.n_iter) +``` + +定义训练模型的方法: + +```py + # 'training_data' is a 2D numpy array where each row is 13-dimensional + def train(self, training_data): + np.seterr(all='ignore') + cur_model = self.model.fit(training_data) + self.models.append(cur_model) +``` + +定义一种方法来计算输入数据的分数: + +```py + # Run the HMM model for inference on input data + def compute_score(self, input_data): + return self.model.score(input_data) +``` + +定义一个函数为训练数据集中的每个单词构建模型: + +```py +# Define a function to build a model for each word +def build_models(input_folder): + # Initialize the variable to store all the models + speech_models = [] +``` + +解析输入目录: + +```py + # Parse the input directory + for dirname in os.listdir(input_folder): + # Get the name of the subfolder + subfolder = os.path.join(input_folder, dirname) + if not os.path.isdir(subfolder): + continue +``` + +提取标签: + +```py + # Extract the label + label = subfolder[subfolder.rfind('/') + 1:] +``` + +初始化变量以存储训练数据: + +```py + # Initialize the variables + X = np.array([]) +``` + +创建用于训练的文件列表: + +```py + # Create a list of files to be used for training + # We will leave one file per folder for testing + training_files = [x for x in os.listdir(subfolder) if x.endswith('.wav')][:-1] +``` + +```py + # Iterate through the training files and build the models + for filename in training_files: + # Extract the current filepath + filepath = os.path.join(subfolder, filename) +``` + +从当前文件读取音频信号: + +```py + # Read the audio signal from the input file + sampling_freq, signal = wavfile.read(filepath) +``` + +提取 MFCC 功能: + +```py + # Extract the MFCC features + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + features_mfcc = mfcc(signal, sampling_freq) +``` + +将数据点附加到变量`X`: + +```py + # Append to the variable X + if len(X) == 0: + X = features_mfcc + else: + X = np.append(X, features_mfcc, axis=0) +``` + +初始化 HMM 模型: + +```py + # Create the HMM model + model = ModelHMM() +``` + +使用训练数据训练模型: + +```py + # Train the HMM + model.train(X) +``` + +将模型保存为当前单词: + +```py + # Save the model for the current word + speech_models.append((model, label)) +``` + +```py + # Reset the variable + model = None +``` + +```py + return speech_models +``` + +定义一个函数以在测试数据集上运行测试: + +```py +# Define a function to run tests on input files +def run_tests(test_files): + # Classify input data + for test_file in test_files: + # Read input file + sampling_freq, signal = wavfile.read(test_file) +``` + +提取 MFCC 功能: + +```py + # Extract MFCC features + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + features_mfcc = mfcc(signal, sampling_freq) +``` + +定义变量以存储最高分数和输出标签: + +```py + # Define variables + max_score = -float('inf') + output_label = None +``` + +遍历每种模型以选择最佳模型: + +```py + # Run the current feature vector through all the HMM + # models and pick the one with the highest score + for item in speech_models: + model, label = item +``` + +评估分数并与最高分数进行比较: + +```py + score = model.compute_score(features_mfcc) + if score > max_score: + max_score = score + predicted_label = label +``` + +打印输出: + +```py + # Print the predicted output + start_index = test_file.find('/') + 1 + end_index = test_file.rfind('/') + original_label = test_file[start_index:end_index] + print('\nOriginal: ', original_label) + print('Predicted:', predicted_label) +``` + +定义`main`函数并从输入参数获取输入文件夹: + +```py +if __name__=='__main__': + args = build_arg_parser().parse_args() + input_folder = args.input_folder +``` + +为输入文件夹中的每个单词建立一个 HMM 模型: + +```py + # Build an HMM model for each word + speech_models = build_models(input_folder) +``` + +我们在每个文件夹中保留了一个文件进行测试。 使用该文件来查看模型的准确性: + +```py + # Test files -- the 15th file in each subfolder + test_files = [] + for root, dirs, files in os.walk(input_folder): + for filename in (x for x in files if '15' in x): + filepath = os.path.join(root, filename) + test_files.append(filepath) +``` + +```py + run_tests(test_files) +``` + +完整代码在文件`speech_recognizer.py`中给出。 确保`data`文件夹与代码文件位于同一文件夹中。 运行代码,如下所示: + +```py +$ python3 speech_recognizer.py --input-folder data +``` + +如果运行代码,将看到以下输出: + +![](img/B15441_14_08.png) + +图 8:识别的单词输出 + +正如我们在前面的编辑屏幕截图中所见一样,我们的语音识别系统可以正确识别的所有单词。 + +# 摘要 + +在本章中,我们学习了语音识别。 我们讨论了如何使用语音信号和相关概念。 我们学习了如何可视化音频信号。 我们讨论了如何使用傅立叶变换将时域音频信号转换为频域。 我们讨论了如何使用预定义的参数生成音频信号。 + +然后,我们使用此概念通过将音调缝合在一起来合成音乐。 我们讨论了 MFCC 及其在现实世界中的使用方式。 我们了解了如何从语音中提取频率特征。 我们学习了如何使用所有这些技术来构建语音识别系统。 在下一章中,我们将讨论自然语言处理以及如何通过 m 编码并对其进行分类来分析文本数据。 \ No newline at end of file diff --git a/docs/ai-py/15.md b/docs/ai-py/15.md new file mode 100644 index 0000000000000000000000000000000000000000..257cad4204592170f6d23bae74fddbcbeb297079 --- /dev/null +++ b/docs/ai-py/15.md @@ -0,0 +1,963 @@ +# 15 + +# 自然语言处理 + +在本章中,我们将学习自然语言处理(NLP)的令人兴奋的主题。 正如我们在前几章中所讨论的,拥有能够理解人类语言的计算机是真正使计算机变得更加有用的突破之一。 NLP 为开始了解如何实现提供了基础。 + +我们将讨论并使用各种概念(例如标记化,词干和词形化)来处理文本。 然后,我们将讨论*单词袋*模型以及如何使用它对文本进行分类。 我们将看到如何使用机器学习来分析句子的情感。 然后,我们将讨论主题建模并实现一个系统来识别给定文档中的主题。 + +在本章结束时,您将熟悉以下主题: + +* 安装相关的 NLP 软件包 +* 标记文本数据 +* 使用词干将单词转换为其基本形式 +* 使用词义化将单词转换为其基本形式 +* 将文本数据分成大块 +* 使用单词袋模型提取文档项矩阵 +* 建立类别预测器 +* 构造性别标识符 +* 建立情绪分析器 +* 使用潜在 Dirichlet 分配的主题建模 + +# 软件包的介绍和安装 + +**自然语言处理**( **NLP** )已成为现代系统的重要组成部分。 它广泛用于搜索引擎,会话界面,文档处理器等。 机器可以很好地处理结构化数据,但是在处理自由格式的文本时,它们会遇到困难。 NLP 的目标是开发使计算机能够理解自由格式文本并帮助他们理解语言的算法。 + +自由处理-形式自然语言最具挑战性的之一是数量众多。 上下文在句子的理解中起着非常重要的作用。 人类天生善于理解语言。 尚不清楚人类如何轻松而直观地理解语言。 我们利用我们过去的知识和经验来理解对话,即使在几乎没有明确上下文的情况下,我们也可以迅速了解其他人在谈论什么。 + +为了解决这个问题,NLP 研究人员开始使用机器学习方法来开发各种应用程序。 为了构建这样的应用程序,需要获取大量的文本,然后在该数据上训练算法以执行各种任务,例如对文本进行分类,分析情感和对主题进行建模。 对算法进行训练,以检测输入文本数据中的模式并从中获取见解。 + +在本章中,我们将讨论用于分析文本和构建 NLP 应用程序的各种基础概念。 这将使我们了解如何从给定的文本数据中提取有意义的信息。 我们将使用称为**自然语言工具包**( **NLTK** )的 Python 软件包来构建这些应用程序。 您可以通过运行以下命令来安装它: + +```py +$ pip3 install nltk +``` + +您可以在 [http://www.nltk.org](http://www.nltk.org) 上找到有关 NLTK 的更多信息。 + +为了访问 NLTK 提供的所​​有数据集,我们需要下载它。 通过键入以下内容打开 Python Shell: + +```py +$ python3 +``` + +现在,我们位于 Python Shell 中。 输入以下内容以下载数据: + +```py +>>> import nltk +>>> nltk.download() +``` + +在本章中,我们还将使用名为`gensim`的软件包。 `gensim`是健壮的语义建模库,对许多应用程序都非常有用。 可以通过运行以下命令来安装它: + +```py +$ pip3 install gensim +``` + +您可能需要另一个软件包`pattern`才能使`gensim`正常运行。 您可以通过运行以下命令来安装它: + +```py +$ pip3 install pattern +``` + +您可以在 [https://radimrehurek.com/gensim](https://radimrehurek.com/gensim) 上找到有关 gensim 的更多信息。 现在,您已经安装了 NLTK 和`gensim`,让我们继续进行 Discu 会话。 + +# 标记文本数据 + +当我们处理文本时,我们需要将其分解成较小的部分进行分析。 为此,可以应用标记化。 标记化是将文本分为一组片段的过程,例如单词或句子。 这些片段称为令牌。 根据我们要执行的操作,我们可以定义自己的方法以将文本分为许多标记。 让我们看一下如何使用 NLTK 标记输入文本。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +from nltk.tokenize import sent_tokenize, \ + word_tokenize, WordPunctTokenizer +``` + +定义将用于标记化的输入文本: + +```py +# Define input text +input_text = "Do you know how tokenization works? It's actually \ + quite interesting! Let's analyze a couple of sentences and \ + figure it out." +``` + +将输入文本分为句子标记: + +```py +# Sentence tokenizer +print("\nSentence tokenizer:") +print(sent_tokenize(input_text)) +``` + +将输入文本分为单词标记: + +```py +# Word tokenizer +print("\nWord tokenizer:") +print(word_tokenize(input_text)) +``` + +使用`WordPunct`标记程序将输入文本分为单词标记: + +```py +# WordPunct tokenizer +print("\nWord punct tokenizer:") +print(WordPunctTokenizer().tokenize(input_text)) +``` + +完整代码在文件`tokenizer.py`中给出。 如果运行代码,将得到以下输出: + +![](img/B15441_15_01.png) + +图 1:分词器输出 + +句子标记程序将输入文本分为句子。 当涉及标点时,两个单词的分词器的行为会有所不同。 例如,单词'It's'由点词标记器划分的方式与常规 tok 分配器的划分方式不同。 + +# 使用词干将单词转换为其基本形式 + +使用文本意味着需要进行很多变化。 我们必须处理同一个单词的不同形式,并使计算机理解这些不同的单词具有相同的基本形式。 例如,单词`sing`可以以多种形式出现,例如*歌手*,*唱歌*,*歌曲,演唱的*等。 这组单词具有相似的含义。 此过程称为“阻止”。 词干是产生词根/基词形态变异的一种方式。 人类可以轻松地识别这些基本形式并得出上下文。 + +分析文本时,提取这些基本形式很有用。 这样做可以提取从输入文本中导出的有用统计信息。 阻止是实现此目的的一种方法。 词干分析器的目标是将单词从其不同形式简化为通用的基本形式。 基本上,这是一种启发式过程,可切断单词的结尾以提取其基本形式。 让我们看看如何使用 NLTK 做到这一点。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +from nltk.stem.porter import PorterStemmer +from nltk.stem.lancaster import LancasterStemmer +from nltk.stem.snowball import SnowballStemmer +``` + +定义一些输入词: + +```py +input_words = ['writing', 'calves', 'be', 'branded', 'horse', 'randomize', + 'possibly', 'provision', 'hospital', 'kept', 'scratchy', 'code'] +``` + +为**搬运工**, **Lancaster** 和 **Snowball** 茎干创建对象: + +```py +# Create various stemmer objects +porter = PorterStemmer() +lancaster = LancasterStemmer() +snowball = SnowballStemmer('english') +``` + +创建用于表显示的名称列表,并相应地格式化输出文本: + +```py +# Create a list of stemmer names for display +stemmer_names = ['PORTER', 'LANCASTER', 'SNOWBALL'] +formatted_text = '{:>16}' * (len(stemmer_names) + 1) +print('\n', formatted_text.format('INPUT WORD', *stemmer_names), + '\n', '='*68) +``` + +遍历单词,并使用以下三个词干器对其进行词干处理: + +```py +# Stem each word and display the output +for word in input_words: + output = [word, porter.stem(word), + lancaster.stem(word), snowball.stem(word)] + print(formatted_text.format(*output)) +``` + +完整代码在文件`stemmer.py`中给出。 如果运行代码,将得到以下输出: + +![](img/B15441_15_02.png) + +图 2:语音输出 + +让我们讨论一下这里使用的三种词干算法。 他们基本上都试图实现相同的目标。 它们之间的区别是用来达到基本形式的严格程度。 + +Porter 词干分析器最不严格,而 Lancaster 最严格。 如果仔细观察输出,您会发现差异。 对于`possibly`或`provision`之类的词,词干的行为有所不同。 从 Lancaster 词干提取器获得的词干输出有些混乱,因为它大大减少了单词数。 同时,该算法速度很快。 有一个很好的经验法则是使用 Snowball 提取器,因为它是速度和 s t 严格性之间的良好折衷。 + +# 使用词形化将单词转换为其基本形式 + +**合法化**是将单词还原为基本形式的另一种方法。 在上一节中,我们看到了从这些词干提取的一些基本形式没有意义。 词法化是将单词的不同变形形式组合在一起的过程,因此可以将它们作为单个项目进行分析。 合法化就像发芽一样,但是却为这些词带来了语境。 因此,它将具有相似含义的单词链接到一个单词。 例如,所有三个词干都说*犊牛*的基本形式是 *calv* ,这不是一个真实的词。 合法化采用一种更加结构化的方法来解决此问题。 以下是一些词素化的示例: + +* 岩石:岩石 +* 身体 +* 更糟:不好 + +词素化过程使用单词的词法和词法分析。 它通过删除诸如*或*或*或*之类的词尾变化来获得基本形式。 任何单词的基本形式都称为引理。 如果对*小牛*一词进行去词化,则应该得到*小牛*作为输出。 需要注意的一件事是,输出取决于单词是动词还是名词。 让我们看看如何使用 NLTK 做到这一点。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +from nltk.stem import WordNetLemmatizer +``` + +定义一些输入词。 我们将使用与上一节相同的一组单词,以便我们可以比较输出: + +```py +input_words = ['writing', 'calves', 'be', 'branded', 'horse', 'randomize', + 'possibly', 'provision', 'hospital', 'kept', 'scratchy', 'code'] +``` + +创建一个`lemmatizer`对象: + +```py +# Create lemmatizer object +lemmatizer = WordNetLemmatizer() +``` + +为表显示创建`lemmatizer`名称的列表,并相应地设置文本格式: + +```py +# Create a list of lemmatizer names for display +lemmatizer_names = ['NOUN LEMMATIZER', 'VERB LEMMATIZER'] +formatted_text = '{:>24}' * (len(lemmatizer_names) + 1) +print('\n', formatted_text.format('INPUT WORD', *lemmatizer_names), + '\n', '='*75) +``` + +遍历单词并使用名词和动词 lemmatizers 对单词进行 lemmatize: + +```py +# Lemmatize each word and display the output +for word in input_words: + output = [word, lemmatizer.lemmatize(word, pos='n'), + lemmatizer.lemmatize(word, pos='v')] + print(formatted_text.format(*output)) +``` + +完整代码在文件`lemmatizer.py`中给出。 如果运行代码,将得到以下输出: + +![](img/B15441_15_03.png) + +图 3:脱胶机输出 + +我们可以看到,名词 lemmatizer 的作用与动词 lemmatizer 的作用不同,例如涉及`writing`或`calves`的单词。 如果将这些输出与词干输出进行比较,则也会看到差异。 lemmatizer 的输出都是有意义的,而词干提取器的输出可能是有意义的 是有意义的。 + +# 将文本数据分为多个块 + +通常需要将文本数据分成多个部分以进行进一步分析。 该处理称为**分块**。 这在文本分析中经常使用。 用于将文本分为几部分的条件会根据当前问题而有所不同。 这与标记化不同,在标记化中,文本也分为几部分。 在分块期间,除了输出分块需要有意义之外,我们没有遵守任何约束条件。 + +当我们处理大型文本文档时,将文本分成大块以提取有意义的信息变得很重要。 在本节中,我们将看到如何将输入文本分为几部分。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +from nltk.corpus import brown +``` + +定义一个函数,将输入文本分为多个块。 第一个参数是文本,第二个参数是每个块中的单词数: + +```py +# Split the input text into chunks, where +# each chunk contains N words +def chunker(input_data, N): + input_words = input_data.split(' ') + output = [] +``` + +遍历单词,并使用输入参数将它们分成大块。 该函数返回一个列表: + +```py + cur_chunk = [] + count = 0 + for word in input_words: + cur_chunk.append(word) + count += 1 + if count == N: + output.append(' '.join(cur_chunk)) + count, cur_chunk = 0, [] + output.append(' '.join(cur_chunk)) + return output +``` + +定义主要功能并使用 Brown 语料库读取输入数据。 在这种情况下,我们将读取`12000`字。 您可以随意阅读任意多个单词: + +```py +if __name__=='__main__': + # Read the first 12000 words from the Brown corpus + input_data = ' '.join(brown.words()[:12000]) +``` + +定义每个块中的单词数: + +```py + # Define the number of words in each chunk + chunk_size = 700 +``` + +将输入文本分成大块并显示输出: + +```py + chunks = chunker(input_data, chunk_size) + print('\nNumber of text chunks =', len(chunks), '\n') + for i, chunk in enumerate(chunks): + print('Chunk', i+1, '==>', chunk[:50]) +``` + +完整代码在文件`text_chunker.py`中给出。 如果运行代码,将得到以下输出: + +![](img/B15441_15_04.png) + +图 4:文本分块器输出 + +上面的屏幕快照显示了每个块的前 50 个字符。 + +现在,我们已经探索了将文本分割和分块的技术,让我们开始研究开始执行 文本分析的方法。 + +# 使用“词袋”模型提取词频 + +单词袋模型的文本分析的主要目标之一是将文本转换为数字形式,以便我们可以在其上使用机器学习。 让我们考虑包含数百万个单词的文本文档。 为了分析这些文档,我们需要提取文本并将其转换为数字表示形式。 + +机器学习算法需要使用数字数据,以便它们可以分析数据并提取有意义的信息。 这就是单词袋模型的用处。该模型从文档中的所有单词中提取词汇,并使用文档术语矩阵构建模型。 这使我们可以将每个文档表示为*单词*袋。 我们只是跟踪字数,而忽略语法细节和字序。 + +让我们看看文档术语矩阵的含义。 文档术语矩阵基本上是一个表,它为我们提供了文档中出现的各种单词的计数。 因此,文本文档可以表示为各个单词的加权组合。 我们可以设置阈值并选择更有意义的词。 在某种程度上,我们正在构建文档中所有单词的直方图,并将其用作特征向量。 该特征向量用于文本分类。 + +考虑以下句子: + +* 句子 1:孩子们在大厅玩耍 +* 句子 2:大厅有很多空间 +* 句子 3:很多孩子喜欢在露天场所玩耍 + +如果您考虑所有三个句子,我们有以下 14 个唯一词: + +* 的 +* 孩子们 +* 是 +* pl aying +* 在 +* 大厅 +* 有 +* 一个 +* 很多 +* 的 +* 空间 +* 喜欢 +* 一个 +* 打开 + +让我们通过使用每个句子中的单词计数为每个句子构造一个直方图。 每个特征向量都是 14 维的,因为我们有 14 个唯一的词: + +* 句子 1:[2,1,1,1,1,1,0,0,0,0,0,0,0,0] +* 句子 2:[1,0,0,0,0,1,1,1,1,1,1,1,0,0] +* 句子 3:[0,1,0,1,1,0,0,0,1,1,1,1,1,1] + +现在,我们已经使用“单词袋”模型提取了这些特征,我们可以使用机器学习算法来分析这些数据。 + +让我们看看如何在 NLTK 中构建“语言袋”模型。 创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +from sklearn.feature_extraction.text import CountVectorizer +from nltk.corpus import brown +from text_chunker import chunker +``` + +从 Brown 语料库读取输入数据。 我们将使用`5,400`字。 随意尝试使用尽可能多的单词: + +```py +# Read the data from the Brown corpus +input_data = ' '.join(brown.words()[:5400]) +``` + +定义每个块中的单词数: + +```py +# Number of words in each chunk +chunk_size = 800 +``` + +将输入文本分成多个块: + +```py +text_chunks = chunker(input_data, chunk_size) +``` + +将这些块转换成字典项: + +```py +# Convert to dict items +chunks = [] +for count, chunk in enumerate(text_chunks): + d = {'index': count, 'text': chunk} + chunks.append(d) +``` + +提取文档术语矩阵,从中获得每个单词的计数。 我们将使用`CountVectorizer`方法来实现这一点,该方法需要两个输入参数。 第一个参数是最小文档频率,第二个参数是最大文档频率。 频率是指单词在文本中出现的次数: + +```py +# Extract the document term matrix +count_vectorizer = CountVectorizer(min_df=7, max_df=20) +document_term_matrix = count_vectorizer.fit_transform([chunk['text'] for chunk in chunks]) +``` + +用单词袋模型提取词汇表并显示它。 词汇表是在上一步中提取的不同单词的列表: + +```py +# Extract the vocabulary and display it +vocabulary = np.array(count_vectorizer.get_feature_names()) +print("\nVocabulary:\n", vocabulary) +``` + +生成显示名称: + +```py +# Generate names for chunks +chunk_names = [] +for i in range(len(text_chunks)): + chunk_names.append('Chunk-' + str(i+1)) +``` + +打印文档术语矩阵: + +```py +# Print the document term matrix +print("\nDocument term matrix:") +formatted_text = '{:>12}' * (len(chunk_names) + 1) +print('\n', formatted_text.format('Word', *chunk_names), '\n') +for word, item in zip(vocabulary, document_term_matrix.T): + # 'item' is a 'csr_matrix' data structure + output = [word] + [str(freq) for freq in item.data] + print(formatted_text.format(*output)) +``` + +完整代码在文件`bag_of_words.py`中给出。 如果运行代码,将得到以下输出: + +![](img/B15441_15_05.png) + +图 5:文档术语矩阵输出 + +可以在单词袋模型文档术语矩阵中看到所有单词以及每个块中相应的自发计数。 + +现在我们已经对单词进行了计数,我们可以在此基础上开始基于单词的频率做出一些预测。 + +# 建立类别预测变量 + +类别预测器用于预测给定文本所属的类别。 这在文本分类中经常用于对文本文档进行分类。 搜索引擎经常使用此工具按相关性对搜索结果进行排序。 例如,假设我们要预测给定的句子是属于体育,政治还是科学。 为此,我们建立了一个数据集并训练了一个算法。 然后可以将该算法用于推断未知数据。 + +为了构建此预测变量,我们将使用一个称为**术语频率-文档反向频率**( **tf-idf** )的度量。 在文档集中,我们需要了解每个单词的重要性。 tf-idf 指标可帮助我们了解给定单词对文档集中的文档 t 的重要性。 + +让我们考虑该指标的的第一部分。 **术语频率**( **tf** )基本上是,用于衡量每个单词在给定文档中出现的频率。 由于不同的文档具有不同的单词数,因此直方图中的确切数字将有所不同。 为了有一个公平的竞争环境,我们需要标准化直方图。 因此,我们将每个单词的计数除以给定文档中的单词总数,以获得词频。 + +度量的第二部分是,**反向文档频率**( **idf** ),它是一个单词在给定文档集中对文档的唯一性的度量。 当我们计算频率一词时,假设所有单词都同等重要。 但是我们不能仅仅依靠每个单词的频率,因为*和*,*或*以及等单词的出现很多。 为了平衡这些常见单词的频率,我们需要减少它们的权重并增加稀有单词的权重。 这也有助于我们识别每个文档唯一的单词,从而帮助我们制定独特的特征向量。 + +要计算此统计信息,我们需要计算具有给定单词的文档数量的比率,并将其除以文档总数。 该比率实质上是包含给定单词的文档的一部分。 然后通过采用该比率的负算法来计算文档的逆向频率。 + +然后,我们将术语频率和文档的逆频率结合起来,以制定特征向量来对文档进行分类。 这项工作是深入分析文本以获得更深层含义的基础,例如情感分析,文本上下文或主题分析。 让我们看看如何构建类别预测器。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +from sklearn.datasets import fetch_20newsgroups +from sklearn.naive_bayes import MultinomialNB +from sklearn.feature_extraction.text import TfidfTransformer +from sklearn.feature_extraction.text import CountVectorizer +``` + +定义将用于培训的类别图。 在这种情况下,我们将使用五个类别。 该字典对象中的键引用 scikit-learn 数据集中的名称: + +```py +# Define the category map +category_map = {'talk.politics.misc': 'Politics', 'rec.autos': 'Autos', + 'rec.sport.hockey': 'Hockey', 'sci.electronics': 'Electronics', + 'sci.med': 'Medicine'} +``` + +使用`fetch_20newsgroups`获取训练数据集: + +```py +# Get the training dataset +training_data = fetch_20newsgroups(subset='train', + categories=category_map.keys(), shuffle=True, random_state=5) +``` + +使用`CountVectorizer`对象提取术语计数: + +```py +# Build a count vectorizer and extract term counts +count_vectorizer = CountVectorizer() +train_tc = count_vectorizer.fit_transform(training_data.data) +print("\nDimensions of training data:", train_tc.shape) +``` + +创建 **tf-idf** 变压器,并使用以下数据对其进行训练: + +```py +# Create the tf-idf transformer +tfidf = TfidfTransformer() +train_tfidf = tfidf.fit_transform(train_tc) +``` + +定义一些将用于测试的样本输入语句: + +```py +# Define test data +input_data = [ + 'You need to be careful with cars when you are driving on slippery roads', + 'A lot of devices can be operated wirelessly', + 'Players need to be careful when they are close to goal posts', + 'Political debates help us understand the perspectives of both sides' +] +``` + +使用训练数据训练多项式贝叶斯分类器: + +```py +# Train a Multinomial Naive Bayes classifier +classifier = MultinomialNB().fit(train_tfidf, training_data.target) +``` + +使用计数矢量化器转换输入数据: + +```py +# Transform input data using count vectorizer +input_tc = count_vectorizer.transform(input_data) +``` + +使用`tf-idf`转换器转换向量化的数据,以便可以在推理模型中运行它: + +```py +# Transform vectorized data using tfidf transformer +input_tfidf = tfidf.transform(input_tc) +``` + +使用`tf-idf`转换后的向量预测输出: + +```py +# Predict the output categories +predictions = classifier.predict(input_tfidf) +``` + +在输入测试数据中打印每个样本的输出类别: + +```py +# Print the outputs +for sent, category in zip(input_data, predictions): + print('\nInput:', sent, '\nPredicted category:', \ + category_map[training_data.target_names[category]]) +``` + +完整代码在文件`category_predictor.py`中给出。 如果运行代码,将得到以下输出: + +![](img/B15441_15_06.png) + +图 6:类别预测器输出 + +我们可以直观地看到预测的类别是正确的。 接下来,我们将看另一种形式的文本分析 sis –性别识别。 + +# 构造性别标识符 + +性别识别是一个有趣的问题,远非一门精确的科学。 我们可以快速想到可以用于男性和女性的名字: + +* 天 +* 天使 +* 林赛 +* 摩根 +* 杰西 +* 克里斯 +* 佩顿 +* 特蕾西 +* 斯泰西 +* 约旦 +* 罗宾 +* 悉尼 + +另外,在诸如美国的异质社会中,将会有许多种族名称不遵循英语规则。 通常,我们可以对各种名称进行有根据的猜测。 在这个简单的示例中,我们将使用启发式方法来构建特征向量,并使用它来训练分类器。 这里将使用的启发式是给定名称的最后 *N* 个字母。 例如,如果名称以*或*结尾,则很可能是女性名称,例如 *Amelia* 或 *Genelia* 。 另一方面,如果名称以 *rk* 结尾,则可能是男性名称,例如 *Mark* 或 *Clark* 。 由于我们不确定要使用的字母的确切数量,因此我们将使用此参数并找出最佳答案。 让我们来看看如何做。 + +创建一个新的 Python 文件,然后导入以下软件包: + +```py +import random +from nltk import NaiveBayesClassifier +from nltk.classify import accuracy as nltk_accuracy +from nltk.corpus import names +``` + +定义一个函数以从输入单词中提取最后的 *N* 个字母: + +```py +# Extract last N letters from the input word +# and that will act as our "feature" +def extract_features(word, N=2): + last_n_letters = word[-N:] + return {'feature': last_n_letters.lower()} +``` + +定义`main`功能并从`scikit-learn`包中提取训练数据。 此数据包含带有标签的男性和女性姓名: + +```py +if __name__=='__main__': + # Create training data using labeled names available in NLTK + male_list = [(name, 'male') for name in names.words('male.txt')] + female_list = [(name, 'female') for name in names.words('female.txt')] + data = (male_list + female_list) +``` + +播种随机数生成器并混洗数据: + +```py + # Seed the random number generator + random.seed(5) + # Shuffle the data + random.shuffle(data) +``` + +创建一些用于测试的样本名称: + +```py +# Create test data +input_names = ['Alexander', 'Danielle', 'David', 'Cheryl'] +``` + +定义将用于培训和测试的数据百分比: + +```py + # Define the number of samples used for train and test + num_train = int(0.8 * len(data)) +``` + +最后的 *N* 个字符将用作预测性别的特征向量。 将该参数更改为,以了解性能如何变化。 在这种情况下,我们将从`1`转到`6`: + +```py + # Iterate through different lengths to compare the accuracy + for i in range(1, 6): + print('\nNumber of end letters:', i) + features = [(extract_features(n, i), gender) for (n, gender) in data] +``` + +将数据分为训练和测试: + +```py + train_data, test_data = features[:num_train], features[num_train:] +``` + +使用训练数据构建一个`NaiveBayesClassifier`: + +```py + classifier = NaiveBayesClassifier.train(train_data) +``` + +使用 NLTK 中可用的内置精度方法来计算分类器的精度: + +```py + # Compute the accuracy of the classifier + accuracy = round(100 * nltk_accuracy(classifier, test_data), 2) + print('Accuracy = ' + str(accuracy) + '%') +``` + +预测输入测试列表中每个名称的输出: + +```py + # Predict outputs for input names using + # the trained classifier model + for name in input_names: + print(name, '==>', classifier.classify(extract_features(name, i))) +``` + +完整代码在文件`gender_identifier.py`中给出。 如果运行代码,您将得到以下输出 t: + +![](img/B15441_15_07.png) + +图 7:性别识别输出 + +前面的屏幕截图显示了的准确性以及测试数据的预测输出。 让我们进一步看看发生了什么: + +![](img/B15441_15_08.png) + +图 8:性别识别输出 + +我们可以看到,准确性在两个字母处达到峰值,然后开始下降。 接下来,我们将研究另一个有趣的问题-肛门,它分析了文本的情感。 + +# 建立情感分析器 + +情感分析是确定一段文本情感的过程。 例如,它可以用于确定电影评论是正面的还是负面的。 这是自然语言处理最流行的应用之一。 我们还可以根据眼前的问题添加更多类别。 可以使用此技术来了解人们对产品,品牌或主题的感觉。 它通常用于分析营销活动,民意调查,社交媒体的存在,电子商务网站上的产品评论等。 让我们看看如何确定电影评论的情感。 + +我们将使用朴素的贝叶斯分类器来构建此情感分析器。 首先,从文本中提取所有唯一的单词。 NLTK 分类器需要将此数据以字典的形式排列,以便可以吸收它。 将文本数据分为训练和测试数据集后,将对朴素贝叶斯分类器进行训练,以将评论分为正面和负面。 然后,可以计算和显示表示正面和负面评论的最有用的信息。 该信息很有趣,因为它显示了正在使用的单词来表示各种反应。 + +让我们看看如何实现这一目标。 首先,创建一个新的 Python 文件并导入以下软件包: + +```py +from nltk.corpus import movie_reviews +from nltk.classify import NaiveBayesClassifier +from nltk.classify.util import accuracy as nltk_accuracy +``` + +定义一个函数,根据输入的单词构造一个字典对象并返回它: + +```py +# Extract features from the input list of words +def extract_features(words): + return dict([(word, True) for word in words]) +``` + +定义`main`功能并加载带有标签的电影评论: + +```py +if __name__=='__main__': + # Load the reviews from the corpus + fileids_pos = movie_reviews.fileids('pos') + fileids_neg = movie_reviews.fileids('neg') +``` + +从电影评论中提取功能,并相应地标记它们: + +```py + # Extract the features from the reviews + features_pos = [(extract_features(movie_reviews.words( + fileids=[f])), 'Positive') for f in fileids_pos] + features_neg = [(extract_features(movie_reviews.words( + fileids=[f])), 'Negative') for f in fileids_neg] +``` + +定义培训和测试之间的区别。 在这种情况下,我们将为培训分配 80%,为测试分配 20%: + +```py + # Define the train and test split (80% and 20%) + threshold = 0.8 + num_pos = int(threshold * len(features_pos)) + num_neg = int(threshold * len(features_neg)) +``` + +分离用于训练和测试的特征向量: + +```py + # Create training and training datasets + features_train = features_pos[:num_pos] + features_neg[:num_neg] + features_test = features_pos[num_pos:] + features_neg[num_neg:] +``` + +打印用于训练和测试的数据点数: + +```py + # Print the number of datapoints used + print('\nNumber of training datapoints:', len(features_train)) + print('Number of test datapoints:', len(features_test)) +``` + +使用训练数据训练`NaiveBayesClassifier`,并使用 NLTK 中内置的精度方法来计算精度: + +```py + # Train a Naive Bayes classifier + classifier = NaiveBayesClassifier.train(features_train) + print('\nAccuracy of the classifier:', nltk_accuracy( + classifier, features_test)) +``` + +打印`N`信息量最高的单词: + +```py + N = 15 + print('\nTop ' + str(N) + ' most informative words:') + for i, item in enumerate(classifier.most_informative_features()): + print(str(i+1) + '. ' + item[0]) + if i == N - 1: + break +``` + +定义用于测试的样本句子: + +```py + # Test input movie reviews + input_reviews = [ + 'The costumes in this movie were great', + 'I think the story was terrible and the characters were very weak', + 'People say that the director of the movie is amazing', + 'This is such an idiotic movie. I will not recommend it to anyone.' + ] +``` + +遍历样本数据并预测输出: + +```py + print("\nMovie review predictions:") + for review in input_reviews: + print("\nReview:", review) +``` + +计算每个类别的概率: + +```py + # Compute the probabilities + probabilities = classifier.prob_classify(extract_features(review.split())) +``` + +在概率中选择最大值: + +```py + # Pick the maximum value + predicted_sentiment = probabilities.max() +``` + +打印预测的输出类别(正面或负面情绪): + +```py + # Print outputs + print("Predicted sentiment:", predicted_sentiment) + print("Probability:", round(probabilities.prob(predicted_sentiment), 2)) +``` + +完整代码在文件`sentiment_analyzer.py`中给出。 如果运行代码,将得到以下输出: + +![](img/B15441_15_09.png) + +图 9:情感分析输出 + +前面的屏幕截图显示了前 15 个最有用的单词。 如果向下滚动,您会看到 : + +![](img/B15441_15_10.png) + +图 10:电影评论情绪输出 + +我们可以直观地看到并验证预测是否正确。 + +在本节中,我们构建了一个复杂的情绪分析器。 我们将继续在 NLP 空间中的旅程,并学习潜在 Dirichlet 分配的基础 ns。 + +# 使用潜在狄利克雷分配的主题建模 + +主题建模是识别文本数据中与主题相对应的模式的过程。 如果文本包含多个主题,则可以使用此技术来识别和分隔输入文本中的那些主题。 该技术可用于发现给定文档集中的隐藏主题结构。 + +主题建模可以帮助我们以最佳方式组织文档,然后可以将其用于分析。 关于主题建模算法要注意的一件事是它们不需要标记的数据。 就像无监督学习一样,它将自行识别模式。 考虑到 Internet 上生成的大量文本数据,主题建模非常重要,因为它可以汇总大量数据,否则这是不可能的。 + +**潜在狄利克雷分配**是一种主题建模技术,其基本概念是文本的给定片段是多个主题的组合。 让我们考虑以下句子:数据可视化是财务分析中的重要工具。 这句话有多个主题,例如数据,可视化和财务。 这种组合有助于识别大型文档中的文本。 它是一个统计模型,试图捕获概念并基于这些概念创建模型。 该模型假定文档是基于这些主题通过随机过程生成的。 主题是固定单词词汇的分布。 让我们看看如何在 Python 中进行主题建模。 + +`gensim`库将在本节中使用。 该库已在本章的第一部分中安装。 在继续操作之前,请确保已安装。 创建一个新的 Python 文件并导入以下软件包: + +```py +from nltk.tokenize import RegexpTokenizer +from nltk.corpus import stopwords +from nltk.stem.snowball import SnowballStemmer +from gensim import models, corpora +``` + +定义一个函数来加载输入数据。 输入文件包含 10 个以行分隔的句子: + +```py +# Load input data +def load_data(input_file): + data = [] + with open(input_file, 'r') as f: + for line in f.readlines(): + data.append(line[:-1]) + return data +``` + +定义一个函数来处理输入文本。 第一步是将其标记化: + +```py +# Processor function for tokenizing, removing stop +# words, and stemming +def process(input_text): + # Create a regular expression tokenizer + tokenizer = RegexpTokenizer(r'\w+') +``` + +然后,我们需要阻止标记化文本: + +```py + # Create a Snowball stemmer + stemmer = SnowballStemmer('english') +``` + +我们需要从输入文本中删除停用词,因为它们不会添加信息。 让我们获取停用词列表: + +```py + # Get the list of stop words + stop_words = stopwords.words('english') +``` + +标记输入字符串: + +```py +# Tokenize the input string +tokens = tokenizer.tokenize(input_text.lower()) +``` + +删除停用词: + +```py + # Remove the stop words + tokens = [x for x in tokens if not x in stop_words] +``` + +阻止标记化的单词并返回列表: + +```py + # Perform stemming on the tokenized words + tokens_stemmed = [stemmer.stem(x) for x in tokens] + return tokens_stemmed +``` + +定义`main`功能并从为您提供的文件`data.txt`中加载输入数据: + +```py +if __name__=='__main__': + # Load input data + data = load_data('data.txt') +``` + +标记文本: + +```py + # Create a list for sentence tokens + tokens = [process(x) for x in data] +``` + +根据带标记的句子创建字典: + +```py + # Create a dictionary based on the sentence tokens + dict_tokens = corpora.Dictionary(tokens) +``` + +使用句子标记创建文档术语矩阵: + +```py + # Create a document-term matrix + doc_term_mat = [dict_tokens.doc2bow(token) for token in tokens] +``` + +我们需要提供主题数作为输入参数。 在这种情况下,我们知道输入文本有两个不同的主题。 让我们指定: + +```py + # Define the number of topics for the LDA model + num_topics = 2 +``` + +生成`LatentDirichlet`模型: + +```py + # Generate the LDA model + ldamodel = models.ldamodel.LdaModel(doc_term_mat, + num_topics=num_topics, id2word=dict_tokens, passes=25) +``` + +打印每个主题的前五个贡献词: + +```py + num_words = 5 + print('\nTop ' + str(num_words) + ' contributing words to each topic:') + for item in ldamodel.print_topics(num_topics=num_topics, num_words=num_words): + print('\nTopic', item[0]) + # Print the contributing words along with + # their relative contributions + list_of_strings = item[1].split(' + ') + for text in list_of_strings: + weight = text.split('*')[0] + word = text.split('*')[1] + print(word, '==>', str(round(float(weight) * 100, 2)) + '%') +``` + +完整代码在文件`topic_modeler.py`中给出。 如果运行代码,将得到以下输出: + +![](img/B15441_15_11.png) + +图 11:主题建模器输出 + +我们可以看到,将数学和历史这两个主题区分开来是相当不错的。 如果您查看这些文本,则可以验证每个句子 i 都是关于数学还是历史的。 + +# 摘要 + +在本章中,我们学习了自然语言处理中的各种基础概念。 我们讨论了标记化以及如何将输入文本分成多个标记。 我们学习了如何使用词干和词形化将单词简化为基本形式。 我们实现了文本分块器,可根据预定义的条件将输入文本分为多个块。 + +我们讨论了*单词袋*模型,并为输入文本建立了文档术语矩阵。 然后,我们学习了如何使用机器学习对文本进行分类。 我们使用启发式方法构造了性别标识符。 我们还使用机器学习来分析电影评论的情绪。 最后,我们讨论了主题建模并实现了一个用于识别给定文档中主题的系统。 + +在下一章中,我们将学习如何使用隐马尔可夫模型和 对顺序数据进行建模,然后使用它们来分析股市数据。 \ No newline at end of file diff --git a/docs/ai-py/16.md b/docs/ai-py/16.md new file mode 100644 index 0000000000000000000000000000000000000000..057b63acd75d376a8e0c5217f2a6965bb379fd68 --- /dev/null +++ b/docs/ai-py/16.md @@ -0,0 +1,527 @@ +# 16 + +# 聊天机器人 + +在本章中,我们将学习聊天机器人。 我们将了解它们是什么以及如何使用它们。 您还将学习如何创建自己的聊天机器人。 我们将涵盖以下主题: + +* 聊天机器人的未来 +* 今天的聊天机器人 +* 基本的聊天机器人概念 +* 流行的聊天机器人平台 +* DialogFlow: + * 设置 DialogFlow + * 使用小部件将聊天机器人集成到网站中 + * 使用 Python 将 chatbot 集成到网站中 + * 在 DialogFlow 中设置 Webhook + * 为意图启用 webhook + * 为意图设定训练短语 + * 设置意图的参数和动作 + * 通过 Webhook 建立履行响应 + * 检查来自 Webhook 的响应 + +我们将首先讨论聊天机器人的未来及其许多潜在应用。 + +# 聊天机器人的未来 + +很难准确预测 AI 在未来几年将如何颠覆我们的社会。 就像核技术已被用于开发核武器和为核电站供能一样,人工智能也可以用于崇高的事业或邪恶的目的。 不难想象,世界各地的军事人员拥有利用人工智能技术的强大武器。 例如,使用当前的“现成”技术,我们可以制造一架无人驾驶飞机,向其提供目标人员的照片,然后让无人驾驶飞机追捕该人,直到将其消灭。 + +即使将该技术用于更具建设性的用例,也很难预测该技术在未来几年中将如何发展。 各种各样的研究在某种程度上预测,由于 AI 推动的生产率提高,整个行业将不再需要过去的工人。 货运和运输业以及呼叫中心业是两个“低落的果实”。 + +过去几年中,语音接口终于突破并渗透到我们的生活中。 Alexa,Siri 和 Google Home 之类的应用程序已开始嵌入我们的生活和文化中。 此外,微信,Facebook Messenger,WhatsApp 和 Slack 等消息传递平台为企业与人互动创造了机会,并有可能通过这些互动获利。 这些消息传递平台变得如此流行和普及,以至于 2019 年,四大服务比四大社交网络平台拥有更多的活跃用户(41 亿对 34 亿)。 + +呼叫中心在过去几年中已经发生了巨大变化。 随着使用聊天机器人,云计算和语音生物识别技术的不断发展,公司可以改善客户服务并以更少的人员处理更多的电话。 + +我们还没有,但是很容易想象,在接下来的 5 或 10 年中,当您致电您的银行时,只有最不常见的情况需要人工干预,并且很大一部分电话会自动处理。 + +这种趋势只会继续加速。 当前,没有人将大多数聊天机器人对话与人类对话混淆。 但是随着它们在未来几年变得更好,它会变得更加自然和流畅。 当我们拨打呼叫中心时,有时至少是,打电话给我们的原因之一是抱怨或发泄,不仅能解决问题。 随着聊天机器人变得越来越好,他们将能够展示出我们所理解的同情和理解。 此外,他们将拥有对您以前所有通话的完全访问权限,并且能够通过记住先前对话的片段来发展历史并建立融洽的关系。 + +例如,聊天机器人很快就可以记住您提到孩子的名字,下次打电话时问您 Bobby 做得如何。 此外,就像现在一样,当您通过网络,电话应用程序等不同渠道与银行进行通信,或者与分行中的某人交谈时,聊天机器人将能够访问通过其他渠道输入的信息并使用 它可以为您提供更好,更快的服务。 再说一次,我们还没有到那儿,但是可能会有一天,我们宁愿打电话给客户服务,而不是使用其他渠道(如在线访问),因为这样会更快更有效。 举例来说,我确实发现自己越来越多地使用 Alexa,并且对她的功能和怪癖也越来越熟悉和熟悉。 我仍在努力使她动摇,但尚未实现。 + +不仅与 Alexa 一起,而且与其他智能家居助手一起,我们许多人使用它们来: + +* 听音乐 +* 设置闹钟 +* 创建购物清单 +* 获取天气预报 +* 房屋周围的控制设备 +* 订购在线商品 +* 预订机票 + +但是这种经验可能会变得更加复杂。 随着它们变得更好,它们将至少在某些方面超越我们。 例如,除非我们以这种方式对其进行编程,否则聊天机器人将永远不会感到沮丧。 + +对于一般的 AI 尤其是聊天机器人,持续不断的发展在伦理上的影响是一个不变的话题。 随着聊天机器人变得越来越好,越来越像人类,当我们与聊天机器人而不是人类聊天时,监管机构可能会迫使企业披露信息。 这可能不是一个坏规则。 但是,我们可能会达到这样的地步:聊天机器人是如此出色,以至于尽管在一开始就进行了披露,但我们很快就会忘记,另一端是一台计算机,而不是理解我们并同情我们的人。 + +Google Duplex 是一个很好的聊天机器人听起来很自然的有力例子。 您应该可以在此处观看它的演示: + +[https://www.youtube.com/watch?v=D5VN56jQMWM](https://www.youtube.com/watch?v=D5VN56jQMWM ) + +顺便说一下,这项技术通常是可用的,如果您有 Android 手机或 iPhone,则应该可以使用它。 + +毫无疑问,聊天机器人将是多产的—在我们的家中,在我们的汽车中,在可穿戴设备中,在呼叫中心以及在电话中。 根据一项估计,聊天机器人的全球市场预计将从 2019 年的 42 亿美元增长到 2024 年的 157 亿美元,年复合增长率为 30.2%。 与其他技术一样,使用该技术成长的年轻人永远不会知道没有聊天机器人为我们服务并改善我们的生活会是什么样子。 + +本节讨论了聊天机器人在未来几年内的外观。 在下一节中,我们将回到现实,并就如何利用现有的聊天机器人技术使用当今可用的工具创建出色的应用程序提供一些建议。 + +# 今天的聊天机器人 + +在的上一节中,我们讨论了随着人工智能技术的发展,未来几年可能实现的目标。 与任何技术一样,我们不应该等到一切都变得完美为止。 在本节以及本章的其余部分,我们将重点介绍当今可行的方法以及使您自己的应用程序尽可能有用和用户友好的最佳实践。 + +为了利用当今可用的现有技术,并且鉴于仍然需要使用域数据和特定意图来对当前的聊天机器人进行专门编程,因此,我们应该谨慎设计一个好的设计和好的计划来对我们的聊天机器人进行编程。 从为聊天机器人明确定义的目标开始,避免尝试提出广泛的解决方案。 当前,与定义为“万事通”的聊天机器人相比,在定义明确且狭窄的域空间中发挥作用的聊天机器人具有更好的表现和有用的机会。 设计用于在在线商务体验期间提供支持的聊天机器人不能用于诊断汽车问题,而必须在该域中重新编程。 将聊天机器人明确地放在特定的目标和空间上,这样很可能会为用户创造更好的体验。 + +为了说明这一点,我将分享一个个人故事。 几年前,我参观了迈阿密的一家餐馆。 如您所知,英语是美国最常见的语言,但在迈阿密却不那么普遍。 我们看了看菜单,点了饮料,然后点了开胃菜。 现在该订购主菜了。 我决定与服务员开始一些闲聊。 我忘记了我的具体问题,但这与“您喜欢在迈阿密生活如何?”类似。 脸上惊慌失措的表情告诉我,他不明白这个问题,无论有多少次,我试图解释,他都不会。 为了使他放松,我切换到西班牙语并完成了我们的小聊天。 + +这里的要点是服务生知道“餐馆英语”以及完成餐厅交易所需的所有短语和互动。 但是其他一切都超出了他的舒适范围。 同样,当我们在以下各节中开发聊天机器人时,只要我们停留在预期的域中,它就可以与我们的用户进行通信。 如果聊天机器人被开发用于预订餐厅,那么如果用户的意图是进行医疗诊断,它将无法提供帮助。 + +今天的聊天机器人仍然有些狭窄。 目前,我们使用 Alexa,Siri 和 Google Home 可以做的事情很多,它们只能帮助我们完成特定的任务。 他们还不能很好地处理某些人类特质,例如同理心,讽刺和批判性思维。 在目前的状态下,聊天机器人将能够以人类为中心的方式帮助我们完成重复性的交易任务。 + +但是,即使我们应该尝试使聊天机器人在域上保持尽可能紧密,但这并不意味着我们不应该在机器人中注入一些“个性”。 Alexa 有时可能会显得厚脸皮和幽默,因此您应该为自己的机器人而努力。 这样可以提高与机器人的互动度。 + +人们在聊天时,通常希望对对话有一定程度的共同兴趣,因此,对话将以这样的方式进行:将存在回答后续问题的答案,以及有助于和促进对话的答案 。 使用一点语将使您的机器人更真实,更有吸引力。 + +在深入研究自己的聊天机器人的设计之前,让我们介绍一些在开发过程中对我们有帮助的基础概念。 + +# Chatbot 概念 + +在开发代码之前,让我们设定一个基准并访问一些与聊天机器人相关的有用定义。 + +**代理** + +代理是一个可以处理所有对话并路由所有必要的操作的系统。 这是一种自然语言理解模块,需要经常接受培训以适应特定于使用的需求。 + +**意图** + +当两个人进行通信时,他们俩都有他们开始进行通信的原因。 这可能很简单,例如与朋友追赶并找出他们在做什么。 + +可能其中一个正在试图出售某物等等。 这些“意图”可分为三大类: + +* **扬声器正在尝试娱乐** –例如,有人告诉您开玩笑。 +* **扬声器正在尝试通知** –有人问几点了,或者温度是多少? 他们收到了答案。 +* **演讲者试图说服**-议程是试图出售一些东西。 + +对于大多数聊天机器人,它们的作用是执行命令和执行任务。 由于的原因,他们需要执行的第一个任务是确定调用他们的人的意图。 意图具有诸如上下文,训练阶段,动作和参数以及响应之类的元素。 + +**上下文** + +上下文是用来给予讨论的连贯性和流畅性,保留了对话中已经使用的关键概念。 + +**实体** + +这基本上是将一组单词归为一个已定义的实体。 例如,笔,铅笔,纸,橡皮和笔记本可以称为文具。 因此,DialogFlow 提供了已经过培训的预建实体,或者我们可以构建自定义实体并对其进行培训。 这有助于减少训练短语的冗余。 + +**集成** + +像 DialogFlow 和 Lex 这样的 Chatbot 平台可以与大多数最受欢迎的对话和消息传递平台集成,例如 Google Assistant,Facebook Messenger,Kik 和 Viber 等。 + +**达成** + +履行是的一项连接服务,可让您根据最终用户的表达来执行操作,并将动态响应发送回用户。 例如,如果用户正在寻找员工详细信息,则您的服务可以从数据库中获取详细信息,并立即对用户结果进行响应。 + +**讲话** + +每当我们与某人进行对话时,使用稍有不同的方式提出相同的问题是完全正常的。 例如,我们可能会问“今天过得怎么样?”的问题,但是有很多方法可以问相同的问题。 示例包括: + +* 告诉我你的一天 +* 你今天过得还好么? +* 你今天过的好吗? +* 工作进展如何? + +人类自然会善于从话语中解释含义,并回答发问者想问的问题而不是他们实际问的问题。 例如,对问题“您过得愉快吗?”的简单解释。 将要求答案为是或否。 但是作为人类,我们有足够的技巧来理解的真正含义可能是“告诉我您的一天”。 + +许多聊天机器人平台都变得越来越好,不再要求我们拼出每一个发音,而是能够进行一些“模糊”匹配,并且不需要为每个单独的组合输入内容。 + +**唤醒词** + +像 Alexa 或 Siri 这样的许多聊天机器人保持休眠状态,直到它们被“唤醒”并准备好接收命令为止。 要唤醒它们,需要一个“唤醒词”。 对于 Alexa,最常用的唤醒词是“ Alexa”。 对于 Siri,默认唤醒字为“ Hey Siri”。 并且,对于星舰企业版,唤醒词是“计算机”。 + +**启动词** + +聊天机器人唤醒后,很多时候我们希望该聊天机器人为我们执行一个动作,因此我们需要“启动”该动作。 一些启动词的示例是: + +* 订购 +* 告诉我 +* 加 +* 打开 + +您可以将启动词视为命令。 + +**广告位值或实体** + +插槽值是字,将被转换为参数。 让我们看几个例子: + +* 订购牛奶 +* 告诉我意大利的首都 +* 将面包添加到购物清单 +* 打开烤箱 + +插槽值带有下划线。 插槽值可以具有插槽类型。 就像参数可以具有参数类型(整数,字符串等)一样。 某些插槽类型是内置的,还可以创建自定义插槽类型。 插槽类型的一些示例是: + +* 国名 +* 电子邮件地址 +* 电话号码 +* 日期 + +一些聊天机器人平台将插槽类型称为实体。 + +**错误计划和默认情况** + +设计良好的聊天机器人应始终优雅地处理无法预料的情况。 当聊天机器人没有针对特定交互的程序化答案时,它应该能够显示默认行为,以尽可能优雅地处理无法预料的情况。 例如,如果聊天机器人的功能是预订美国境内的国内航班,但用户请求飞往加拿大温哥华的包机,则聊天机器人应能够优雅地告诉用户他们仅服务于美国城市,然后再次询问目的地 。 + +**Webhooks** + +Webhook 是,HTTP 推送 API 或 Web 回调。 它也称为反向 API,因为一旦事件发生,它就会将数据从应用程序发送到应用程序使用者。 它消除了消费者不断轮询应用程序的需求。 + +既然我们已经介绍了与聊天机器人更好地协作所需的基本概念,让我们考虑如何创建一个有用的,“结构良好”的聊天机器人。 + +# 一个结构合理的聊天机器人 + +为了使聊天机器人有用并使其高效,它必须具有某些特质。 我们将具有这些特质的聊天机器人称为“结构良好”的聊天机器人。 我们列出并定义以下质量: + +**适应性** + +自适应聊天机器人是一种可以理解并适应收到的所有语音的聊天机器人。 即使对于未明确编程的语音,也应该有一个优美的响应,以使聊天机器人用户重回正轨,或者利用此机会将聚会转移给实时操作员。 + +**个性化设置** + +作为人类,我们喜欢感到与众不同。 我们喜欢听到自己的名字,也喜欢别人记住我们的一些小事(我们孩子的名字,母校等等)。 个性化的聊天机器人会记住以前的交互以及他们收集的有关单个用户的信息。 + +**可用性** + +应该有一个聊天机器人可以帮助用户。 这超出了传统平台的可用性。 当然,我们的聊天机器人应该随时准备提供帮助,并在需要时随时进行访问。 但这也适用于我们能够以多快的速度获取聊天机器人来帮助我们实现意图。 想一想在传统**交互式语音响应**( **IVR** )系统中的导航树,在这些导航树中,我们必须按很多数字,然后他们才知道我们要执行的意图。 此类系统的可用性较低。 + +**相关性** + +与相关的聊天机器人可以使聊天机器人的用户感知到他们确实在进行正常的对话。 + +我们几乎已经准备好继续开发自己的聊天机器人。 但是,在此之前,我们应该考虑主要的聊天机器人平台,这些平台是我们聊天机器人的开发和发行的基础。 + +# Chatbot 平台 + +一些使用最广泛的聊天机器人是由主要供应商(例如 Google,AWS 和 Microsoft)开发的平台实现的。 在为聊天机器人选择技术堆栈时,应仔细考虑他们的服务产品。 这三大供应商均提供可靠且可扩展的云计算服务,这些服务将帮助您根据需要实现和自定义聊天机器人。 到目前为止,可以轻松创建基于文本或语音的机器人的最著名平台如下: + +* DialogFlow(Google,以前为 Api.ai) +* Azure Bot 服务(Microsoft) +* Lex(AWS) +* Wit.ai(Facebook) +* 沃森(IBM) + +当然可以使用此处列出的平台以及其他流行的平台编写功能强大的聊天机器人。 但是我们现在将更深入地研究一个平台,尤其是获得更深入的了解。 在聊天机器人服务中,DialogFlow 对于初学者来说是一个不错的选择。 我们将在下一节中讨论 DialogFlow,并在本章的其余部分中使用该平台进行聊天机器人的开发。 + +# 使用 DialogFlow 创建聊天机器人 + +Google 在机器学习和**自然语言处理**( **NLP** )中有着广泛的研究历史。 这项研究的大部分内容都反映在他们的 DialogFlow 工具中。 DialogFlow 与 Google Cloud 语音转文本 API 以及其他第三方服务(例如 Google Assistant,Amazon Alexa 和 Facebook Messenger)集成在一起。 + +无需编写任何代码即可创建提供很多功能的聊天机器人。 最初,我们将回顾如何使用 **Google Cloud Platform** ( **GCP** )控制台,仅通过即可不使用代码来配置聊天机器人。 在本章的以下各节中,我们将演示如何将 chatbot 与其他服务集成。 本章后面的部分将需要对 Python 语言有基本的了解。 + +**入门步骤** + +1. 使用 Google 帐户在 [https://dialogflow.com](https://dialogflow.com) 免费注册。 +2. 为了使聊天机器人正常工作,您需要接受 DialogFlow 所请求的所有权限。 这样,您就可以管理 GCP 服务中的数据,并与 Google 助手集成。 +3. 您可以通过 [https://console.dialogflow.com](https://console.dialogflow.com) 访问 DialogFlow 控制台。 +4. 最后,通过选择主要语言(以后可以添加其他语言)和 Google Project 标识符来创建新代理。 要启用结算和其他设置,需要使用 Google Cloud Console 中的项目名称。 如果您没有现有项目,请不要担心:创建一个新项目。 + +现在您已经注册并可以使用,让我们开始设置 DialogFlow。 + +## DialogFlow 设置 + +首次登录 DialogFlow 时,将要求您允许某些权限。 建议您允许这些权限。 否则,以下练习将无法正常进行: + +![](img/B15441_16_01.png) + +图 1:Google Cloud Console 访问 DialogFlow 控制台的权限 + +DialogFlow 中的开发利用了我们之前讨论的两个主要概念-意向和上下文。 目的标识用户向聊天机器人发出语音的目的。 上下文使对话变得连贯和流畅。 + +单击**意图**选项卡后,您应该看到如下屏幕: + +![](img/B15441_16_02.png) + +图 2:DialogFlow chatbot 意图创建 + +正如我们先前看到的,聊天机器人开发中的另一个重要概念是插槽类型。 在 DialogFlow 中,我们将插槽类型称为实体。 通过实体,可以识别对话中的常见或重复出现的参数。 实体可以是内置的或定制的。 实体的使用使聊天机器人更具通用性和灵活性。 单击**实体**标签时,您应该看到以下屏幕: + +![](img/B15441_16_03.png) + +图 3:DialogFlow chatbot 实体创建 + +让我们从仅使用意图的基本示例开始。 首先,我们将创建代理,然后通过 DialogFlow 接口定义一些意图。 可以通过编程方式创建这些意图,但是为了使示例保持简单,我们将使用图形化界面来创建意图。 首先,让我们设置后备意图。 如果没有其他意图被调用,则将被调用: + +![](img/B15441_16_04.png) + +图 4:创建 DialogFlow 后备意图 + +正如您在*图 4* 中看到的那样,只需编写**立即尝试**表格即可获得答案。 最初,当尚未创建任何意图时,聊天机器人将使用后备意图。 具有后备意图可防止对话陷入停顿。 + +当我们浏览**默认回退意图**时,我们看到**响应**的完整列表。 如您所见,已经定义了许多响应。 当意图匹配时,聊天机器人引擎将随机选择一个项目作为答案。 + +让我们创建我们的第一个意图。 我们可以使用控制台执行此操作。 确保您还填写了**培训短语**表格。 这些是我们期望用户触发这些意图的句子。 我们在构造这些句子时越精确,越全面,那么聊天机器人在识别意图方面将越成功。 + +现在,我们可以通过插入更多意图为聊天机器人添加更多功能来进行操作。 我们可以使用右侧的帮助程序连续测试我们的聊天机器人。 + +希望很明显,仅使用意图就可以创建功能强大的聊天机器人。 DialogFlow 正在为我们完成大部分繁重的工作。 为了使聊天机器人更加强大,我们可以开始向意图添加上下文。 我们可以通过在从一个意图转到另一个意图的同时添加参数,同时保持对话的上下文,来使我们的聊天机器人更加灵活。 在本教程的下一部分中,我们将看到如何将聊天机器人集成到网站中。 + +## 使用小部件将聊天机器人集成到网站中 + +可以通过两种方法将 DialogFlow 聊天机器人集成到网站中: + +* 通过小部件 +* 使用 Python + +我们将从访问第一种方法开始,这是更简单的方法。 此方法使用 *iframe* 将 DialogFlow 集成到网页中。 要使用此方法,请从左侧菜单中选择 **Integrations** ,并确保已启用 **Web Demo** 。 复制 HTML 代码并将其粘贴到网页中,这样就可以在您的网站上使用聊天机器人了: + +![](img/B15441_16_05.png) + +图 5:使用 iframe 将聊天机器人集成到网站中 + +在下面的部分中,我们将考虑第二种方法-使用 Python。 如我们所见,集成聊天机器人的小部件方法非常简单。 但是,我们几乎无法控制机器人的实施方式。 使用 Python 将聊天机器人集成到站点中,可以在如何部署聊天机器人方面为开发人员提供更多的控制权和灵活性。 + +## 使用 Python 将聊天机器人集成到网站中 + +调用 DialogFlow 聊天机器人的另一种方法是使用 Python。 首先,我们需要安装运行代码所需的软件包要求: + +```py +$ pip3 install DialogFlow +$ pip3 install google-api-core +``` + +该代码初始化一个以意图为输入的客户端会话,最后返回一个响应,即所谓的`fulfillment`,并将相应的置信度作为一个十进制值。 我们要获取答案的句子保存在名为`text_to_be_analyzed`的变量中。 通过添加句子来编辑脚本。 使用 Python,很容易创建更多的自定义逻辑。 对于示例,您可以捕获意图,然后触发自定义操作: + +```py +# Install the following requirements: +# DialogFlow 0.5.1 +# google-api-core 1.4.1 +import DialogFlow +from google.api_core.exceptions import InvalidArgument +PROJECT_ID = 'google-project-id' +LANGUAGE_CODE = 'en-US' +GOOGLE_APPLICATION_CREDENTIALS = 'credentials.json' +SESSION_ID = 'current-user-id' +analyzed_text = "Hi! I'm Billy. I want tacos. Can you help me?" +session_client = DialogFlow.SessionsClient() +session = session_client.session_path(PROJECT_ID, SESSION_ID) +text_input = DialogFlow.types.TextInput(text=analyzed_text, + language_code=LANGUAGE_CODE) +query_input = DialogFlow.types.QueryInput(text=text_input) +try: + response = session_client.detect_intent(session=session, + query_input=query_input) +except InvalidArgument: + raise +print("Query text:", response.query_result.query_text) +print("Detected intent:", + response.query_result.intent.display_name) +print("Detected intent confidence:", + response.query_result.intent_detection_confidence) +print("Fulfillment text:", + response.query_result.fulfillment_text) +``` + +如您所见,该函数需要一个`session_id`。 这是一个标识当前会话的值。 因此,我们建议您使用用户的 ID 使其易于检索。 + +为了使 Python 代码正常工作,需要一个新令牌。 实际上,DialogFlow API 的 2.0 版依赖于身份验证系统,该系统基于与 GCP 服务帐户关联的私钥而不是访问令牌。 使用此过程,可以获取 JSON 格式的私钥。 + +**实现和网络挂钩** + +现在我们已经建立了,如何创建会话,让我们将其用于一些有用的事情。 进行会话的目的是能够向服务器发出请求并接收可以满足该请求的响应。 在 DialogFlow 中,请求称为 Webhooks,并且大致对应于响应。 实现是 DialogFlow 的一项有用功能:通过实现,我们可以与后端进行通信并生成动态响应。 通过实现,我们可以开发一个 Webhook,该 Webhook 接受来自 DialogFlow 的请求,处理该请求,并使用与 DialogFlow 兼容的 JSON 进行响应。 + +在 DialogFlow 中,当调用某些启用了 Webhook 的意图时,将使用 Webhook 从后端获取数据。 来自该意图的信息将传递到 webhook 服务,然后返回响应。 + +为此,可以使用 **ngrok** 。 ngrok 软件是可用于调用 Webhook 的 Web 隧道工具。 它允许使用本地服务器测试 API 和 Webhooks。 本教程本节中将使用的另一个工具是 **Flask** 。 Flask 是一个轻量级的 Web 框架,可用于创建可调用外部应用程序的 Webhook 服务。 在我们的示例中,将被调用的外部应用程序是 DialogFlow 代理。 要使用 Flask,我们首先需要安装它: + +```py +$ pip3 install Flask +``` + +要了解有关 Flask 的更多信息,您可以访问: + +[https://pypi.org/project/Flask](https://pypi.org/project/Flask ) + +**使用 Flask** 创建 Webhook + +首先,我们可以创建一个基本的 Flask 应用程序: + +```py +# import flask dependencies +from flask import Flask +# initialize the flask app +app = Flask(__name__) +# default route +@app.route('/') +def index(): + return 'Hello World' +# create a route for webhook +@app.route('/webhook') +def webhook(): + return 'Hello World' +# run the app +if __name__ == '__main__': + app.run() +``` + +使用以下命令测试应用程序: + +```py +$ python app.py or FLASK_APP=hello.py flask run +``` + +如果您看到前面的输出,这将确认该应用程序的初始版本正在运行。 到目前为止,我们仅使用本地服务器,因此其他外部客户端无法通过 Internet 访问该服务器。 要将其集成为 DialogFlow 的 Webhook,我们需要将其部署在可以通过 Internet 访问的服务器上。 那就是 ngrok 工具出现的地方。可以在这里下载该工具: + +[https://ngrok.io](https://ngrok.io ) + +要运行 ngrok,请使用以下命令: + +```py +$ ngrok http +``` + +例如: + +```py +$ ngrok http 5000 +``` + +收到的输出应该是这样的: + +![](img/B15441_16_06.png) + +图 6:ngrok 初始化输出 + +在下一节中,我们将研究如何在 DialogFlow 中设置 Webhook。 + +## 如何在 DialogFlow 中设置 Webhook + +要在 DialogFlow 中设置 Webhook,请在左侧栏中选择**实现**,然后选择输入 ngrok 生成的 Webhook URL: + +![](img/B15441_16_07.png) + +图 7:Dialogflow Webhook 设置 + +确保将后缀`/webhook`添加到 URL 的末尾。 它看起来应该像这样: + +`https://886b89bc.ngrok.io/webhook` + +不: + +`https:// 886b89bc.ngrok.io/` + +我们将在`/webhook`路由而不是索引路由上处理请求。 + +如果该 URL 没有 webhook 后缀,则应出现以下错误: + +```py +Webhook call failed. Error: 405 Method Not Allowed. +``` + +更正 URL 以包括后缀,这样可以解决该错误。 + +接下来,需要启用 Webhook 以支持意图并获取服务器数据。 在下一节中,我们将介绍如何做到这一点。 + +## 为意图启用 Webhook + +要为意图启用 webhooks ,请打开需要启用 webhook 的意图,向下滚动到页面底部,然后启用选项**为此意图**启用 webhook 调用: + +![](img/B15441_16_08.png) + +图 8:意图的 Dialogflow Webhook 启用 + +触发意图后,它会向 Webhook 发送请求,然后将响应发送回去。 现在,我们可以继续设置培训阶段。 + +## 为意图设置训练短语 + +训练短语是帮助聊天机器人确定被调用意图的语音。 这是一个应如何设置的示例: + +![](img/B15441_16_09.png) + +图 9:意图的 Dialogflow 训练短语设置 + +接下来,我们需要设置参数和操作。 + +## 设置意图的参数和动作 + +需要先设置操作和参数,然后才能在 Webhook 中使用来处理请求。 + +在当前的示例中,`get_results`被设置为**动作**。 每当意图使用`POST`请求调用 Webhook 时,`get_results`将作为**动作**收到。 如果存在可以调用 Webhook 的多个意图,则将使用该动作进行区分,并由此生成不同的响应。 + +我们还可以将**参数**传递到我们的 webhook。 为此,我们可以定义参数名称及其值。 在此示例中,我们将非常简单地开始,但最终我们将允许用户从餐厅订购食物。 因此,例如,用户可能会说:“我想订购汉堡和薯条”,而聊天机器人会将这种话语传递给后端,以进行验证,存储和处理: + +![](img/B15441_16_10.png) + +图 10:Dialogflow 操作和参数设置 + +动作和参数是可选值。 即使未传递操作和参数,Webhook 仍将运行。 为了区分没有动作的意图,可以在请求 JSON 中检查意图名称。 + +## 通过网络挂钩建立实现响应 + +Webhook 响应应该使用有效的 JSON 响应构造。 这样,DialogFlow 将能够在前端正确显示消息。 + +可以使用 Python 构造响应。 可以使用以下响应类型: + +* 简单回应 +* 基本卡 +* 意见建议 +* 清单卡 +* 浏览轮播 +* 轮播响应 + +以下代码为 DialogFlow 生成一个简单的 JSON 响应,并带有`fulfillment`文本: + +```py +# import flask dependencies +from flask import Flask, request, make_response, jsonify +# initialize the flask app +app = Flask(__name__) +# default route +@app.route('/) +def index(): + return 'Hello World' +# function for responses +def results(): + # build a request object + req = request.get_json(force=True) + # fetch action from json + action = req.get('queryResult').get('action') + # return a fulfillment response + return {'fulfillmentText': 'This is a webhook response'} +# create a route for webhook +@app.route('/webhook', methods=['GET', 'POST']) +def webhook(): + # return response + return make_response(jsonify(results())) +# run the app +if __name__ == '__main__': + app.run() +You can see that we have fetched "action" from the request using +action = req.get('queryResult').get('action') +``` + +最初,这只是一个非常简单的示例,用于演示请求/响应(webhook /实现)机制。 用户应该看到以下意图响应: + +```py +This is a webhook response. +``` + +您可以看到我们已经使用以下命令从请求中提取了`action`: + +```py +action = req.get('queryResult').get('action') +``` + +在此示例中未使用操作,但可以将用于此目的。 现在,我们将研究如何从服务器获取响应以及如何根据响应来处理响应。 + +## 检查来自 Webhook 的响应 + +使用窗口右侧的控制台,可以调用意向,并可以检查响应。 在当前示例中,响应将如下所示: + +![](img/B15441_16_11.png) + +图 11:Dialogflow 响应验证 + +如果要调试聊天机器人并对其进行故障排除,可以单击**诊断信息**。 在这里,我们可以查看所有 DialogFlow 请求的详细信息以及从 Webhook 发送回的响应。 如果 Webhook 中存在错误,则**诊断信息**也可以用于调试。 + +本章的目的是介绍聊天机器人的基本知识。 在这一点上,我们的聊天机器人除了演示如何从服务器获取响应外,还没有做其他事情。 我们将其留给您,以探索如何进一步增强聊天机器人。 一些明显的增强功能包括:根据 e 稳定菜单检查所请求的食物,以查看菜单上是否有可用项目; 根据当前库存检查请求的食物数量,以查看是否可以完成订单; 将订单存储在后端数据库中以进行记帐和跟踪; 并将聊天机器人连接到机器人或后端系统以实际完成订单。 + +# 摘要 + +在本章中,我们首先了解了聊天机器人的潜在未来,以及聊天机器人变得越来越好将如何影响我们的社会。 然后,我们了解了当前聊天机器人技术的局限性以及当前局限性推荐的最佳实践。 我们了解了基本的聊天机器人概念以及最流行的聊天机器人平台。 + +最后,我们更深入地研究了由 Google 开发的名为 DialogFlow 的聊天机器人平台。 通过执行基本练习,然后学习如何使用 Webhooks 与其他后端服务集成,我们熟悉了该平台。 我们一步一步地了解了如何测试聊天机器人的功能并确保其设置正确。 + +在下一章中,我们将跳到另一个令人兴奋的主题,并学习如何训练顺序数据并将其用于时间序列分析。 \ No newline at end of file diff --git a/docs/ai-py/17.md b/docs/ai-py/17.md new file mode 100644 index 0000000000000000000000000000000000000000..00cffc9e257cbe17129a0102b5a7f488a88b924a --- /dev/null +++ b/docs/ai-py/17.md @@ -0,0 +1,862 @@ +# 17 + +# 顺序数据和时间序列分析 + +在本章中,我们将学习如何构建序列学习模型。 为此,我们将涵盖许多主题,以使我们更好地掌握如何构建和使用这些模型。 我们将学习如何处理熊猫中的时间序列数据。 我们将了解如何分割时间序列数据并对其执行各种操作,然后我们将讨论如何滚动地从时间序列数据中提取各种统计信息。 接下来,我们将学习隐马尔可夫模型(HMM),然后实现一个用于构建这些模型的系统。 我们将了解如何使用条件随机字段来分析字母序列,最后,我们将讨论如何使用到目前为止学到的技术来分析股市数据。 + +在本章的最后,您将学到: + +* 了解顺序数据 +* 使用熊猫处理时间序列数据 +* 切片时间序列数据 +* 操作时间序列数据 +* 从时间序列数据中提取统计信息 +* 使用隐马尔可夫模型生成数据 +* 使用条件随机字段识别字母序列 +* 股市分析 + +让我们开始研究并理解顺序数据。 + +# 了解顺序数据 + +在机器学习的世界中,我们遇到了许多类型的数据,例如图像,文本,视频和传感器读数。 不同类型的数据需要不同类型的建模技术。 顺序数据是指顺序很重要的数据。 顺序数据可以在许多“野外”情况下找到。 这里有些例子: + +**基因组序列数据** –这个也许是我们拥有的顺序数据的最好和最重要的例子。 基因出现的顺序是创造和维持生命的最基本水平。 基因组学序列包含使我们存活的信息。 + +**人类语言** –沟通时顺序非常重要。 如果我们开始更改本书中单词的顺序,不久之后,本书将变得完全不可理解! + +**计算机语言** –在大多数计算机语言中,正确的输入顺序对于任何功能正常运行至关重要。 例如,在许多计算机语言中,符号“ > =“的意思是“大于或等于”,而在其他语言中,“ = >”的意思可能是赋值或产生语法错误。 + +时间序列数据是顺序数据的子分类。 时间序列数据的一些示例如下: + +**股票市场价格** –时间序列数据的圣杯是股票价格。 许多数据科学家将在其职业生涯中的某个时刻尝试使用其数据科学技能来尝试预测股市。 他们中的许多人将意识到这是一项艰巨的努力,并转向其他主题和问题。 库存预测困难的几个原因是: + +* 在经济周期的不同时间,股票对经济状况的反应不同。 +* 影响股票价格的因素很多,这使它成为一个非常复杂的系统。 +* 股票中一些最剧烈的变动发生在市场交易时间之外,这使得实时处理这些信息变得困难。 + +**应用程序日志** –根据定义,应用程序日志具有两个组成部分。 指示操作何时发生以及正在记录信息或错误的时间戳。 + +**IoT 活动** – IoT 设备中的活动以时间顺序方式发生,因此可以用作时间的数据。 + +时间序列数据是从任何数据源(例如传感器,麦克风,股票市场等)获得的时间戳值。 时间序列数据具有许多重要特征,需要对其进行建模才能进行有效分析。 + +时间序列数据中某些参数的测量以固定的时间间隔进行。 这些测量被安排并存储在时间线上,并且它们的出现顺序至关重要。 此顺序用于从数据中提取模式。 + +在本章中,我们将看到如何构建通常描述时间序列数据和顺序数据的模型。 这些模型将用于了解时间序列变量的行为。 然后,我们将能够使用这些模型来预测和推断该模型以前未看到的值。 + +时间序列数据分析被广泛应用于金融,传感器数据分析,语音识别,经济学,天气预报,制造以及更多领域。 在本章中,我们将广泛使用一个名为 Pandas 的库来处理与时间序列相关的操作。 + +Pandas 是强大的和流行的 Python 软件包,用于数据处理和分析。 具体来说,它提供了用于操纵表结构的方法和操作。 这是一个可爱的名字,让人联想到毛茸茸的熊,但这里有一些没用的琐事。 熊猫的名称来自*面板数据*一词,这是计量经济学术语,用于包含多个时间段内观测值的数据集。 + +我们还将使用其他几个有用的软件包,例如`hmmlearn`和`pystruct`。 确保继续安装它们。 + +可以通过运行以下命令来安装这些软件包: + +```py +$ pip3 install pandas +$ pip3 install hmmlearn +$ pip3 install pystruct +$ pip3 install cvxopt +$ pip3 install timeseries +``` + +如果在安装`cvxopt`时出错,则可以在 [http://cvxopt.org/install](http://cvxopt.org/install) 上找到进一步的说明。 假设您已经成功安装了 pac kages,让我们继续进行下一部分,在此我们将研究如何通过和 Pandas 处理时间序列数据。 + +# 使用熊猫处理时间序列数据 + +熊猫可以说是 Python 中最重要的库。 学习良好地使用其方法至关重要,当您将 Python 用于其他任何项目时,它将为您提供良好的服务。 除了时间序列分析外,熊猫还可以执行更多功能,包括: + +* 具有集成索引的 DataFrame 操作 +* 从各种不同的文件格式读取数据并将数据写入内存数据结构的方法 +* 资料分类 +* 资料筛选 +* 缺少价值估算 +* 重塑和旋转数据集 +* 基于标签的切片,索引和子集创建 +* 高效的列插入和删除 +* 按数据集上的操作分组 +* 合并和合并数据集 + +在本节中,我们将使用它来将数字序列转换为时间序列数据并将其可视化。 Pandas 提供了添加时间戳,组织数据然后对其进行有效操作的选项。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +import pandas as pd +``` + +定义一个函数以从输入文件中读取数据。 参数索引指示包含相关数据的列: + +```py +def read_data(input_file, index): + # Read the data from the input file + input_data = np.loadtxt(input_file, delimiter=',') +``` + +定义`lambda`函数以将字符串转换为 Pandas 日期格式: + +```py + # Lambda function to convert strings to Pandas date format + to_date = lambda x, y: str(int(x)) + '-' + str(int(y)) +``` + +使用此`lambda`函数可从输入文件的第一行获取开始日期: + +```py + # Extract the start date + start = to_date(input_data[0, 0], input_data[0, 1]) +``` + +在执行操作时,Pandas 库需要结束日期为独占日期,因此我们需要将最后一行的`date`字段增加一个月: + +```py + # Extract the end date + if input_data[-1, 1] == 12: + year = input_data[-1, 0] + 1 + month = 1 + else: + year = input_data[-1, 0] + month = input_data[-1, 1] + 1 + end = to_date(year, month) +``` + +使用开始日期和结束日期以及每月一次的频率来创建带有日期的索引列表: + +```py + # Create a date list with a monthly frequency + date_indices = pd.date_range(start, end, freq='M') +``` + +使用时间戳创建一个 Pandas 数据系列: + +```py + # Add timestamps to the input data to create time series data + output = pd.Series(input_data[:, index], index=date_indices) + return output +``` + +定义和`main`功能并指定输入文件: + +```py +if __name__=='__main__': + # Input filename + input_file = 'data_2D.txt' +``` + +指定包含数据的列: + +```py + # Specify the columns that need to be converted + # into time series data + indices = [2, 3] +``` + +遍历各列并读取每列中的数据: + +```py + # Iterate through the columns and plot the data + for index in indices: + # Convert the column to timeseries format + timeseries = read_data(input_file, index) +``` + +绘制时间序列数据: + +```py + # Plot the data + plt.figure() + timeseries.plot() + plt.title('Dimension ' + str(index - 1)) + plt.show() +``` + +完整代码在文件`timeseries.py`中给出。 如果运行代码,您将看到两个屏幕截图。 + +以下屏幕截图显示了第维的数据: + +![](img/B15441_17_01.png) + +图 1:使用每日数据绘制的一维数据 + +第二张屏幕截图以秒 ond 维表示数据: + +![](img/B15441_17_02.png) + +图 2:使用每日数据绘制的第二维数据 + +在本节中,我们为如何使用 Pandas 从外部文件加载数据,如何将其转换为时间序列格式以及如何对其进行绘制和可视化奠定基础。 在下一节中,我们将学习如何进一步处理数据。 + +# 切片时间序列数据 + +现在我们已经加载了时间序列数据,让我们看看如何对其进行切片。 切片的过程是指将数据分为多个子间隔并提取相关信息。 当我们使用时间序列数据集时,这很有用。 我们将使用时间戳来切片数据,而不是使用索引。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +import pandas as pd +from timeseries import read_data +``` + +从输入数据文件中加载第三列(零索引): + +```py +# Load input data +index = 2 +data = read_data('data_2D.txt', index) +``` + +定义开始和结束年份,然后以年级粒度绘制数据: + +```py +# Plot data with year-level granularity +start = '2003' +end = '2011' +plt.figure() +data[start:end].plot() +plt.title('Input data from ' + start + ' to ' + end) +``` + +定义开始和结束月,然后以月级粒度绘制数据: + +```py +# Plot data with month-level granularity +start = '1998-2' +end = '2006-7' +plt.figure() +data[start:end].plot() +plt.title('Input data from ' + start + ' to ' + end) +plt.show() +``` + +完整代码在文件`slicer.py`中给出。 如果运行代码,您将看到两个数字。 + +第一个屏幕截图显示了从 *2003* 到 *2011* 的数据: + +![](img/B15441_17_03.png) + +图 3:使用每月滴答作图的数据(2003 年至 2011 年) + +第二张屏幕截图显示了 1998 年 2 月到 2006 年 7 月的数据: + +![](img/B15441_17_04.png) + +图 4:使用月度滴答作图的数据(1998 年至 2006 年) + +正如我们在上一节创建的图表(*图 1* 和*图 2* )中看到的那样,它们很难阅读。 数据被“捆绑”。 通过使用每月刻度对数据进行切片,可以更轻松地可视化数据的起伏。 在下一节中,我们将继续学习 Pandas 库中可用的不同功能,例如过滤和求和,以及该功能如何帮助更好地分析和处理 数据集。 + +# 对时间序列数据进行操作 + +熊猫库可以有效地处理时间序列数据,并执行各种操作,例如过滤和加法。 可以设置条件,熊猫会过滤数据集并根据条件返回正确的子集。 时间序列数据也可以加载和过滤。 让我们看另一个例子来说明这一点。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt +from timeseries import read_data +``` + +定义输入文件名: + +```py +# Input filename +input_file = 'data_2D.txt' +``` + +将第三和第四列加载到单独的变量中: + +```py +# Load data +x1 = read_data(input_file, 2) +x2 = read_data(input_file, 3) +``` + +通过命名两个维度来创建 Pandas `DataFrame`对象: + +```py +# Create pandas dataframe for slicing +data = pd.DataFrame({'dim1': x1, 'dim2': x2}) +``` + +通过指定开始和结束年份来绘制数据: + +```py +# Plot data +start = '1968' +end = '1975' +data[start:end].plot() +plt.title('Data overlapped on top of each other') +``` + +使用条件过滤数据,然后显示它。 在这种情况下,我们将获取`dim1`中所有小于`45`的数据点和`dim2`中所有大于`30`的值: + +```py +# Filtering using conditions +# - 'dim1' is smaller than a certain threshold +# - 'dim2' is greater than a certain threshold +data[(data['dim1'] < 45) & (data['dim2'] > 30)].plot() +plt.title('dim1 < 45 and dim2 > 30') +``` + +我们也可以在熊猫中添加两个系列。 让我们在给定的开始日期和结束日期之间添加`dim1`和`dim2`: + +```py +# Adding two dataframes +plt.figure() +diff = data[start:end]['dim1'] + data[start:end]['dim2'] +diff.plot() +plt.title('Summation (dim1 + dim2)') +plt.show() +``` + +完整代码在文件`operator.py`中给出。 如果运行代码,您将看到三个屏幕截图。 第一个屏幕快照显示了从到 *1968* 到 *1975* 的数据: + +![](img/B15441_17_05.png) + +图 5:重叠数据(1968 年至 1975 年) + +第二张屏幕截图显示了他过滤的数据: + +![](img/B15441_17_06.png) + +图 6:具有< 45 and dim > 30 暗值的数据(1968 年-1975 年) + +第三个屏幕截图显示了 的求和结果: + +![](img/B15441_17_07.png) + +图 7:dim1 和 dim2 的总和(1968 – 1975) + +在本节中,我们继续学习 Pandas 库中可用的不同功能,包括过滤和求和。 在数据科学中,选择和训练模型以了解正在分析的数据集很重要。 熊猫是完成此任务的有用工具。 在下一节中,我们将介绍另外两个有用的库。 这些库用于计算有关数据集的各种统计信息。 + +# 从时间序列数据中提取统计信息 + +为了从时间序列数据中提取有意义的见解,我们可以从中生成统计数据。 这些统计信息的示例包括平均值,方差,相关性,最大值等操作。 这些统计信息可以使用窗口滚动计算。 我们可以使用预定的窗口大小,并在该窗口内计算这些统计信息。 当我们可视化一段时间内的统计信息时,我们可能会看到有趣的模式。 让我们看一个如何从时间序列数据中提取这些统计信息的示例。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +import pandas as pd +from timeseries import read_data +``` + +定义输入文件名: + +```py +# Input filename +input_file = 'data_2D.txt' +``` + +将第三和第四列加载到单独的变量中: + +```py +# Load input data in time series format +x1 = read_data(input_file, 2) +x2 = read_data(input_file, 3) +``` + +通过命名两个维度来创建熊猫`DataFrame`: + +```py +# Create pandas dataframe for slicing +data = pd.DataFrame({'dim1': x1, 'dim2': x2}) +``` + +沿每个维度提取最大值和最小值: + +```py +# Extract max and min values +print('\nMaximum values for each dimension:') +print(data.max()) +print('\nMinimum values for each dimension:') +print(data.min()) +``` + +提取前`12`行的总体均值和行均值: + +```py +# Extract overall mean and row-wise mean values +print('\nOverall mean:') +print(data.mean()) +print('\nRow-wise mean:') +print(data.mean(1)[:12]) +``` + +使用`24`窗口大小绘制滚动平均值: + +```py +# Plot the rolling mean using a window size of 24 +data.rolling(center=False, window=24).mean().plot() +plt.title('Rolling mean') +``` + +打印相关系数: + +```py +# Extract correlation coefficients +print('\nCorrelation coefficients:\n', data.corr()) +``` + +使用窗口大小`60`绘制滚动相关性: + +```py +# Plot rolling correlation using a window size of 60 +plt.figure() +plt.title('Rolling correlation') +data['dim1'].rolling(window=60).corr(other=data['dim2']).plot() +plt.show() +``` + +完整的代码在文件`stats_extractor.py`中给出。 如果运行代码,则会看到两个屏幕截图。 第一个屏幕截图显示了滚动平均值: + +![](img/B15441_17_08.png) + +图 8:滚动平均值 + +第二张屏幕截图显示了滚动相关性: + +![](img/B15441_17_09.png) + +图 e 9:滚动相关 + +您也应该看到以下输出: + +![](img/B15441_17_10.png) + +图 10:最大和最小尺寸以及总体平均值 + +如果向下滚动,则将看到按行平均值和打印出的相关系数: + +![](img/B15441_17_11.png) + +图 11:逐行均值和相关系数 + +上图中的相关系数表示每个维度与所有其他维度的相关程度。 `1.0`的相关性表示完全相关,而`0.0`的相关性表示变量根本不相关。 `dim1`与`dim1`完美相关,`dim2`与`dim2`完美相关。 在任何混淆矩阵中,情况总是如此。 只是说一个变量与其自身完全相关。 另外,`dim1`与`dim2`具有低相关性。 这意味着`dim1`将具有较低的功率来预测`dim2`的值。 到目前为止,我们将不会很快凭借我们的模型及其预测股票价格的能力而成为百万富翁。 在下一节中,我们将学习一种有用的技术来分析时间序列数据,称为 **Hi dden Markov 模型**( **HMM** )。 + +# 使用隐马尔可夫模型生成数据 + +**隐藏马尔可夫模型**( **HMM** )是一种用于分析连续数据的强大分析技术。 假设要建模的系统是具有隐藏状态的马尔可夫过程。 这意味着基础系统可以是一组可能状态中的一个。 + +它经历一系列状态转换,从而产生一系列输出。 我们只能观察输出,而不能观察状态。 因此,这些状态对我们是隐藏的。 我们的目标是对数据建模,以便我们可以推断未知数据的状态转换。 + +为了理解 HMM,让我们考虑**旅行商问题**( **TSP** )的版本。 在此示例中,推销员必须在以下三个城市之间旅行才能工作:伦敦,巴塞罗那和纽约。 他的目标是最大程度地减少旅行时间,以使其成为最有效率的人。 考虑到他的工作承诺和时间表,我们有一组概率决定了从城市 *X* 到城市 *Y* 的机会。 在给出的以下信息中, *P(X-> Y)*表示从城市 *X* 到城市 *Y* 的概率: + + +| **城市** | **概率** | +| P(伦敦->伦敦) | 0.10 | +| P(伦敦->巴塞罗那) | 0.70 | +| P(伦敦-> NY) | 0.20 | +| P(巴塞罗那->巴塞罗那) | 0.15 | +| P(巴塞罗那->伦敦) | 0.75 | +| P(巴塞罗那-> NY) | 0.10 | +| P(纽约->纽约) | 0.05 | +| P(纽约->伦敦) | 0.60 | +| P(纽约州->巴塞罗那) | 0.35 | + +让我们用过渡矩阵表示此信息: + + +| | **伦敦** | **巴塞罗那** | **纽约** | +| **伦敦** | 0.10 | 0.70 | 0.20 | +| **巴塞罗那** | 0.75 | 0.15 | 0.10 | +| **纽约** | 0.60 | 0.35 | 0.05 | + +现在我们有了所有信息,让我们继续设置问题陈述。 推销员在星期二从伦敦开始他的旅程,并在星期五计划一些事情。 但这取决于他在哪里。 他星期五将在巴塞罗那举行的可能性是多少? 该表将帮助我们找出答案。 + +如果我们没有马尔可夫链来对该问题进行建模,那么我们将不知道旅行时间表是什么样的。 我们的目标是非常确定地说他将在给定的一天在每个城市中。 + +如果我们用 *T* 表示过渡矩阵,而用 *X(i)*表示当日,则: + +*X(i + 1)= X(i).T* + +在我们的情况下,星期五距离星期二 3 天。 这个意味着我们需要计算 *X(i + 3)*。 计算将如下所示: + +*X(i + 1)= X(i).T* + +*X(i + 2)= X(i + 1).T* + +*X(i + 3)= X(i + 2).T* + +因此,本质上: + +*X(i + 3)= X(i).T ^ 3* + +我们将 *X(i)*设置为: + +*X(i)= [0.10 0.70 0.20]* + +下一步是计算矩阵的立方。 在线提供了许多工具来执行矩阵运算,例如: + +[http://matrix.reshish.com/multiplication.php](http://matrix.reshish.com/multiplication.php ) + +如果您进行了所有矩阵计算,那么您将看到在星期四将获得以下概率: + +*P(伦敦)= 0.31* + +*P(巴塞罗那)= 0.53* + +*P(NY)= 0.16* + +我们可以看到他在巴塞罗那的机会要比其他任何城市都高。 这也具有地理意义,因为巴塞罗那比纽约更靠近伦敦。 让我们看看如何在 Pytho n 中为 HMM 建模。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import datetime +import numpy as np +import matplotlib.pyplot as plt +from hmmlearn.hmm import GaussianHMM +from timeseries import read_data +``` + +从输入文件加载数据: + +```py +# Load input data +data = np.loadtxt('data_1D.txt', delimiter=',') +``` + +提取第三列进行培训: + +```py +# Extract the data column (third column) for training +X = np.column_stack([data[:, 2]]) +``` + +使用`5`分量和对角协方差创建高斯 HMM: + +```py +# Create a Gaussian HMM +num_components = 5 +hmm = GaussianHMM(n_components=num_components, + covariance_type='diag', n_iter=1000) +``` + +训练 HMM: + +```py +# Train the HMM +print('\nTraining the Hidden Markov Model...') +hmm.fit(X) +``` + +打印 HMM 每个组成部分的均值和方差值: + +```py +# Print HMM stats +print('\nMeans and variances:') +for i in range(hmm.n_components): + print('\nHidden state', i+1) + print('Mean =', round(hmm.means_[i][0], 2)) + print('Variance =', round(np.diag(hmm.covars_[i])[0], 2)) +``` + +使用训练有素的 HMM 生成`1200`样本并绘制它们: + +```py +# Generate data using the HMM model +num_samples = 1200 +generated_data, _ = hmm.sample(num_samples) +plt.plot(np.arange(num_samples), generated_data[:, 0], c='black') +plt.title('Generated data') +plt.show() +``` + +完整代码在文件`hmm.py`中给出。 如果运行代码,将看到以下屏幕截图,其中显示了 1200 个生成的示例: + +![](img/B15441_17_12.png) + +图 12:生成的数据 + +您还将看到以下输出 t: + +![](img/B15441_17_13.png) + +图 13:HMM 培训 + +在给定不同行程的情况下,我们解释图表的方式是遍历销售路线所需的时间不同。 第二个输出指示这些路径的均值和方差。 + +既然我们已经了解了 HMM,现在让我们了解与时间序列分析相关的另一个主题。 在下一部分中,我们将学习通常称为条件随机场的概率模型及其与 HMM 的区别。 + +# 使用条件随机字段识别字母序列 + +**条件随机字段**( **CRF** )是概率模型,经常用于分析结构化数据。 我们使用它们以各种形式标记和分割顺序数据。 以下是一些应用 CRF 的最常见用例: + +* 手写识别 +* 字符识别 +* 物体检测 +* 命名实体识别 +* 基因预测 +* 图像分割 +* 语音标记的一部分 +* 降噪 + +关于 CRF 的注意事项之一是它们是判别模型。 将其与生成模型 HMM 对比。 + +我们可以在标记的测量序列上定义条件概率分布。 我们将使用它来构建 CRF 模型。 在 HMM 中,我们定义了观察序列和标签上的联合分布。 + +CRF 的主要优势之一是它们本质上是有条件的。 HMM 并非如此。 CRF 不假定输出观测值之间有任何独立性。 HMM 假定任何给定时间的输出在统计上均独立于先前的数据点。 HMM 必须做出此假设,以确保推理过程以可靠的方式工作。 但是这个假设并不总是正确的。 现实世界的数据充满了时间依赖性。 + +在自然语言处理,语音识别,生物技术等各种应用中,CRF 往往优于 HMM。 在本节中,我们将讨论如何使用 CRF 分析和识别单词。 + +这是一个很好的用例,将突出显示识别数据中依存关系的能力。 英语单词中字母的顺序决不是随机的。 例如,考虑单词*随机*。 在第一个字母之后的下一个字母将成为元音的可能性高于其在辅音中的可能性。 单词中的第二个字母变为字母 *x* 的可能性不为零。 我们可以想到几个符合此条件的单词-豁免,准确,展示等。 但是,考虑到第一个字母是 *r* ,单词中第二个字母是 *x* 的概率是多少? 我们想不出一个符合该标准的单词。 即使它们存在,也没有那么多,因此可能性较低。 CRF 利用这一事实。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import os +import argparse +import string +import pickle +import numpy as np +import matplotlib.pyplot as plt +from pystruct.datasets import load_letters +from pystruct.models import ChainCRF +from pystruct.learners import FrankWolfeSSVM +``` + +定义一个函数以解析输入参数。 我们可以将`C`值作为输入参数。 `C`参数控制我们要对错误分类进行惩罚的程度。 较高的`C`值表示我们会在训练过程中对错误分类施加更高的罚款,但最终可能会使模型过度拟合。 另一方面,如果我们为`C`选择较低的值,则可以使模型更好地推广。 但这也意味着我们将对训练数据点的错误分类处以较低的罚款。 + +```py +def build_arg_parser(): + parser = argparse.ArgumentParser(description='Trains a Conditional\ + Random Field classifier') + parser.add_argument("--C", dest="c_val", required=False, type=float, + default=1.0, help='C value to be used for training') + return parser +``` + +定义一个类来处理构建 CRF 模型的所有功能。 我们将使用带有`FrankWolfeSSVM`的链式 CRF 模型: + +```py +# Class to model the CRF +class CRFModel(object): + def __init__(self, c_val=1.0): + self.clf = FrankWolfeSSVM(model=ChainCRF(), + C=c_val, max_iter=50) +``` + +定义一个函数来加载训练数据: + +```py + # Load the training data + def load_data(self): + alphabets = load_letters() + X = np.array(alphabets['data']) + y = np.array(alphabets['labels']) + folds = alphabets['folds'] + return X, y, folds +``` + +定义一个函数来训练 CRF 模型: + +```py + # Train the CRF + def train(self, X_train, y_train): + self.clf.fit(X_train, y_train) +``` + +定义一个函数来评估 CRF 模型的准确性: + +```py + # Evaluate the accuracy of the CRF + def evaluate(self, X_test, y_test): + return self.clf.score(X_test, y_test) +``` + +定义一个函数以在未知数据点上运行经过训练的 CRF 模型: + +```py + # Run the CRF on unknown data + def classify(self, input_data): + return self.clf.predict(input_data)[0] +``` + +定义一个函数以根据索引列表从字母中提取子字符串: + +```py +# Convert indices to alphabets +def convert_to_letters(indices): + # Create a numpy array of all alphabets + alphabets = np.array(list(string.ascii_lowercase)) +``` + +提取字母: + +```py + # Extract the letters based on input indices + output = np.take(alphabets, indices) + output = ''.join(output) + return output +``` + +定义主函数并解析输入参数: + +```py +if __name__=='__main__': + args = build_arg_parser().parse_args() + c_val = args.c_val +``` + +创建 CRF 模型对象: + +```py + # Create the CRF model + crf = CRFModel(c_val) +``` + +加载输入数据并将其分为训练集和测试集: + +```py + # Load the train and test data + X, y, folds = crf.load_data() + X_train, X_test = X[folds == 1], X[folds != 1] + y_train, y_test = y[folds == 1], y[folds != 1] +``` + +训练 CRF 模型: + +```py + # Train the CRF model + print('\nTraining the CRF model...') + crf.train(X_train, y_train) +``` + +评估 CRF 模型的准确性并打印: + +```py + # Evaluate the accuracy + score = crf.evaluate(X_test, y_test) + print('\nAccuracy score =', str(round(score*100, 2)) + '%') +``` + +在一些测试数据点上运行它并打印输出: + +```py + indices = range(3000, len(y_test), 200) + for index in indices: + print("\nOriginal =", convert_to_letters(y_test[index])) + predicted = crf.classify([X_test[index]]) + print("Predicted =", convert_to_letters(predicted)) +``` + +完整的代码在文件`crf.py`中指定为。 如果运行代码,则应看到以下输出: + +![](img/B15441_17_14.png) + +图 14:CRF 模型训练 + +如果滚动到末尾,还应该看到以下输出: : + +![](img/B15441_17_15.png) + +图 15:原始输出与预测输出 + +如我们所见,可以正确预测中的大多数单词。 希望我们能够说明 CRF 的功能以及它们与 HMM 的区别。 在下一节中,我们将重新介绍 HMM,并在示例中应用它们来分析股票市场数据。 + +# 股市分析 + +我们将在本节中使用 HMM 分析股票市场数据。 这是一个已经组织好数据并加上时间戳的示例。 我们将使用`matplotlib`包中的可用数据集。 数据集包含这些年来不同公司的股票价值。 HMM 是生成模型,可以分析此类时间序列数据并提取底层结构。 我们将使用此模型来分析股票价格变化并生成输出。 + +请不要指望此模型产生的结果将接近生产质量,并且您将能够使用此模型执行实时交易并从中获利。 它将为开始思考如何实现这一目标提供基础。 如果您愿意,我们建议您继续增强模型,并针对不同的数据集强调该模型,并可能将其与当前市场数据一起使用。 我们不对模型的盈利或不盈利做任何表述。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import datetime +import warnings +import numpy as np +import matplotlib.pyplot as plt +import yfinance as yf +from hmmlearn.hmm import GaussianHMM +``` + +加载 1970 年 9 月 4 日至 2016 年 5 月 17 日的历史股票市场报价。您可以自由选择任何日期范围: + +```py +# Load historical stock quotes from matplotlib package +start_date = datetime.date(1970, 9, 4) +end_date = datetime.date(2016, 5, 17) +intc = yf.Ticker('INTC').history(start=start_date, end=end_date) +``` + +每天计算收盘报价的百分比差异: + +```py +# Take the percentage difference of closing stock prices +diff_percentages = 100.0 * np.diff(intc.Close) / intc.Close[:-1] +``` + +堆叠两个数据列以创建训练数据集: + +```py +# Stack the differences and volume values +# column-wise for training +training_data = np.column_stack([diff_percentages, intc.Volume[:-1]]) +``` + +使用`7`分量和对角协方差创建并训练高斯 HMM: + +```py +# Create and train Gaussian HMM +hmm = GaussianHMM(n_components=7, covariance_type='diag', n_iter=1000) +with warnings.catch_warnings(): + warnings.simplefilter('ignore') + hmm.fit(training_data) +``` + +使用训练有素的 HMM 模型来生成`300`样本。 您可以选择生成任意数量的样本。 + +```py +# Generate data using the HMM model +num_samples = 300 +samples, _ = hmm.sample(num_samples) +``` + +绘制生成的差异百分比值: + +```py +# Plot the difference percentages +plt.figure() +plt.title('Difference percentages') +plt.plot(np.arange(num_samples), samples[:, 0], c='black') +``` + +绘制交易股票数量的生成值: + +```py +# Plot the volume of shares traded +plt.figure() +plt.title('Volume of shares') +plt.plot(np.arange(num_samples), samples[:, 1], c='black') +plt.ylim(ymin=0) +plt.show() +``` + +完整代码在文件`stock_market.py`中给出。 如果运行代码,您将看到以下两个屏幕截图。 第一个屏幕截图显示了 HMM 生成的差异百分比: + +![](img/B15441_17_16.png) + +图 16:差异百分比 + +第二张屏幕截图显示了 HMM 为交易的股票数量生成的值: + +![](img/B15441_17_17.png) + +图 17:股票数量 + +我们将其留给阅读器,以针对数据集中的实际数据点计算 HMM 模型预测的值的准确性。 然后将需要一些工作来使用它来产生交易信号。 正如我们在本节开头提到的,我们不建议您使用此代码来使用真实货币进行实际交易。 + +# 摘要 + +在本章中,我们学习了如何构建序列学习模型。 我们了解了如何处理熊猫中的时间序列数据。 我们讨论了如何分割时间序列数据并对其执行各种操作。 我们学习了如何以滚动方式从时间序列数据中提取各种统计信息。 我们了解了隐马尔可夫模型,然后实现了构建该模型的系统。 + +我们讨论了如何使用条件随机字段来分析字母序列。 我们学习了如何使用各种技术来分析股票 m 市场数据。 在下一章中,我们将继续学习如何在图像识别领域内实现 AI。 \ No newline at end of file diff --git a/docs/ai-py/18.md b/docs/ai-py/18.md new file mode 100644 index 0000000000000000000000000000000000000000..5cfa54b8d83ba8d6f02e43fdedddfbf089269daf --- /dev/null +++ b/docs/ai-py/18.md @@ -0,0 +1,1322 @@ +# 18 + +# 图像识别 + +在本章中,我们将学习有关对象检测和跟踪的知识。 首先,我们将花费一些时间来理解为什么图像识别对于机器学习非常重要。 然后,我们将学习称为 OpenCV 的图像识别程序包,该程序包是计算机视觉的流行库。 我们还将学习如何安装 OpenCV 并讨论帧差异,以了解如何检测视频中的运动部分。 我们将学习如何使用色彩空间跟踪对象,以及如何使用背景减法来跟踪对象。 之后,我们将使用 CAMShift 算法构建交互式对象跟踪器,并学习如何构建基于光流的跟踪器。 我们将讨论面部检测和相关概念,例如 Haar 级联和积分图像。 然后,我们将使用此技术来构建眼睛检测器和跟踪器。 + +在本章结束时,您将了解: + +* 安装 OpenCV +* 帧差异 +* 使用色彩空间跟踪对象 +* 使用背景减法跟踪对象 +* 使用 CAMShift 算法构建交互式对象跟踪器 +* 基于光流的跟踪 +* 人脸检测和追踪 +* 使用 Haar 级联进行对象检测 +* 使用积分图像进行特征提取 +* 眼睛检测和跟踪 + +我们将从介绍 OpenCV 开始,然后逐步介绍如何安装它。 + +# 图像识别的重要性 + +作为,希望从本书的主题中变得清晰起来,一般的人工智能,尤其是机器学习,是推动当今社会发生数字化转型的一些技术。 能够“看到”是人类学习过程的关键组成部分。 类似地,即使他们使用不同的方法“查看”,捕获图像并识别这些图像中包含的内容对于计算机来说也是最重要的,以便创建数据集以馈入机器学习管道并从该数据中获取洞察力。 + +无人驾驶技术就是一个明显的例子。 在这种情况下,计算机就像人类的同类计算机一样,需要能够在任何给定的每秒内提取千兆字节的数据,分析这些数据,并实时做出改变人生的决定。 当这项技术被广泛使用的那天,我感到非常兴奋。 我的估计是,这将尽早发生。 根据世界卫生组织的数据,2013 年道路交通事故死亡人数为 125 万人。部署自动驾驶汽车时,可以避免其中的很大比例。 + +无人驾驶技术只是图像识别的一种应用,其应用几乎是无限的,仅受我们的想象力限制。 其他一些流行的用途是: + +**自动图像分类** –我们可以在 Google 相册中以及在将图像上传到 Facebook 以及查看 Facebook 如何向我们提供有关图像中人物的建议时看到的第一手示例。 + +**反向图像搜索** – Google 除其他功能外,还提供功能,您可以将图像用作输入,而不是使用关键字作为输入并获取图像,而 Google 可以猜测 图片包含。 您可以在这里尝试: + +[httpsimg.google.com/](httpsimg.google.com/ ) + +**光学字符识别** –将 图像转换为文本非常依赖于图像识别。 + +**MRI 和超声解释** –在识别癌症和其他疾病方面,某些工具的性能优于人类。 + +考虑了图像识别的一些实际应用后,让我们进入将要使用的程序包以亲自了解它。 + +# OpenCV + +在本章中,我们将使用名为 OpenCV 的软件包。 的名称隐含了 **OpenCV** (**开源计算机视觉**)是一个开源跨平台 Python 软件包,可用于启用实时计算机视觉。 该工具起源于英特尔实验室。 + +OpenCV 可以与 TensorFlow,PyTorch 和 Caffe 结合使用。 + +**安装** + +在本章中,我们将使用名为 OpenCV 的软件包。 您可以在此处了解更多信息: [http://opencv.org](http://opencv.org) 。 在继续操作之前,请确保已安装。 以下是在各种操作系统上使用 Python 3 安装 OpenCV 3 的链接: + +* **Windows** : [https://solarianprogrammer.com/2016/09/17/install-opencv-3-with-python-3-on-windows](https://solarianprogrammer.com/2016/09/17/install-opencv-3-with-python-3-on-windows ) +* **Ubuntu** : [http://www.pyimagesearch.com/2015/07/20/install-opencv-3-0-and-python-3-4-on-ubuntu](http://www.pyimagesearch.com/2015/07/20/install-opencv-3-0-and-python-3-4-on-ubuntu ) +* **Mac** : [http://www.pyimagesearch.com/2015/06/29/install-opencv-3-0-and-python-3-4-on-osx](http://www.pyimagesearch.com/2015/06/29/install-opencv-3-0-and-python-3-4-on-osx ) + +现在您已经安装了它,让我们转到下一部分,我们将讨论帧差异分段。 + +# 帧差异 + +帧差分是可用于识别视频中运动部分的最简单技术之一。 直观地,在大多数应用程序中,这是有趣的部分所在。 如果我们有一个跑步者的视频,我们可能想分析跑步者的跑步情况,而不是背景图像。 当我们看电影时,我们主要关注最前沿的人物在说话和做事时。 我们不会倾向于关注背景中无聊的相框。 + +有时候,您会发现一个单身的怪胎会在隐藏在这种背景下的电影中发现问题,正如我们在*权力游戏*的最新情节中几次看到有人在杯子里发现星巴克时那样 背景,但这是例外而不是规则。 + +当我们观看实时视频流时,从该流捕获的连续帧之间的差异为我们提供了很多信息。 让我们看看如何获​​取连续帧之间的差异并显示差异。 本节中的代码需要连接的摄像头,因此请确保您的计算机上装有摄像头。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import cv2 +``` + +定义一个函数来计算帧差。 首先计算当前帧和下一帧之间的差异: + +```py +# Compute the frame differences +def frame_diff(prev_frame, cur_frame, next_frame): + # Difference between the current frame and the next frame + diff_frames_1 = cv2.absdiff(next_frame, cur_frame) +``` + +计算当前帧和上一帧之间的差异: + +```py + # Difference between the current frame and the previous frame + diff_frames_2 = cv2.absdiff(cur_frame, prev_frame) +``` + +计算两个差异帧之间的按位与并返回: + +```py + return cv2.bitwise_and(diff_frames_1, diff_frames_2) +``` + +定义一个功能以从网络摄像头抓取当前帧。 首先从视频捕获对象中读取它: + +```py +# Define a function to get the current frame from the webcam +def get_frame(cap, scaling_factor): + # Read the current frame from the video capture object + _, frame = cap.read() +``` + +根据缩放比例调整框架大小并返回: + +```py + # Resize the image + frame = cv2.resize(frame, None, fx=scaling_factor, + fy=scaling_factor, interpolation=cv2.INTER_AREA) +``` + +将图像转换为灰度并返回: + +```py + # Convert to grayscale + gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) + return gray +``` + +定义`main`函数并初始化视频捕获对象: + +```py +if __name__=='__main__': + # Define the video capture object + cap = cv2.VideoCapture(0) +``` + +定义比例因子以调整图像大小: + +```py + # Define the scaling factor for the images + scaling_factor = 0.5 +``` + +抓取当前的帧,下一帧以及之后的帧: + +```py + # Grab the current frame + prev_frame = get_frame(cap, scaling_factor) + # Grab the next frame + cur_frame = get_frame(cap, scaling_factor) + # Grab the frame after that + next_frame = get_frame(cap, scaling_factor) +``` + +无限期迭代,直到用户按下`Esc`键。 首先计算帧差异: + +```py + # Keep reading the frames from the webcam + # until the user hits the 'Esc' key + while True: + # Display the frame difference + cv2.imshow('Object Movement', frame_diff(prev_frame, + cur_frame, next_frame)) +``` + +更新`frame`变量: + +```py + # Update the variables + prev_frame = cur_frame + cur_frame = next_frame +``` + +从网络摄像头抓取下一帧: + +```py + # Grab the next frame + next_frame = get_frame(cap, scaling_factor) +``` + +检查用户是否按下了`Esc`键。 如果是这样,请退出循环: + +```py + # Check if the user hit the 'Esc' key + key = cv2.waitKey(10) + if key == 27: + break +``` + +退出循环后,请确保所有窗口均已正确关闭: + +```py + # Close all the windows + cv2.destroyAllWindows() +``` + +完整代码在提供给您的文件`frame_diff.py`中给出。 如果运行代码,您将看到一个显示实时输出的输出窗口。 如果四处走动,您会在这里看到自己的轮廓为 sh 所拥有的: + +![](img/B15441_18_01.png) + +图 1:轮廓图像 + +前面的屏幕截图中的白线表示轮廓。 我们取得了什么成就? 为什么这很重要? 根据应用程序的不同,我们可能不需要原始图像提供的所有信息。 原始图像具有更多细节,更多对比度和更多颜色。 让我们以自动驾驶汽车为例。 我们可能不在乎我们前面的车是红色还是绿色。 我们更担心要知道汽车是否正在驶向我们并即将撞向我们。 通过过滤所有额外的不相关信息,它允许系统中的其他算法更有效地处理图像中的相关信息,因此可以更快地对任何潜在的危险做出反应。 + +这种过滤可能有用的另一个实例是,加载视频的空间有限或成本高昂,我们需要压缩图像并提高空间利用率。 我们将由读者提出其他可能有用的方案,但希望我们能给您足够的灵感,激发您的想象力和创造力。 + +通过帧差分获得的信息很有用,但是我们将无法使用它构建健壮的跟踪器。 它对噪声敏感,并且不能真正完全跟踪物体。 要构建健壮的对象跟踪器,我们需要知道可以使用对象的哪些特征来精确跟踪它。 这是色彩空间变得重要的地方,我们将在 n 扩展部分中讨论。 + +# 使用颜色空间跟踪对象 + +可以使用各种色彩空间来表示图像。 RGB 颜色空间可能是最流行的颜色空间,但不适用于对象跟踪之类的应用程序。 因此,我们将改用 HSV 颜色空间。 这是一种直观的色彩空间模型,更接近于人类对色彩的感知方式。 您可以在此处了解更多信息: + +[https://zh.wikipedia.org/wiki/HSL_and_HSV](https://en.wikipedia.org/wiki/HSL_and_HSV) + +我们可以将捕获的帧从 RGB 转换为 HSV 颜色空间,然后使用颜色阈值跟踪任何给定的对象。 我们应该注意,我们需要知道对象的颜色分布,以便为阈值选择合适的范围。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import cv2 +import numpy as np +``` + +定义一个功能以从网络摄像头抓取当前帧。 首先从视频捕获对象中读取它: + +```py +# Define a function to get the current frame from the webcam +def get_frame(cap, scaling_factor): + # Read the current frame from the video capture object + _, frame = cap.read() +``` + +根据缩放比例调整框架大小并返回: + +```py + # Resize the image + frame = cv2.resize(frame, None, fx=scaling_factor, + fy=scaling_factor, interpolation=cv2.INTER_AREA) + return frame +``` + +定义`main`功能。 首先初始化视频捕获对象: + +```py +if __name__=='__main__': + # Define the video capture object + cap = cv2.VideoCapture(0) +``` + +定义用于调整捕获帧大小的比例因子: + +```py + # Define the scaling factor for the images + scaling_factor = 0.5 +``` + +无限期地进行迭代,直到用户按下`Esc`键。 抓取当前帧开始: + +```py + # Keep reading the frames from the webcam + # until the user hits the 'Esc' key + while True: + # Grab the current frame + frame = get_frame(cap, scaling_factor) +``` + +使用 OpenCV 中可用的内置函数将图像转换为 HSV 颜色空间: + +```py + # Convert the image to HSV colorspace + hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) +``` + +为人类皮肤的颜色定义近似 HSV 颜色范围: + +```py + # Define range of skin color in HSV + lower = np.array([0, 70, 60]) + upper = np.array([50, 150, 255]) +``` + +阈值 HSV 图像以创建掩码: + +```py + # Threshold the HSV image to get only skin color + mask = cv2.inRange(hsv, lower, upper) +``` + +计算蒙版和原始图像之间的按位与: + +```py + # Bitwise-AND between the mask and original image + img_bitwise_and = cv2.bitwise_and(frame, frame, mask=mask) +``` + +运行中值模糊以使图像平滑: + +```py + # Run median blurring + img_median_blurred = cv2.medianBlur(img_bitwise_and, 5) +``` + +显示输入和输出帧: + +```py + # Display the input and output + cv2.imshow('Input', frame) + cv2.imshow('Output', img_median_blurred) +``` + +检查用户是否按下了`Esc`键。 如果是这样,则退出循环: + +```py + # Check if the user hit the 'Esc' key + c = cv2.waitKey(5) + if c == 27: + break +``` + +退出循环后,请确保所有窗口均已正确关闭: + +```py + # Close all the windows + cv2.destroyAllWindows() +``` + +提供给您的文件`colorspaces.py`中提供了完整的代码。 如果运行代码,您将获得两个屏幕截图。 标题为**输入**的窗口是捕获的帧: + +![](img/B15441_18_02.png) + +图 2:捕获的帧 + +标题为**输出**的第二个窗口显示 皮肤蒙版: + +![](img/B15441_18_03.png) + +图 3:输出框架 + +如您在中看到的输出帧,我们现在只在图像中看到一种颜色,它对应于任何皮肤。 其他一切都是黑色的。 与上一节中看到的类似,我们对图像进行了过滤,以仅包含我们感兴趣的信息。在这种情况下,过滤是不同的,但结果是事实是我们现在仅具有所需的信息 进一步处理图像。 我想到了一些应用程序: + +* 检测异常皮肤状况或变色。 +* 仅在看到人的肤色时才会打开的安全系统。 这可用于人类可能藏在容器中的港口。 + +您还能想到其他一些应用程序吗? 在下一节中,我们将学习另一种称为背景减法的图像转换技术。 + +# 使用背景减法的对象跟踪 + +背景减法是一种技术,它对给定视频中的背景进行建模,然后使用该模型来检测运动对象。 该技术在视频压缩和视频监视中大量使用。 在必须检测静态场景中的移动物体的情况下,它的性能很好。 该算法的基本原理是检测背景,为其建立模型,然后从当前帧中减去背景以获得前景。 该前景对应于移动的对象。 + +这里的主要步骤之一是建立背景的模型。 它与帧差分不同,因为我们不差分连续帧。 我们正在对背景进行建模并实时更新,这使其成为一种自适应算法,可以适应不断变化的基线。 因此,它的性能要比帧差分好得多。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import cv2 +import numpy as np +``` + +定义一个函数以获取当前帧: + +```py +# Define a function to get the current frame from the webcam +def get_frame(cap, scaling_factor): + # Read the current frame from the video capture object + _, frame = cap.read() +``` + +调整框架大小并返回: + +```py + # Resize the image + frame = cv2.resize(frame, None, fx=scaling_factor, + fy=scaling_factor, interpolation=cv2.INTER_AREA) + return frame +``` + +定义`main`函数并初始化视频捕获对象: + +```py +if __name__=='__main__': + # Define the video capture object + cap = cv2.VideoCapture(0) +``` + +定义背景减法器对象: + +```py + # Define the background subtractor object + bg_subtractor = cv2.createBackgroundSubtractorMOG2() +``` + +定义历史记录和学习率。 关于`history`的全部内容,以下注释不言自明: + +```py + # Define the number of previous frames to use to learn. + # This factor controls the learning rate of the algorithm. + # The learning rate refers to the rate at which your model + # will learn about the background. Higher value for + # 'history' indicates a slower learning rate. You can + # play with this parameter to see how it affects the output. + history = 100 + # Define the learning rate + learning_rate = 1.0/history +``` + +无限期迭代,直到用户按下`Esc`键。 首先抓取当前帧: + +```py + # Keep reading the frames from the webcam + # until the user hits the 'Esc' key + while True: + # Grab the current frame + frame = get_frame(cap, 0.5) +``` + +使用先前定义的背景减法器对象计算`mask`: + +```py + # Compute the mask + mask = bg_subtractor.apply(frame, learningRate=learning_rate) +``` + +将`mask`从灰度转换为 RGB: + +```py + # Convert grayscale image to RGB color image + mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR) +``` + +显示输入和输出图像: + +```py + # Display the images + cv2.imshow('Input', frame) + cv2.imshow('Output', mask & frame) +``` + +检查用户是否按下了 *Esc* 键。 如果是这样,请退出循环: + +```py + # Check if the user hit the 'Esc' key + c = cv2.waitKey(10) + if c == 27: + break +``` + +退出循环后,请确保释放视频捕获对象并正确关闭所有窗口: + +```py + # Release the video capture object + cap.release() + # Close all the windows + cv2.destroyAllWindows() +``` + +完整代码在提供给您的文件`background_subtraction.py`中给出。 如果运行代码,您将看到一个显示实时输出的窗口。 如果四处走动,您将部分看到自己,如下所示: + +![](img/B15441_18_04.png) + +图 4:背景减影 + +一旦停止移动,它就会开始褪色,因为您现在是背景的一部分。 该算法会将您视为背景的一部分,并开始相应地更新模型: + +![](img/B15441_18_05.png) + +图 5:Bac kground 减法图像 + +当您保持静止时,它将继续消失,如下所示: + +![](img/B15441_18_06.png) + +图 6:背景减影 + +淡入淡出的过程表示当前场景是,而背景模型是。 + +您已经可以想象到,仅在移动时才生成图像将节省大量存储空间。 一个简单的例子是使用安全摄像机。 看着一个小时或几个小时的镜头对准空旷的停车场,可能比看着油漆枯燥更无聊,但是如果安全系统足够智能,可以在车架发生运动时进行记录,我们将能够辨别出“有趣的东西”。 视频中的“”部分。 + +基于颜色空间的跟踪允许我们跟踪有色对象,但是我们必须首先定义颜色。 这似乎是限制性的! 让我们看看如何在实时视频中选择一个对象,然后使用一个可以跟踪它的跟踪器。 这是,在其中 **CAMShift** 算法(代表代表**连续自适应均值移位**)变得很重要。 这基本上是 Mean Shift 算法的自适应版本。 我们将在下一节讨论 C AMShift。 + +# 使用 CAMShift 算法构建交互式对象跟踪器 + +为了理解 CAMShift,首先让我们了解平均移位的工作原理。 考虑给定帧中的感兴趣区域。 我们选择该区域是因为它包含感兴趣的对象。 我们要跟踪该对象,因此在其周围绘制了一个粗糙的边界,这就是感兴趣的*区域*所指的区域。 我们希望我们的对象跟踪器在视频中四处移动时跟踪该对象。 + +为此,我们基于该区域的颜色直方图选择一组点,然后计算质心。 如果此质心的位置在该区域的几何中心,则我们知道该对象没有移动。 但是,如果质心的位置不在此区域的几何中心,则我们知道对象已移动。 这意味着我们还需要移动封闭边界。 质心的运动直接指示物体的运动方向。 我们需要移动边界框,以使新质心成为此边界框的几何中心。 我们对每一帧都保持这种状态,并实时跟踪对象。 因此,此算法称为均值平移,因为均值(即质心)一直在移动,我们使用此跟踪对象。 + +让我们看看这与 CAMShift 有何关系。 均值平移的问题之一是不允许对象的大小随时间变化。 绘制边界框后,无论物体离相机有多近,它都将保持不变。 因此,我们需要使用 CAMShift,因为它可以使边界框的大小适应于对象的大小。 如果您想进一步探索,可以查看以下链接: + +[http://docs.opencv.org/3.1.0/db/df8/tutorial_py_meanshift.html](http://docs.opencv.org/3.1.0/db/df8/tutorial_py_meanshift.html ) + +让我们看看如何构建跟踪器。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import cv2 +import numpy as np +``` + +定义一个类来处理与对象跟踪有关的所有功能: + +```py +# Define a class to handle object tracking related functionality +class ObjectTracker(object): + def __init__(self, scaling_factor=0.5): + # Initialize the video capture object + self.cap = cv2.VideoCapture(0) +``` + +捕获当前帧: + +```py + # Capture the frame from the webcam + _, self.frame = self.cap.read() +``` + +设置比例因子: + +```py + # Scaling factor for the captured frame + self.scaling_factor = scaling_factor +``` + +调整框架大小: + +```py + # Resize the frame + self.frame = cv2.resize(self.frame, None, + fx=self.scaling_factor, fy=self.scaling_factor, + interpolation=cv2.INTER_AREA) +``` + +创建一个窗口以显示输出: + +```py + # Create a window to display the frame + cv2.namedWindow('Object Tracker') +``` + +设置鼠标回调函数以从鼠标获取输入: + +```py + # Set the mouse callback function to track the mouse + cv2.setMouseCallback('Object Tracker', self.mouse_event) +``` + +初始化变量以跟踪矩形选择: + +```py + # Initialize variable related to rectangular region selection + self.selection = None + # Initialize variable related to starting position + self.drag_start = None + # Initialize variable related to the state of tracking + self.tracking_state = 0 +``` + +定义一个函数来跟踪鼠标事件: + +```py + # Define a method to track the mouse events + def mouse_event(self, event, x, y, flags, param): + # Convert x and y coordinates into 16-bit numpy integers + x, y = np.int16([x, y]) +``` + +当鼠标左键按下时,表明用户已经开始绘制矩形: + +```py + # Check if a mouse button down event has occurred + if event == cv2.EVENT_LBUTTONDOWN: + self.drag_start = (x, y) + self.tracking_state = 0 +``` + +如果用户当前正在拖动鼠标以设置矩形选区的大小,请跟踪宽度和高度: + +```py + # Check if the user has started selecting the region + if self.drag_start: + if flags & cv2.EVENT_FLAG_LBUTTON: + # Extract the dimensions of the frame + h, w = self.frame.shape[:2] +``` + +设置矩形的 *X* 和 *Y* 坐标的: + +```py + # Get the initial position + xi, yi = self.drag_start +``` + +获取坐标的最大值和最小值,以使其与拖动鼠标以绘制矩形的方向无关: + +```py + # Get the max and min values + x0, y0 = np.maximum(0, np.minimum([xi, yi], [x, y])) + x1, y1 = np.minimum([w, h], np.maximum([xi, yi], [x, y])) +``` + +重置选择变量: + +```py + # Reset the selection variable + self.selection = None +``` + +完成矩形选择: + +```py + # Finalize the rectangular selection + if x1-x0 > 0 and y1-y0 > 0: + self.selection = (x0, y0, x1, y1) +``` + +如果选择完成,则设置标志,指​​示我们应该开始跟踪矩形区域内的对象: + +```py + else: + # If the selection is done, start tracking + self.drag_start = None + if self.selection is not None: + self.tracking_state = 1 +``` + +定义跟踪对象的方法: + +```py + # Method to start tracking the object + def start_tracking(self): + # Iterate until the user presses the Esc key + while True: + # Capture the frame from webcam + _, self.frame = self.cap.read() +``` + +调整框架大小: + +```py + # Resize the input frame + self.frame = cv2.resize(self.frame, None, + fx=self.scaling_factor, fy=self.scaling_factor, + interpolation=cv2.INTER_AREA) +``` + +创建框架的副本。 我们稍后将需要它: + +```py + # Create a copy of the frame + vis = self.frame.copy() +``` + +将帧的颜色空间从 RGB 转换为 HSV: + +```py + # Convert the frame to HSV colorspace + hsv = cv2.cvtColor(self.frame, cv2.COLOR_BGR2HSV) +``` + +根据预定义的阈值创建掩码: + +```py + # Create the mask based on predefined thresholds + mask = cv2.inRange(hsv, np.array((0., 60., 32.)), + np.array((180., 255., 255.))) +``` + +检查用户是否选择了区域: + +```py + # Check if the user has selected the region + if self.selection: + # Extract the coordinates of the selected rectangle + x0, y0, x1, y1 = self.selection + # Extract the tracking window + self.track_window = (x0, y0, x1-x0, y1-y0) +``` + +从 HSV 图像以及蒙版中提取感兴趣的区域。 根据以下这些计算感兴趣区域的直方图: + +```py + # Extract the regions of interest + hsv_roi = hsv[y0:y1, x0:x1] + mask_roi = mask[y0:y1, x0:x1] + # Compute the histogram of the region of + # interest in the HSV image using the mask + hist = cv2.calcHist( [hsv_roi], [0], mask_roi, + [16], [0, 180] ) +``` + +标准化直方图: + +```py + # Normalize and reshape the histogram + cv2.normalize(hist, hist, 0, 255, cv2.NORM_MINMAX) + self.hist = hist.reshape(-1) +``` + +从原始帧中提取感兴趣的区域: + +```py + # Extract the region of interest from the frame + vis_roi = vis[y0:y1, x0:x1] +``` + +计算感兴趣区域的按位非。 这仅用于显示目的: + +```py + # Compute the image negative (for display only) + cv2.bitwise_not(vis_roi, vis_roi) + vis[mask == 0] = 0 +``` + +检查系统是否处于跟踪模式: + +```py + # Check if the system in the "tracking" mode + if self.tracking_state == 1: + # Reset the selection variable + self.selection = None +``` + +计算直方图的反向投影: + +```py + # Compute the histogram back projection + hsv_backproj = cv2.calcBackProject([hsv], [0], + self.hist, [0, 180], 1) +``` + +计算直方图和掩码之间的按位与: + +```py + # Compute bitwise AND between histogram + # backprojection and the mask + hsv_backproj &= mask +``` + +定义跟踪器的终止条件: + +```py + # Define termination criteria for the tracker + term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1) +``` + +将 CAMShift 算法应用于反投影直方图: + +```py + # Apply CAMShift on 'hsv_backproj' + track_box, self.track_window = cv2.CamShift(hsv_backproj, self.track_window, term_crit) +``` + +在对象周围绘制一个椭圆并显示它: + +```py + # Draw an ellipse around the object + cv2.ellipse(vis, track_box, (0, 255, 0), 2) + # Show the output live video + cv2.imshow('Object Tracker', vis) +``` + +如果用户按下`Esc`,则退出循环: + +```py + # Stop if the user hits the 'Esc' key + c = cv2.waitKey(5) + if c == 27: + break +``` + +退出循环后,请确保正确关闭所有窗口: + +```py + # Close all the windows + cv2.destroyAllWindows() +``` + +定义`main`功能并开始跟踪: + +```py +if __name__ == '__main__': + # Start the tracker + ObjectTracker().start_tracking() +``` + +完整代码在提供给您的文件`camshift.py`中给出。 如果运行代码,您将看到一个窗口,显示来自网络摄像头的实时视频。 + +拿一个对象,将其握在手中,然后在其周围绘制一个矩形。 绘制矩形后,请确保将鼠标指针从最终位置移开。 该图像将如下所示: + +![](img/B15441_18_07.png) + +图 7:物体检测图像 + +完成选择后,将鼠标指针移动到另一个位置以锁定矩形。 该事件将开始跟踪过程,如下图所示: + +![](img/B15441_18_08.png) + +图 8:物体检测图像 2 + +让我们将对象移动到并查看是否仍在跟踪中 + +![](img/B15441_18_09.png) + +图 9:物体检测图 3 + +看起来运作良好。 您可以移动 obj 主题,以查看如何实时对其进行跟踪。 + +希望到现在为止,您已经看到了图像识别的许多应用的可能性,并且可能已经提出了关于如何应用到目前为止所学知识的自己的想法。 他们使用的技术可能比我们在本章中使用的技术要复杂一些,但是概念并没有什么不同。 NFL 使用这些技术将*实际上设置为*,在电视上设置 10 码标记,而美国职业棒球大联盟使用与我们在本节中学到的技术类似的技术来绘制打击区。 与我们在这里看到的最接近的示例是温网锦标赛用来确定网球着陆的位置并确定其进出的例子。 在下一节中,我们将学习基于光流的跟踪。 这是用于图像识别的有用技术。 + +# 基于光流的跟踪 + +2020 年 1 月,宣布由马丁·斯科塞斯(Martin Scorsese)执导的电影*爱尔兰人*被提名奥斯卡奖。 这部电影详细介绍了卡车司机,徒和队友弗兰克·希兰(Frank Sheeran)的生活(由罗伯特·德尼罗(Robert DeNiro)饰演)。 在电影中,我们看到希兰生活中的不同时期。 从他 20 多岁到 80 多岁。 整个过程中,我们在屏幕上看到 DeNiro 的时候很明显是他,你真的可以看出他是 20 岁还是 80 岁。 + +在以前的电影中,这可能是通过化妆实现的。 对于这部电影,没有为此目的使用化妆。 相反,他们使用特殊效果,并使用*数字*化妆修饰了 DeNiro 的脸。 太好了吧? + +多年来,用计算机制作逼真的面孔非常困难,但好莱坞及其特效艺术家终于破解了密码。 显然,他们正在使用比本章将介绍的技术更复杂的技术,但是光流技术是开始实现此功能的基础技术。 您必须能够在视频移动的任何时刻跟踪该人的脸,然后才能在视频中更改该人的脸。 这是光流可以解决的问题之一。 + +光流是计算机视觉中使用的一种流行技术。 它使用图像特征点来跟踪对象。 在实时视频的连续帧中跟踪各个特征点。 当我们在给定帧中检测到一组特征点时,我们将计算位移向量以对其进行跟踪。 我们显示了连续帧之间这些特征点的运动。 这些向量被称为运动向量。 有许多方法可以执行光流,但是 **Lucas-Kanade** 方法可能是最受欢迎的方法。 这是描述此技术的原始论文: + +[http://cseweb.ucsd.edu/classes/sp02/cse252/lucaskanade81.pdf](http://cseweb.ucsd.edu/classes/sp02/cse252/lucaskanade81.pdf ) + +第一步是从当前帧中提取特征点。 对于提取的每个特征点,将以特征点为中心创建一个 3×3 的像素块。 我们假设每个面片中的所有点都具有相似的运动。 该窗口的大小可以根据情况进行调整。 + +对于每个补丁,我们在前一帧中在其附近寻找匹配项。 我们根据错误指标选择最佳匹配。 搜索区域大于 3×3,因为我们寻找一堆不同的 3×3 色块以获得与当前色块最接近的那个。 一旦获得该信息,从当前补丁的中心点到前一帧中匹配的补丁的路径将成为运动矢量。 类似地,我们为所有其他补丁计算运动矢量。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import cv2 +import numpy as np +``` + +定义一个函数以使用光流开始跟踪。 首先初始化视频捕获对象和缩放因子: + +```py +# Define a function to track the object +def start_tracking(): + # Initialize the video capture object + cap = cv2.VideoCapture(0) + # Define the scaling factor for the frames + scaling_factor = 0.5 +``` + +定义要跟踪的帧数和要跳过的帧数: + +```py + # Number of frames to track + num_frames_to_track = 5 + # Skipping factor + num_frames_jump = 2 +``` + +初始化与跟踪路径和帧索引相关的变量: + +```py + # Initialize variables + tracking_paths = [] + frame_index = 0 +``` + +定义跟踪参数,例如窗口大小,最大级别和终止条件: + +```py + # Define tracking parameters + tracking_params = dict(winSize = (11, 11), maxLevel = 2, + criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, + 10, 0.03)) +``` + +无限期迭代,直到用户按下`Esc`键。 首先捕获当前帧并调整其大小: + +```py + # Iterate until the user hits the 'Esc' key + while True: + # Capture the current frame + _, frame = cap.read() + # Resize the frame + frame = cv2.resize(frame, None, fx=scaling_factor, + fy=scaling_factor, interpolation=cv2.INTER_AREA) +``` + +将帧从 RGB 转换为灰度: + +```py + # Convert to grayscale + frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) +``` + +创建框架的副本: + +```py + # Create a copy of the frame + output_img = frame.copy() +``` + +检查跟踪路径的长度是否大于零: + +```py + if len(tracking_paths) > 0: + # Get images + prev_img, current_img = prev_gray, frame_gray +``` + +组织特征点: + +```py + # Organize the feature points + feature_points_0 = np.float32([tp[-1] for tp in \ + tracking_paths]).reshape(-1, 1, 2) +``` + +使用特征点和跟踪参数,根据先前图像和当前图像计算光流: + +```py + # Compute optical flow + feature_points_1, _, _ = cv2.calcOpticalFlowPyrLK( + prev_img, current_img, feature_points_0, + None, **tracking_params) + # Compute reverse optical flow + feature_points_0_rev, _, _ = cv2.calcOpticalFlowPyrLK( + current_img, prev_img, feature_points_1, + None, **tracking_params) + # Compute the difference between forward and + # reverse optical flow + diff_feature_points = abs(feature_points_0 – \ + feature_points_0_rev).reshape(-1, 2).max(-1) +``` + +提取好的特征点: + +```py + # Extract the good points + good_points = diff_feature_points < 1 +``` + +初始化新跟踪路径的变量: + +```py + # Initialize variable + new_tracking_paths = [] +``` + +遍历所有好的特征点并在它们周围绘制圆圈: + +```py + # Iterate through all the good feature points + for tp, (x, y), good_points_flag in zip(tracking_paths, + feature_points_1.reshape(-1, 2), good_points): + # If the flag is not true, then continue + if not good_points_flag: + continue +``` + +附加 *X* 和 *Y* 坐标,并且不要超过我们应该跟踪的帧数: + +```py + # Append the X and Y coordinates and check if + # its length greater than the threshold + tp.append((x, y)) + if len(tp) > num_frames_to_track: + del tp[0] + new_tracking_paths.append(tp) +``` + +围绕该点画一个圆。 更新跟踪路径并使用新的跟踪路径绘制线条以显示运动: + +```py + # Draw a circle around the feature points + cv2.circle(output_img, (x, y), 3, (0, 255, 0), -1) + # Update the tracking paths + tracking_paths = new_tracking_paths + # Draw lines + cv2.polylines(output_img, [np.int32(tp) for tp in \ + tracking_paths], False, (0, 150, 0)) +``` + +跳过之前指定的帧数后,进入`if`条件: + +```py + # Go into this 'if' condition after skipping the + # right number of frames + if not frame_index % num_frames_jump: + # Create a mask and draw the circles + mask = np.zeros_like(frame_gray) + mask[:] = 255 + for x, y in [np.int32(tp[-1]) for tp in tracking_paths]: + cv2.circle(mask, (x, y), 6, 0, -1) +``` + +使用内置函数以及蒙版,最大拐角,质量级别,最小距离和块大小等参数,计算要跟踪的良好功能: + +```py + # Compute good features to track + feature_points = cv2.goodFeaturesToTrack(frame_gray, + mask = mask, maxCorners = 500, qualityLevel = 0.3, + minDistance = 7, blockSize = 7) +``` + +如果特征点存在,请将其附加到跟踪路径: + +```py + # Check if feature points exist. If so, append them + # to the tracking paths + if feature_points is not None: + for x, y in np.float32(feature_points).reshape(-1, 2): + tracking_paths.append([(x, y)]) +``` + +更新与帧索引和以前的灰度图像有关的变量: + +```py + # Update variables + frame_index += 1 + prev_gray = frame_gray +``` + +显示输出: + +```py + # Display output + cv2.imshow('Optical Flow', output_img) +``` + +检查用户是否按下了`Esc`键。 如果是这样,请退出循环: + +```py + # Check if the user hit the 'Esc' key + c = cv2.waitKey(1) + if c == 27: + break +``` + +定义`main`功能并开始跟踪。 停止跟踪器后,请确保所有窗口均已正确关闭: + +```py +if __name__ == '__main__': + # Start the tracker + start_tracking() + # Close all the windows + cv2.destroyAllWindows() +``` + +完整代码在提供给您的文件`optical_flow.py`中给出。 如果运行代码,您将看到一个显示实时视频的窗口。 您将看到特征点,如以下屏幕截图所示: + +![](img/B15441_18_10.png) + +图 10:对象跟踪图像 + +如果四处走动,将看到显示这些特征点移动的线: + +![](img/B15441_18_11.png) + +图 11:对象跟踪图像 + +如果您随后沿相反方向移动,则 wi ll 线也会相应地更改其方向: + +![](img/B15441_18_12.png) + +图 12:对象跟踪图像 + +看到了的输出后,我们继续下一部分:*人脸检测和跟踪*。 + +# 人脸检测和追踪 + +面部检测是指在给定图像中检测面部的位置。 这通常与面部识别相混淆,面部识别是识别人物身份的过程。 典型的生物特征识别系统利用面部检测和面部识别两者来执行任务。 它使用人脸检测来定位人脸,然后使用人脸识别来识别人。 在本节中,我们将看到如何自动来自动检测实时视频中人脸的位置并对其进行跟踪。 + +## 使用 Haar 级联进行对象检测 + +我们将使用 Haar 级联来检测示例视频中的人脸。 在这种情况下,Haar 级联是指基于 Haar 特征的级联分类器。 *Paul Viola* 和 *Michael Jones* 于 2001 年在他们的标志性研究论文中首次提出了这种对象检测方法。您可以在这里查看: + +[https://www.cs.cmu.edu/~efros/courses/LBMV07/Papers/viola-cvpr-01.pdf](https://www.cs.cmu.edu/~efros/courses/LBMV07/Papers/viola-cvpr-01.pdf ) + +他们在论文中描述了一种有效的机器学习技术,可用于检测任何物体。 + +他们使用简单分类器的增强级联。 该级联用于构建以高精度执行的整体分类器。 之所以相关,是因为它帮助我们规避了构建具有较高准确性的单步分类器的过程。 建立这样一个健壮的单步分类器是一个计算量大的过程。 + +考虑一个示例,在该示例中我们必须检测一个物体,例如网球。 为了构建检测器,我们需要一个可以了解网球外观的系统。 它应该能够推断出给定的图像是否包含网球。 我们需要使用很多网球图像来训练该系统。 我们还需要很多不包含网球的图像。 这有助于系统学习如何区分对象。 + +如果我们建立一个准确的模型,它将是复杂的。 因此,我们将无法实时运行它。 如果太简单,则可能不准确。 在机器学习领域中,经常会在速度和准确性之间进行权衡。 Viola-Jones 方法通过构建一组简单的分类器克服了这个问题。 然后将这些分类器级联到或健壮且准确的统一分类器中。 + +让我们看看如何使用它来执行面部检测。 为了构建用于检测人脸的机器学习系统,我们首先需要构建特征提取器。 机器学习算法将使用这些功能来了解人脸。 这就是 Haar 功能变得相关的地方。 + +它们只是图像上补丁的简单总结和差异。 Haar 功能易于计算。 为了使其具有强大的缩放能力,我们在多种图像尺寸下执行此操作。 如果您想以教程格式了解更多信息,可以查看以下链接: + +[http://www.cs.ubc.ca/~lowe/425/slides/13-ViolaJones.pdf](http://www.cs.ubc.ca/~lowe/425/slides/13-ViolaJones.pdf ) + +提取特征后,我们将其传递给简单分类器的增强级联。 我们检查图像中的各个矩形子区域,并继续丢弃不包含面部的区域。 我们很高兴迅速得出最终答案。 为了高效地计算这些特征 qu ,他们使用了称为积分图像的概念。 + +## 使用积分图像进行特征提取 + +为了计算 Haar 特征,我们必须计算图像中许多子区域的总和和差。 我们需要在多个尺度上计算这些求和和差异,这使其成为计算密集型过程。 为了构建实时系统,我们使用积分图像。 考虑下图: + +![](img/B15441_18_13.png) + +图 13:ABCD 区域 + +如果要计算此图像中矩形 **ABCD** 的总和,则无需遍历该矩形区域中的每个像素。 假设 **OC** 表示由矩形的左上角 **O** 和矩形的对角相对角点 **C** 形成的矩形区域。 要计算矩形 **ABCD** 的面积,我们可以使用以下公式: + +*矩形 ABCD 的面积= OC –(OB + OD – OA)* + +这个公式有何特别之处? 如果您注意到了,我们不必进行任何迭代或重新计算任何矩形区域。 等式右侧的所有值均已可用,因为它们是在较早的循环中计算的。 我们直接使用它们来计算此矩形的面积。 我们有效地做的是考虑一个较大的矩形,其中 **O** 和 **C** 代表相对的对角线,然后我们“切出”白色部分以仅留下蓝色区域。 考虑到这一点,让我们看看如何构建一个面部检测器。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import cv2 +import numpy as np +``` + +加载与面部检测相对应的 Haar 级联文件: + +```py +# Load the Haar cascade file +face_cascade = cv2.CascadeClassifier( + 'haar_cascade_files/haarcascade_frontalface_default.xml' +# Check if the cascade file has been loaded correctly +if face_cascade.empty(): + raise IOError('Unable to load the face cascade classifier xml file') +``` + +初始化视频捕获对象并定义缩放比例: + +```py +# Initialize the video capture object +cap = cv2.VideoCapture(0) +# Define the scaling factor +scaling_factor = 0.5 +``` + +无限期地迭代,直到用户按下`Esc`键。 捕获当前帧: + +```py +# Iterate until the user hits the 'Esc' key +while True: + # Capture the current frame + _, frame = cap.read() +``` + +调整框架大小: + +```py + # Resize the frame + frame = cv2.resize(frame, None, + fx=scaling_factor, fy=scaling_factor, + interpolation=cv2.INTER_AREA) +``` + +将图像转换为灰度: + +```py + # Convert to grayscale + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) +``` + +在灰度图像上运行面部检测器: + +```py + # Run the face detector on the grayscale image + face_rects = face_cascade.detectMultiScale(gray, 1.3, 5) +``` + +遍历检测到的面部并在其周围绘制矩形: + +```py + # Draw a rectangle around the face + for (x,y,w,h) in face_rects: + cv2.rectangle(frame, (x,y), (x+w,y+h), (0,255,0), 3) +``` + +显示输出: + +```py + # Display the output + cv2.imshow('Face Detector', frame) +``` + +检查用户是否按下了`Esc`键。 如果是这样,请退出循环: + +```py + # Check if the user hit the 'Esc' key + c = cv2.waitKey(1) + if c == 27: + break +``` + +退出循环后,请确保释放视频捕获对象并正确关闭所有窗口: + +```py +# Release the video capture object +cap.release() +# Close all the windows +cv2.destroyAllWindows() +``` + +完整代码为提供给您的文件`face_detector.py`中提供的。 如果运行代码,您将看到以下内容: + +![](img/B15441_18_14.png) + +图 14:人脸检测图像 + +从面部检测,我们将在下一部分中转到类似的概念:*眼睛检测和跟踪*。 + +# 眼睛检测和跟踪 + +眼睛检测与面部检测相似。 我们将使用眼睛级联文件,而不是使用面部级联文件。 创建一个新的 Python 文件并导入以下软件包: + +```py +import cv2 +import numpy as np +``` + +加载与面部和眼睛检测相对应的 Haar 级联文件: + +```py +# Load the Haar cascade files for face and eye +face_cascade = cv2.CascadeClassifier('haar_cascade_files/haarcascade_frontalface_default.xml') +eye_cascade = cv2.CascadeClassifier('haar_cascade_files/haarcascade_eye.xml') +# Check if the face cascade file has been loaded correctly +if face_cascade.empty(): + raise IOError('Unable to load the face cascade classifier xml file') +# Check if the eye cascade file has been loaded correctly +if eye_cascade.empty(): + raise IOError('Unable to load the eye cascade classifier xml file') +``` + +初始化视频捕获对象并定义缩放比例: + +```py +# Initialize the video capture object +cap = cv2.VideoCapture(0) +# Define the scaling factor +ds_factor = 0.5 +``` + +无限期重复,直到用户按下`Esc`键: + +```py +# Iterate until the user hits the 'Esc' key +while True: + # Capture the current frame + _, frame = cap.read() +``` + +调整框架大小: + +```py + # Resize the frame + frame = cv2.resize(frame, None, fx=ds_factor, fy=ds_factor, interpolation=cv2.INTER_AREA) +``` + +将帧从 RGB 转换为灰度: + +```py + # Convert to grayscale + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) +``` + +运行面部检测器: + +```py + # Run the face detector on the grayscale image + faces = face_cascade.detectMultiScale(gray, 1.3, 5) +``` + +对于检测到的每个脸部,在该区域内运行眼睛检测器: + +```py + # For each face that's detected, run the eye detector + for (x,y,w,h) in faces: + # Extract the grayscale face ROI + roi_gray = gray[y:y+h, x:x+w] +``` + +提取感兴趣的区域并运​​行眼睛检测器: + +```py + # Extract the color face ROI + roi_color = frame[y:y+h, x:x+w] + # Run the eye detector on the grayscale ROI + eyes = eye_cascade.detectMultiScale(roi_gray) +``` + +在眼睛周围绘制圆圈并显示输出: + +```py + # Draw circles around the eyes + for (x_eye,y_eye,w_eye,h_eye) in eyes: + center = (int(x_eye + 0.5*w_eye), int(y_eye + 0.5*h_eye)) + radius = int(0.3 * (w_eye + h_eye)) + color = (0, 255, 0) + thickness = 3 + cv2.circle(roi_color, center, radius, color, thickness) + # Display the output + cv2.imshow('Eye Detector', frame) +``` + +如果用户按下`Esc`键,则退出循环: + +```py + # Check if the user hit the 'Esc' key + c = cv2.waitKey(1) + if c == 27: + break +``` + +退出循环后,请确保释放视频捕获对象并关闭所有窗口: + +```py +# Release the video capture object +cap.release() +# Close all the windows +cv2.destroyAllWindows() +``` + +完整代码在提供给您的文件`eye_detector.py`中给出。 如果运行代码,您将看到类似以下内容: + +![](img/B15441_18_15.png) + +图 15:眼睛检测图像 + +窃听上一节中的想法,我们可以使用在本节中学到的技术为电影(或胡须或胡须等)中的屏幕角色添加眼镜。 + +我想到的另一个应用程序是跟踪卡车驾驶员的眼睛,并确定他们眨眼或闭上眼睛的速度,以查看他们是否感到疲倦,并要求他们(可能是强迫他们)翻身。 + +本章旨在演示如何在各种应用中使用图像识别。 我们期待您的来信,并结合您自己的想法,学习如何应用本章中学到的技术。 + +# 摘要 + +在本章中,我们学习了对象检测和跟踪。 我们了解了如何在各种操作系统上安装具有 Python 支持的 OpenCV。 我们了解了帧差异,并用它来检测视频中的运动部分。 我们讨论了如何使用色彩空间跟踪人类皮肤。 我们讨论了背景减法以及如何将其用于跟踪静态场景中的对象。 我们使用 CAMShift 算法构建了一个交互式对象跟踪器。 + +我们学习了如何构建基于光流的跟踪器。 我们讨论了面部检测技术,并了解了 Haar 级联和积分图像的概念。 我们使用这种技术来构建眼睛检测器和跟踪器。 + +在下一章中,我们将讨论人工神经网络,并使用这些技术来构建光学字符识别引擎。 \ No newline at end of file diff --git a/docs/ai-py/19.md b/docs/ai-py/19.md new file mode 100644 index 0000000000000000000000000000000000000000..d1c8452d9037765b7367e557538c17b6490b844b --- /dev/null +++ b/docs/ai-py/19.md @@ -0,0 +1,922 @@ +# 19 + +# 神经网络 + +在本章中,我们将学习神经网络。 我们将从神经网络的介绍和相关库的安装开始。 然后,我们将讨论感知器以及如何基于它们构建分类器。 之后,我们将更深入地学习单层神经网络和多层神经网络。 + +稍后,我们将看到如何使用神经网络构建矢量量化器。 我们将使用递归神经网络分析顺序数据,最后将使用神经网络构建光学字符识别引擎。 在本章的最后,我们将介绍: + +* 神经网络简介 +* 构建基于感知器的分类器 +* 构建单层神经网络 +* 构建多层神经网络 +* 构建矢量量化器 +* 使用递归神经网络分析顺序数据 +* 在**光学字符识别**( **OCR** )数据库中可视化字符 +* 构建**光学字符识别**( **OCR** )引擎 + +让我们开始介绍神经网络。 + +# 神经网络介绍 + +人工智能的基本前提之一是构建可以执行通常需要人类智能的任务的系统。 人脑在学习新概念方面非常了不起。 为什么不使用人脑模型来构建系统? 神经网络是一种旨在宽松地模拟人脑学习过程的模型。 + +神经网络的设计使其可以识别数据中的基本模式并从中学习。 它们可用于各种任务,例如分类,回归和细分。 神经网络的一个缺点是,在将给定数据输入神经网络之前,我们需要将其转换为数字格式。 例如,我们处理许多不同类型的数据,包括视觉,文本和时间序列。 我们需要弄清楚如何以神经网络可以理解的方式表示问题。 为了理解这一过程,让我们首先考虑如何构建神经网络,然后如何训练神经网络。 + +## 建立神经网络 + +人类学习过程的某些组成部分是分层的。 我们大脑神经网络中有多个部分,每个阶段对应不同的粒度。 有些部分学习简单的东西,有些部分学习更复杂的东西。 让我们考虑视觉上识别对象的示例。 + +当我们看着一个盒子时,大脑的第一部分可能会识别出简单的事物,例如角落和边缘。 下一部分将标识通用形状,其后的部分将标识其是哪种对象。 对于不同的大脑功能,此过程可能有所不同,但是您可以理解。 使用这种层次结构,人脑可以分离任务并识别给定的对象。 + +为了模拟人脑的学习过程,使用神经元层构建了神经网络。 这些神经元受到我们在上一段中讨论的生物神经元的启发。 神经网络中的每一层都是一组独立的输入神经元。 一层中的每个神经元都与相邻层 r 中的神经元相连。 + +## 训练神经网络 + +如果我们要用 *N* 维输入数据处理,则输入层将由 *N* 个神经元组成。 如果我们在训练数据中具有 *M* 个不同的类,则输出层将包含 *M* 个神经元。 输入和输出层之间的层称为隐藏层。 一个简单的神经网络将由两层组成,而一个深度神经网络将由许多层组成。 + +那么如何使用神经网络对数据进行分类呢? 第一步是收集适当的培训数据并贴上标签。 每个神经元都充当简单函数,并且神经网络会自我训练,直到误差降至某个阈值以下。 + +误差是预测输出与实际输出之间的差。 基于误差有多大,神经网络会自行调整并重新训练,直到其更接近可解度。 + +足够抽象地思考神经网络。 就像我们在本书中一直在做的那样,是时候动手动手,边做边学。 在本章中,我们将使用名为 **NeuroLab** 的库。 NeuroLab 是库,它实现了基本的神经网络算法。 它具有各种参数,可以对其进行配置。 其界面类似于 **MATLAB** 中的**神经网络工具箱**( **NNT** )软件包。 该库是基于 NumPy 软件包的。 您可以在以下位置找到有关它的更多信息: + +[https://pythonhosted.org/neurolab](https://pythonhosted.org/neurolab ) + +您可以通过在终端上运行以下命令来安装它: + +```py +$ pip3 install neurolab +``` + +安装后,您可以继续进行下一部分,在此我们将构建基于 Perceptron 的分类器。 + +# 构建基于感知器的分类器 + +神经元,树突和轴突构成了大脑的组成部分。 同样,感知器是神经网络中最基本的结构。 + +神经网络的发展经历了许多变化。 他们的发展基于圣地亚哥·拉蒙·卡哈尔和查尔斯·斯科特·谢灵顿爵士所做的神经系统工作。 拉蒙·卡哈尔(Ramon y Cajal)是探索神经组织结构的先驱,并证明: + +* 神经元可以互相交流 +* 神经元与其他神经元在物理上是分开的 + +利用 Ramon y Cajal 和 Sherrington,Warren McCulloch 和 Walter Pitts 在 1943 年的论文中进行的研究,*神经活动固有的逻辑思想*描述了一种结构,该结构借鉴了具有二进制阈值激活的神经元的结构。 功能类似于一阶逻辑语句。 + +以下是 McCulloch 和 Pitts 神经元的基本表示,也称为感知器: + +![](img/B15441_19_01.png) + +图 1:基本的 Perceptron 功能 + +因此,感知器是许多神经网络的基本构建块。 它接受输入,对其进行计算,然后产生输出。 它使用简单的线性函数进行决策。 假设我们正在处理 *N* 维输入数据点。 感知器计算这些 *N* 个数字的加权总和,然后添加一个常数以产生输出。 该常数称为神经元的偏差。 值得注意的是,这些简单的感知器可用于设计复杂的深度神经网络。 + +在本章中,我们将看到如何使用这种基本结构进行机器学习。 在后面的章节中,我们将看到更复杂的示例以及神经网络的一些有趣应用。 许多神经网络的核心,无论它们多么复杂,都利用了感知器的简单概念。 这就是为什么对这个主题有一个透彻的了解很重要的原因。 让我们看看如何使用 NeuroLab 构建基于 Perceptron 的分类器。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +import neurolab as nl +``` + +从提供给您的文本文件`data_perceptron.txt`中加载输入数据。 每行包含用空格分隔的数字,其中前两个数字是要素,最后一个数字是标签: + +```py +# Load input data +text = np.loadtxt('data_perceptron.txt') +``` + +将文本分为数据点和标签: + +```py +# Separate datapoints and labels +data = text[:, :2] +labels = text[:, 2].reshape((text.shape[0], 1)) +``` + +绘制数据点: + +```py +# Plot input data +plt.figure() +plt.scatter(data[:,0], data[:,1]) +plt.xlabel('Dimension 1') +plt.ylabel('Dimension 2') +plt.title('Input data') +``` + +定义每个尺寸可以采用的最大值和最小值: + +```py +# Define minimum and maximum values for each dimension +dim1_min, dim1_max, dim2_min, dim2_max = 0, 1, 0, 1 +``` + +由于数据分为两类,我们只需要一位即可代表输出。 因此,输出层将包含单个神经元。 + +```py +# Number of neurons in the output layer +num_output = labels.shape[1] +``` + +我们有一个数据集,其中的数据点是二维的。 让我们定义一个具有两个输入神经元的感知器,在其中为每个维度分配一个神经元。 + +```py +# Define a perceptron with 2 input neurons (because we +# have 2 dimensions in the input data) +dim1 = [dim1_min, dim1_max] +dim2 = [dim2_min, dim2_max] +perceptron = nl.net.newp([dim1, dim2], num_output) +``` + +用训练数据训练感知器: + +```py +# Train the perceptron using the data +error_progress = perceptron.train(data, labels, epochs=100, show=20, lr=0.03) +``` + +使用误差度量绘制训练进度: + +```py +# Plot the training progress +plt.figure() +plt.plot(error_progress) +plt.xlabel('Number of epochs') +plt.ylabel('Training error') +plt.title('Training error progress') +plt.grid() +plt.show() +``` + +文件`perceptron_classifier.py`中提供了完整代码。 如果运行代码,您将获得两个输出屏幕截图。 第一个屏幕截图显示了输入数据点: + +![](img/B15441_19_02.png) + +图 2:培训进度图 + +第二张屏幕截图使用错误指标表示培训进度: + +![](img/B15441_19_03.png) + +图 3:训练误差图 + +我们可以从前面的屏幕截图中观察到,在第四个时期结束时,错误降至 **0** ,这正是我们想要发生的情况。 如果错误为 **0** ,则无法进一步改善。 在下一部分中,我们将增强模型并创建单层神经 网络。 + +# 构建单层神经网络 + +建立一个带有几个感知器的模型是一个好的开始,它使我们对这个令人兴奋的概念有了基本的了解,但是要真正解决问题,这种简单的模型是不够的。 人脑大约有 850 亿个神经元。 我们不会建立具有这么多节点的神经网络,但是这个数字使您了解解决复杂问题所需的方法。 在建立具有数十亿个节点的模型之前,让我们进行下一步以建立具有单层的网络。 这个单层神经网络由作用于输入数据以产生输出的独立神经元组成。 让我们开始吧。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +import neurolab as nl +``` + +我们将使用提供给您的文件`data_simple_nn.txt`中的输入数据。 该文件中的每一行都包含四个数字。 前两个数字构成数据点,后两个数字是标签。 为什么我们需要为标签分配两个数字? 因为我们在数据集中有四个不同的类,所以我们需要两位来表示它们。 让我们继续加载数据: + +```py +# Load input data +text = np.loadtxt('data_simple_nn.txt') +``` + +将数据分为数据点和标签: + +```py +# Separate it into datapoints and labels +data = text[:, 0:2] +labels = text[:, 2:] +``` + +绘制输入数据: + +```py +# Plot input data +plt.figure() +plt.scatter(data[:,0], data[:,1]) +plt.xlabel('Dimension 1') +plt.ylabel('Dimension 2') +plt.title('Input data') +``` + +提取每个维度的最小值和最大值(我们不需要像上一节中那样对其进行硬编码): + +```py +# Minimum and maximum values for each dimension +dim1_min, dim1_max = data[:,0].min(), data[:,0].max() +dim2_min, dim2_max = data[:,1].min(), data[:,1].max() +``` + +定义输出层中的神经元数量: + +```py +# Define the number of neurons in the output layer +num_output = labels.shape[1] +``` + +使用以上参数定义单层神经网络: + +```py +# Define a single-layer neural network +dim1 = [dim1_min, dim1_max] +dim2 = [dim2_min, dim2_max] +nn = nl.net.newp([dim1, dim2], num_output) +``` + +使用训练数据训练神经网络: + +```py +# Train the neural network +error_progress = nn.train(data, labels, epochs=100, show=20, lr=0.03) +``` + +绘制培训进度: + +```py +# Plot the training progress +plt.figure() +plt.plot(error_progress) +plt.xlabel('Number of epochs') +plt.ylabel('Training error') +plt.title('Training error progress') +plt.grid() +plt.show() +``` + +定义一些样本测试数据点,并在这些点上运行网络: + +```py +# Run the classifier on test datapoints +print('\nTest results:') +data_test = [[0.4, 4.3], [4.4, 0.6], [4.7, 8.1]] +for item in data_test: + print(item, '-->', nn.sim([item])[0]) +``` + +完整代码在文件`simple_neural_network.py`中给出。 如果运行代码,您将获得两个屏幕截图。 第一个屏幕截图表示输入数据点: + +![](img/B15441_19_04.png) + +图 4:数据点图 + +第二张屏幕截图显示了培训进度: + +![](img/B15441_19_05.png) + +图 5:培训进度图 + +关闭图形后,您将看到以下输出: + +![](img/B15441_19_06.png) + +图 6:训练纪元 + +正如我们在*图 5* 中看到的那样,错误迅速开始减少,这表明我们的训练有效地创造了越来越好的预测。 在这种情况下,错误不会降为零。 但是如果我们让模型再运行几个纪元,我们预计误差将继续减少。 如果将这些测试数据点定位在 2D 图形上,则可以直观地验证预测输出的正确性。 + +# 构建多层神经网络 + +因此,我们将模型从几个节点增强到了一个单层,但距离 850 亿个节点还差得很远。 我们也不会在本节中谈到这一点,但让我们朝着正确的方向迈出又一步。 人脑不使用单层模型。 一些神经元的输出变成其他神经元的输入,依此类推。 具有这种特征的模型被称为多层神经网络。 这种类型的体系结构可产生更高的精度,并且使我们能够解决更复杂,更多样化的问题。 让我们看看如何使用 NeuroLab 构建多层神经网络。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +import neurolab as nl +``` + +在前两节中,我们看到了如何使用神经网络作为分类器。 在本节中,我们将看到如何使用多层神经网络作为回归器。 根据公式 *y = 3x ^ 2 + 5* 生成一些样本数据点,然后将这些点归一化: + +```py +# Generate some training data +min_val = -15 +max_val = 15 +num_points = 130 +x = np.linspace(min_val, max_val, num_points) +y = 3 * np.square(x) + 5 +y /= np.linalg.norm(y) +``` + +重塑前面的变量以创建训练数据集: + +```py +# Create data and labels +data = x.reshape(num_points, 1) +labels = y.reshape(num_points, 1) +``` + +绘制输入数据: + +```py +# Plot input data +plt.figure() +plt.scatter(data, labels) +plt.xlabel('Dimension 1') +plt.ylabel('Dimension 2') +plt.title('Input data') +``` + +定义具有两个隐藏层的多层神经网络。 您可以根据需要自由设计神经网络。 对于这种情况,我们在第一层使用`10`神经元,在第二层使用`6`神经元。 我们的任务是预测值,因此输出层将包含单个神经元: + +```py +# Define a multilayer neural network with 2 hidden layers; +# First hidden layer consists of 10 neurons +# Second hidden layer consists of 6 neurons +# Output layer consists of 1 neuron +nn = nl.net.newff([[min_val, max_val]], [10, 6, 1]) +``` + +将训练算法设置为梯度下降: + +```py +# Set the training algorithm to gradient descent +nn.trainf = nl.train.train_gd +``` + +使用生成的训练数据训练神经网络: + +```py +# Train the neural network +error_progress = nn.train(data, labels, epochs=2000, show=100, goal=0.01) +``` + +在训练数据点上运行神经网络: + +```py +# Run the neural network on training datapoints +output = nn.sim(data) +y_pred = output.reshape(num_points) +``` + +绘制培训进度: + +```py +# Plot training error +plt.figure() +plt.plot(error_progress) +plt.xlabel('Number of epochs') +plt.ylabel('Error') +plt.title('Training error progress') +``` + +绘制预测输出: + +```py +# Plot the output +x_dense = np.linspace(min_val, max_val, num_points * 2) +y_dense_pred = nn.sim(x_dense.reshape(x_dense.size,1)).reshape(x_dense.size) +plt.figure() +plt.plot(x_dense, y_dense_pred, '-', x, y, '.', x, y_pred, 'p') +plt.title('Actual vs predicted') +plt.show() +``` + +完整代码在文件`multilayer_neural_network.py`中给出。 如果运行代码,您将获得三个屏幕截图。 第一个屏幕截图显示了输入数据: + +![](img/B15441_19_07.png) + +图 7:输入数据图 + +第二张屏幕截图显示了培训进度: + +![](img/B15441_19_08.png) + +图 8:培训进度图 + +第三个屏幕截图显示了覆盖在输入数据之上的预测输出: + +![](img/B15441_19_09.png) + +图 9:覆盖输入数据的输出图 + +预测的输出似乎在一定程度上接近实际输入。 如果继续训练网络并减少错误,您将看到预测输出将与输入曲线相匹配,即使重新仍很准确。 + +您还应该看到打印出以下内容: + +![](img/B15441_19_10.png) + +图 10:训练纪元 + +在前面的部分中,我们学习了如何构建基本的神经网络以及对基础知识的牢固掌握和理解。 在下一节中,我们将继续学习如何构建神经网络。 现在,我们将学习如何使用 vect 或量化器构建神经网络。 + +# 建立向量量化器 + +**矢量量化**是一种量化技术,其中输入数据由固定数量的代表点表示。 它是 *N* 维的四舍五入数字。 此技术通常用于多个领域,例如语音/图像识别,语义分析和图像/语音压缩。 最佳矢量量化理论的历史可以追溯到 1950 年代的 Bell Labs,在那里进行了研究以使用离散化程序优化信号传输。 矢量量化器神经网络的一个优点是它们具有很高的解释性。 让我们看看如何构建向量 c。 + +由于当前版本的 NeuroLab(v。0.3.5)的某些问题,运行以下代码将引发错误。 幸运的是,有了此修复程序,但其中涉及对 NeuroLab 软件包进行更改。 将 NeuroLab 软件包(`layer_out.np['w'][n][st:i].fill(1.0)`)中`net.py`文件的 **179 行**更改为`layer_out.np['w'][n][int(st):int(i)].fill(1.0))`应该可以解决此问题。 要求读者使用此替代方法,直到在 Neuro Neuro 官方软件包中实施修复为止。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +import neurolab as nl +``` + +从文件`data_vector_quantization.txt`加载输入数据。 该文件中的每一行都包含六个数字。 前两个数字形成数据点,后四个数字形成一个热编码标签。 总体上有四个课程。 + +```py +# Load input data +text = np.loadtxt('data_vector_quantization.txt') +``` + +将文本分为数据和标签: + +```py +# Separate it into data and labels +data = text[:, 0:2] +labels = text[:, 2:] +``` + +定义一个具有两层的神经网络,其中输入层有`10`神经元,输出层有`4`神经元: + +```py +# Define a neural network with 2 layers: +# 10 neurons in input layer and 4 neurons in output layer +num_input_neurons = 10 +num_output_neurons = 4 +weights = [1/num_output_neurons] * num_output_neurons +nn = nl.net.newlvq(nl.tool.minmax(data), num_input_neurons, weights) +``` + +使用训练数据训练神经网络: + +```py +# Train the neural network +_ = nn.train(data, labels, epochs=500, goal=-1) +``` + +为了可视化输出集群,让我们创建一个点网格: + +```py +# Create the input grid +xx, yy = np.meshgrid(np.arange(0, 10, 0.2), np.arange(0, 10, 0.2)) +xx.shape = xx.size, 1 +yy.shape = yy.size, 1 +grid_xy = np.concatenate((xx, yy), axis=1) +``` + +使用神经网络评估点的网格: + +```py +# Evaluate the input grid of points +grid_eval = nn.sim(grid_xy) +``` + +提取四个类: + +```py +# Define the 4 classes +class_1 = data[labels[:,0] == 1] +class_2 = data[labels[:,1] == 1] +class_3 = data[labels[:,2] == 1] +class_4 = data[labels[:,3] == 1] +``` + +提取与这四个类相对应的网格: + +```py +# Define X-Y grids for all the 4 classes +grid_1 = grid_xy[grid_eval[:,0] == 1] +grid_2 = grid_xy[grid_eval[:,1] == 1] +grid_3 = grid_xy[grid_eval[:,2] == 1] +grid_4 = grid_xy[grid_eval[:,3] == 1] +``` + +绘制输出: + +```py +# Plot the outputs +plt.plot(class_1[:,0], class_1[:,1], 'ko', + class_2[:,0], class_2[:,1], 'ko', + class_3[:,0], class_3[:,1], 'ko', + class_4[:,0], class_4[:,1], 'ko') +plt.plot(grid_1[:,0], grid_1[:,1], 'm.', + grid_2[:,0], grid_2[:,1], 'bx', + grid_3[:,0], grid_3[:,1], 'c^', + grid_4[:,0], grid_4[:,1], 'y+') +plt.axis([0, 10, 0, 10]) +plt.xlabel('Dimension 1') +plt.ylabel('Dimension 2') +plt.title('Vector quantization') +plt.show() +``` + +完整代码在文件`vector_quantizer.py`中给出。 如果运行代码,将获得以下屏幕截图,其中显示了输入数据点和集群之间的边界: + +![](img/B15441_19_11.png) + +图 11:输入数据点和集群之间边界的图 + +您还应该看到以下输出: + +![](img/B15441_19_12.png) + +图 12:训练纪元 + +在上一节中,我们学习了如何使用矢量量化器构建神经网络。 在下一节中,我们将继续学习神经网络。 接下来,我们将学习如何使用递归神经 网络(RNN)分析顺序数据。 + +# 使用递归神经网络分析顺序数据 + +到目前为止,在我们所有的神经网络示例中,一直在使用静态数据。 神经网络也可以有效地用于构建处理顺序数据的模型。 **递归神经网络**( **RNN** )在建模顺序数据方面非常出色。 您可以在以下位置了解有关递归神经网络的更多信息: + +[https://www.jeremyjordan.me/introduction-to-recurrent-neural-networks/](https://www.jeremyjordan.me/introduction-to-recurrent-neural-networks/ ) + +当我们使用时间序列数据时,我们通常不能使用通用学习模型。 我们需要捕获数据中的时间依赖性,以便可以构建健壮的模型。 让我们看看如何构建它。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +import neurolab as nl +``` + +定义一个函数来生成波形。 首先定义四个正弦波: + +```py +def get_data(num_points): + # Create sine waveforms + wave_1 = 0.5 * np.sin(np.arange(0, num_points)) + wave_2 = 3.6 * np.sin(np.arange(0, num_points)) + wave_3 = 1.1 * np.sin(np.arange(0, num_points)) + wave_4 = 4.7 * np.sin(np.arange(0, num_points)) +``` + +为整个波形创建变化的幅度: + +```py + # Create varying amplitudes + amp_1 = np.ones(num_points) + amp_2 = 2.1 + np.zeros(num_points) + amp_3 = 3.2 * np.ones(num_points) + amp_4 = 0.8 + np.zeros(num_points) +``` + +创建整体波形: + +```py + wave = np.array([wave_1, wave_2, wave_3, wave_4]).reshape(num_points * 4, 1) + amp = np.array([[amp_1, amp_2, amp_3, amp_4]]).reshape(num_points * 4, 1) + return wave, amp +``` + +定义一个函数以可视化神经网络的输出: + +```py +# Visualize the output +def visualize_output(nn, num_points_test): + wave, amp = get_data(num_points_test) + output = nn.sim(wave) + plt.plot(amp.reshape(num_points_test * 4)) + plt.plot(output.reshape(num_points_test * 4)) +``` + +定义`main`功能并创建一个波形: + +```py +if __name__=='__main__': + # Create some sample data + num_points = 40 + wave, amp = get_data(num_points) +``` + +创建一个具有两层的递归神经网络: + +```py + # Create a recurrent neural network with 2 layers + nn = nl.net.newelm([[-2, 2]], [10, 1], [nl.trans.TanSig(), nl.trans.PureLin()]) +``` + +为每层设置初始化函数: + +```py + # Set the init functions for each layer + nn.layers[0].initf = nl.init.InitRand([-0.1, 0.1], 'wb') + nn.layers[1].initf = nl.init.InitRand([-0.1, 0.1], 'wb') + nn.init() +``` + +训练神经网络: + +```py + # Train the recurrent neural network + error_progress = nn.train(wave, amp, epochs=1200, show=100, goal=0.01) +``` + +通过网络运行数据: + +```py + # Run the training data through the network + output = nn.sim(wave) +``` + +绘制输出: + +```py + # Plot the results + plt.subplot(211) + plt.plot(error_progress) + plt.xlabel('Number of epochs') + plt.ylabel('Error (MSE)') + plt.subplot(212) + plt.plot(amp.reshape(num_points * 4)) + plt.plot(output.reshape(num_points * 4)) + plt.legend(['Original', 'Predicted']) +``` + +在未知的测试数据上测试神经网络的性能: + +```py + # Testing the network performance on unknown data + plt.figure() + plt.subplot(211) + visualize_output(nn, 82) + plt.xlim([0, 300]) + plt.subplot(212) + visualize_output(nn, 49) + plt.xlim([0, 300]) + plt.show() +``` + +文件`recurrent_neural_network.py`中提供了完整代码。 如果运行代码,您将看到两个输出图形。 第一个屏幕截图的上半部分显示了训练进度,下半部分显示了叠加在输入波形顶部的预测输出: + +![](img/B15441_19_13.png) + +图 13:输出波形叠加在输入波形上方 + +以下屏幕截图的上半部分显示了即使我们增加了波形的长度,神经网络也如何模拟波形。 屏幕截图的下半部分显示与减少长度相同: + +![](img/B15441_19_14.png) + +图 14:波形仿真图 + +您还应该看到以下输出: + +![](img/B15441_19_15.png) + +图 15:训练纪元 + +如您所见,错误持续减小,直到达到训练次数的最大值。 到此结束了本节,我们展示了如何使用 RNN 来分析时间序列数据。 在下一部分中,我们将通过研究光学字符识别来演示神经网络的实际应用。 + +# 在光学字符识别数据库中可视化字符 + +神经网络可以将用于光学字符识别。 它可能是其最常见的用例之一。 将手写体转换为计算机字符一直是许多计算机科学家试图解决的基本问题,但仍然难以捉摸。 我们已经取得了长足的进步,但是,由于显而易见的原因,100%的准确性仍然遥不可及。 为什么? + +考虑这种情况。 您曾经写下任何东西吗?五分钟后,您无法阅读自己的笔迹? 计算机也总是会出现此问题。 写下数字 *6* 的方法有无数种,其中有些看起来比 *6 更像 *0* 或 *5* 。* 。 我可能是错的,但是我认为我们将找到一种治愈癌症的方法,然后才能找到一种可靠的方法来使计算机识别医生的笔迹。 我们已经可以达到很高的准确性,并且*的笔迹越漂亮*,阅读起来就越容易。 我们继续尝试解决此问题的原因是,这是一个有价值的目标,具有许多应用程序。 举一个简短的例子,医生的时间受到高度重视。 随着系统能够更好地识别他们的笔记,他们将获得更多的精力来专注于实际治疗和帮助患者的精力,而不再关注文书工作。 + +**光学字符识别**( **OCR** )是识别图像中手写字符的过程。 在构建模型之前,让我们使熟悉数据集。 我们将使用以下位置提供的数据集: + +[http://ai.stanford.edu/~btaskar/ocr](http://ai.stanford.edu/~btaskar/ocr ) + +您将下载一个名为`letter.data`的文件。 为了方便起见,此文件已在代码包中提供给您。 让我们看看如何加载数据并形象化角色。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import os +import sys +import cv2 +import numpy as np +``` + +定义包含 OCR 数据的输入文件: + +```py +# Define the input file +input_file = 'letter.data' +``` + +定义从该文件加载数据所需的可视化效果和其他参数: + +```py +# Define the visualization parameters +img_resize_factor = 12 +start = 6 +end = -1 +height, width = 16, 8 +``` + +遍历该文件的各行,直到用户按下 *Esc* 键。 该文件中的每一行都用制表符分隔。 阅读每一行并将其放大到`255`: + +```py +# Iterate until the user presses the Esc key +with open(input_file, 'r') as f: + for line in f.readlines(): + # Read the data + data = np.array([255 * float(x) for x in line.split('\t')[start:end]]) +``` + +将一维数组重塑为二维图像: + +```py + # Reshape the data into a 2D image + img = np.reshape(data, (height, width)) +``` + +缩放图像以进行可视化: + +```py + # Scale the image + img_scaled = cv2.resize(img, None, fx=img_resize_factor, fy=img_resize_factor) +``` + +显示图像: + +```py + # Display the image + cv2.imshow('Image', img_scaled) +``` + +检查用户是否按下了 *Esc* 键。 如果是,请退出循环: + +```py + # Check if the user pressed the Esc key + c = cv2.waitKey() + if c == 27: + break +``` + +完整代码在文件`character_visualizer.py`中给出。 如果运行代码,您将获得显示字符的输出屏幕截图。 您可以按住空格键查看更多字符。 例如,`o`可能看起来像这样: + +![](img/B15441_19_16.png) + +图 16:字母 O 的图 + +和`i`可能看起来像这样: + +![](img/B15441_19_17.png) + +图 17:字母 I 的图 + +到目前为止,我们还没有识别出任何字符。 我们刚刚想出了一种可视化数据集并验证我们的模型正在做出准确预测的方法。 我们将在下一部分中进行构建。 + +# 构建光学字符识别引擎 + +现在我们已经学习了如何处理这些数据,让我们使用神经网络构建光学字符识别系统。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import neurolab as nl +``` + +定义输入文件: + +```py +# Define the input file +input_file = 'letter.data' +``` + +定义将要加载的数据点的数量: + +```py +# Define the number of datapoints to +# be loaded from the input file +num_datapoints = 50 +``` + +定义包含所有不同字符的字符串: + +```py +# String containing all the distinct characters +orig_labels = 'omandig' +``` + +提取不同类的数量: + +```py +# Compute the number of distinct characters +num_orig_labels = len(orig_labels) +``` + +定义训练和测试拆分。 我们将使用 90%的培训和 10%的测试: + +```py +# Define the training and testing parameters +num_train = int(0.9 * num_datapoints) +num_test = num_datapoints - num_train +``` + +定义数据集提取参数: + +```py +# Define the dataset extraction parameters +start = 6 +end = -1 +``` + +创建数据集: + +```py +# Creating the dataset +data = [] +labels = [] +with open(input_file, 'r') as f: + for line in f.readlines(): + # Split the current line tabwise + list_vals = line.split('\t') +``` + +如果标签不是我们列表中的,则应跳过该标签: + +```py + # Check if the label is in our ground truth + # labels. If not, we should skip it. + if list_vals[1] not in orig_labels: + continue +``` + +提取当前标签并将其附加到主列表中: + +```py + # Extract the current label and append it + # to the main list + label = np.zeros((num_orig_labels, 1)) + label[orig_labels.index(list_vals[1])] = 1 + labels.append(label) +``` + +提取字符向量并将其附加到主列表中: + +```py + # Extract the character vector and append it to the main list + cur_char = np.array([float(x) for x in list_vals[start:end]]) + data.append(cur_char) +``` + +创建数据集后,退出循环: + +```py + # Exit the loop once the required dataset has been created + if len(data) >= num_datapoints: + break +``` + +将列表转换为 NumPy 数组: + +```py +# Convert the data and labels to numpy arrays +data = np.asfarray(data) +labels = np.array(labels).reshape(num_datapoints, num_orig_labels) +``` + +提取维数: + +```py +# Extract the number of dimensions +num_dims = len(data[0]) +``` + +创建一个前馈神经网络并将训练算法设置为梯度下降: + +```py +# Create a feedforward neural network +nn = nl.net.newff([[0, 1] for _ in range(len(data[0]))], + [128, 16, num_orig_labels]) +# Set the training algorithm to gradient descent +nn.trainf = nl.train.train_gd +``` + +训练神经网络: + +```py +# Train the network +error_progress = nn.train(data[:num_train,:], labels[:num_train,:], + epochs=10000, show=100, goal=0.01) +``` + +预测测试数据的输出: + +```py +# Predict the output for test inputs +print('\nTesting on unknown data:') +predicted_test = nn.sim(data[num_train:, :]) +for i in range(num_test): + print('\nOriginal:', orig_labels[np.argmax(labels[i])]) + print('Predicted:', orig_labels[np.argmax(predicted_test[i])]) +``` + +完整代码在文件`ocr.py`中给出。 如果运行代码,则应看到以下输出: + +![](img/B15441_19_18.png) + +图 18:训练纪元 + +它将持续进行直到 10,000 个纪元。 完成后,您应该看到以下输出: + +![](img/B15441_19_19.png) + +图 19:训练纪元 + +正如我们在前面的屏幕截图中看到的,我们的模型正确地选择了其中的三个。 如果使用更大的数据集并训练更长的时间,则应该获得更高的准确性。 我们让您看看它们是否可以通过更长的网络训练和调整模型的配置来获得更高的准确性和更好的结果。 + +希望本章使您对 OCR 特别是神经网络感到兴奋。 在随后的章节中,我们将回顾该技术的许多其他用例,这些用例处于当前机器学习革命的最前沿。 + +# 摘要 + +在本章中,我们学习了神经网络。 我们讨论了如何构建和训练神经网络。 我们讨论了感知器,并在此基础上构建了分类器。 我们了解了单层神经网络以及多层神经网络。 我们讨论了如何将神经网络用于构建矢量量化器。 我们使用递归神经网络分析了顺序数据。 然后,我们使用神经网络构建了光学字符识别引擎。 在第 t 章中,我们将学习强化学习,并了解如何构建智能学习代理。 \ No newline at end of file diff --git a/docs/ai-py/20.md b/docs/ai-py/20.md new file mode 100644 index 0000000000000000000000000000000000000000..151e9a42e92d7eec8ee285aebb85aa01642b9a7c --- /dev/null +++ b/docs/ai-py/20.md @@ -0,0 +1,670 @@ +# 20 + +# 使用卷积神经网络进行深度学习 + +在本章中,我们将学习深度学习和**卷积神经网络**( **CNN** )。 在过去的几年中,CNN 取得了很大的发展势头,尤其是在图像识别领域。 我们将讨论 CNN 的体系结构以及内部使用的层的类型。 我们将看到如何使用一个名为 TensorFlow 的软件包。 我们将构建一个基于感知器的线性回归器。 我们将学习如何使用单层神经网络构建图像分类器。 + +然后,我们将使用 CNN 构建图像分类器。 图像分类器有许多应用。 这是一个奇特的名字,但这只是计算机辨别对象是什么的能力。 例如,您可以构建一个分类器来确定某物是热狗还是非热狗。 这是一个轻松的示例,但是图像分类器也可以使用生死攸关的应用程序。 为嵌入了图像分类软件的无人机拍照,它可以区分平民和敌方战斗人员。 在这种情况下不能犯任何错误。 + +本章涵盖以下主题: + +* CNN 的基础 +* CNN 的架构 +* CNN 中的图层类型 +* 构建基于感知器的线性回归器 +* 使用单层神经网络构建图像分类器 +* 使用 CNN 构建图像分类器 + +让我们开始学习基础知识。 + +# 卷积神经网络的基础 + +总体而言,CNN,尤其是**生成对抗网络**(尤其是 **GAN** ),已经成为新闻。 GAN 是 Ian Goodfellow 及其同事于 2014 年最初开发的一类 CNN。在 GAN 中,两个神经网络在游戏中相互竞争(从博弈论的角度)。 给定一个数据集,GAN 学习创建类似于训练集的新数据示例。 例如,速度可能会有些慢,但是有一个网站会产生不存在的人的面孔。 + +我们将让您的想象力疯狂起来,但是使用其中一些生成的“人类”在电影中出演肯定可以制作一部电影。 还有其他研究试图解决这一问题。 给定一个图像,我们可以确定它是 GAN 生成的图像还是真实的人? 您可以在此处浏览该网站: + +[https://thispersondoesnotexist.com/](https://thispersondoesnotexist.com/ ) + +要使用它,只需继续刷新页面,它将每次生成一个新图像。 GAN 最初是作为无监督学习的生成模型而创建的。 GAN 还被证明可用于半监督学习,监督学习和强化学习。 AI 的巨头之一 Yann LeCun 称 GAN *是 ML* [1]中最近十年中最有趣的想法。 让我们考虑 GAN 的其他一些用例和应用程序。 + +**使用 GAN 生成更多示例数据。** –数据是 ML 中的组成部分。 在某些情况下,不可能获得足够的数据来馈送到模型。 使用 GAN 生成更多输入数据是生成附加质量数据以馈入模型的好方法。 + +**安全性** – ML 为许多行业提供了提升。 无论市场部门如何,网络安全始终是企业高管的“首要任务”。 某些安全供应商使用 GAN 来处理网络攻击。 简而言之,GAN 会创建伪造的入侵,然后使用这些入侵来训练模型以识别这些威胁,从而使我们能够阻止这些攻击的真实版本。 + +**数据操作** – GAN 可用于“伪样式传输”,即,在不完全修改示例的情况下修改示例的某些尺寸。 + +GAN 可用于语音应用程序。 给定语音,可以训练 GAN 来重现著名的声音。 + +在一些著名的例子中,使用 GAN 修改了 Trump,Obama 或 Mona Lisa 的视频,并且开发人员使这些数字说出了他们从未说过的短语。 他们可能是很现实的。 或者可以将视频或图像更改为看起来像不同的人。 这是在麻省理工学院创建的唐纳德·特朗普总统形象之上插入尼古拉斯·凯奇的脸的例子: + +![](img/B15441_20_01.png) + +图 1:尼古拉斯·凯奇(Nicolas Cage)担任唐纳德·特朗普(Donald Trump)总统 + +可以将这些技术移植到其他领域,例如自然语言处理,语音处理等。 例如,GAN 可能会略微调整句子,从而改变句子的含义。 + +**隐私** –作为其安全策略的的一部分,许多公司希望将某些数据保密和保密。 显而易见的例子是国防和军事应用。 可以在加密数据时使用 GAN。 例如,生成一次性的密钥。 + +为了更好地利用 GAN,谷歌于 2016 年开始进行研究。 基本思想是让一个网络创建一个密钥,另一个网络试图破解它。 + +在前两章中,我们看到了神经网络是如何工作的。 神经网络由具有权重和偏见的神经元组成。 在训练过程中会调整这些权重和偏见,以得出一个好的学习模型。 每个神经元接收一组输入,以某种方式对其进行处理,然后输出 val ue。 + +如果我们构建具有多层的神经网络,则称为深度神经网络。 AI 处理这些深度神经网络的分支称为深度学习。 + +普通神经网络的主要缺点之一是它们忽略了输入数据的结构。 在将所有数据馈入网络之前,所有数据都将转换为一维数组。 这可能适用于数字数据,但是当我们处理图像时会变得困难。 + +让我们考虑灰度图像。 这些图像是 2D 结构,我们知道像素的空间排列具有很多隐藏信息。 如果我们忽略这些信息,我们将失去很多潜在的模式。 这是 CNN 出现的地方。 CNN 处理图像时会考虑图像的 2D 结构。 + +CNN 也由神经元组成,这些神经元由权重和偏差组成。 这些神经元接受输入数据,对其进行处理,然后输出某些数据。 网络的目标是从输入层中的原始图像数据转到输出层中的正确类别。 普通神经网络和 CNN 之间的区别在于我们使用的图层类型以及我们如何处理输入数据。 CNN 假定输入是图像,这使它们可以提取特定于图像的属性。 这使 CNN 可以更有效地处理 i 个法师。 现在我们已经了解了 CNN 的基础知识,下面让我们看一下它们是如何成立的。 + +# CNN 的体系结构 + +当我们在普通神经网络中使用时,我们需要将输入数据转换为单个向量。 此向量充当神经网络的输入,然后通过神经网络的各层。 在这些层中,每个神经元都连接到上一层中的所有神经元。 还值得注意的是,每一层中的神经元彼此之间没有连接。 它们仅连接到相邻层中的神经元。 网络中的最后一层是输出层,它代表最终输出。 + +如果我们将这种结构用于图像,它将很快变得难以管理。 例如,让我们考虑由 *256×256* RGB 图像组成的图像数据集。 由于这些是 3 通道图像,因此*权重为 256 * 256 * 3 = 196,608。 请注意,这仅适用于单个神经元! 每层将有多个神经元,因此权重数趋于迅速增加。 这意味着模型现在将具有在训练过程中要调整的大量参数。 因此,它很快变得非常复杂且耗时。 将每个神经元连接到上一层中的每个神经元(称为完全连通性)显然不会起作用。* + +CNN 在处理数据时会明确考虑图像的结构。 CNN 中的神经元按 3 个维度排列-宽度,高度和深度。 当前层中的每个神经元都连接到前一层输出的一小块。 就像在输入图像上叠加 *N* *x* *N* 滤镜一样。 这与完全连接层形成对比,在完全连接层中,每个神经元都连接到上一层的所有神经元。 + +由于单个滤镜无法捕获图像的所有细微差别,因此我们多次执行 *M* ,以确保捕获所有细节。 这些 *M* 过滤器充当特征提取器。 如果查看这些过滤器的输出,我们可以看到它们提取了诸如边,角等特征。 对于 CNN 中的初始层来说,这是正确的。 随着我们在网络的层中的进展,我们将看到后面的层提取更高级别的功能。 + +CNN 是深度学习网络。 它通常用于识别图像。 了解它如何识别图像将有助于您了解它们的工作原理。 像任何其他神经网络一样,它为图像中的元素分配权重和偏差,并能够将这些元素彼此区分开。 与其他分类模型相比,CNN 中使用的预处理较少。 当使用经过足够训练的原始方法过滤器时,可以训练 CNN 来区分这些过滤器和特征。 + +CNN 体系结构的基本形式可以与人脑中的神经元和树突进行比较,并从视觉皮层中汲取灵感。 单个神经元对视野受限区域的刺激作出反应。 该区域称为接受场。 这些视场的一组彼此重叠,因此覆盖了整个视场。 + +## CNN 与感知器神经网络 + +图像是像素值矩阵。 为什么我们不能仅将输入图像展平? 例如,可以将 *7x7* 图像展平为 *49x1* 矢量。 然后,我们可以将这个扁平化的图像用作基于感知器的神经网络的输入。 + +当使用基本的二进制(黑白)输入时,此方法在执行类的预测时可能会显示平均精度得分,但对于涉及像素始终的复杂图像,该方法几乎没有准确性。 + +让我们对此进行分析,以通过思考人类如何处理图像来获得一些理解。 考虑一个包含菱形的图像:♦。 我们的大脑可以立即处理图像,并意识到它是菱形:♦。 + +![](img/B15441_20_02.png) + +图 2:菱形 + +如果将其展平会怎样? + +![](img/B15441_20_03.png) + +图 3:菱形但变平 + +并非很容易识别,不是吗? 尽管如此,它是相同的信息。 当使用传统的神经网络而不是 CNN 时,也会发生类似的情况。 现在,当像素连续时,我们拥有的信息将丢失。 + +CNN 可以通过应用相关滤镜来捕获图像中的空间和时间相关性。 CNN 体系结构在数据集上表现更好,这是因为参数数量减少了,并且权重得到了重用。 + +现在,我们对 CNN 架构和图像的处理方式有了更好的了解,让我们考虑一下包含 CNN 的层。 + +# CNN 中的图层类型 + +CNN 通常使用以下类型的层: + +**输入层** –此层直接获取原始图像数据。 + +**卷积层** –此层计算神经元与输入中各种贴片之间的卷积。 如果您需要快速了解图像卷积,可以查看以下链接: + +[http://web.pdx.edu/~jduh/courses/Archive/geog481w07/Students/Ludwig_ImageConvolution.pdf](http://web.pdx.edu/~jduh/courses/Archive/geog481w07/Students/Ludwig_ImageConvolution.pdf ) + +卷积层基本上计算权重和前一层输出中的一个小补丁之间的点积。 + +**整流线性单元层** –此层将激活函数应用于上一层的输出。 此函数通常类似于 *max(0,x)*。 需要这一层来为网络增加非线性,以便可以很好地推广到任何类型的功能。 + +**合并层** –此层对上一层的输出进行采样,从而得到具有较小尺寸的结构。 池化有助于我们在网络发展过程中仅保留重要部分。 最大池化通常在池化层中使用,我们在给定的 *KxK* 窗口中选择最大值。 + +**全连接层** –此层计算最后一层的输出分数。 结果输出的大小为 *1x1xL* ,其中 *L* 是 t 下雨数据集中的类数。 + +![](img/B15441_20_04.png) + +图 4:CNN 层 + +当我们从网络中的输入层到输出层时,输入图像就从像素值转换为最终的类别分数。 已经为 CNN 提出了许多不同的体系结构,这是一个活跃的研究领域。 模型的准确性和鲁棒性取决于许多因素-层的类型,网络的深度,网络内各种类型的层的排列,为每个层选择的功能离子,训练 ng 数据 , 等等。 + +# 建立基于感知器的线性回归器 + +在建立 CNN 之前,让我们为基础建立一个更基本的模型,并了解如何使用 CNN 进行改进。 在本节中,我们将看到如何使用感知器构建线性回归模型。 在前面的章节中我们已经看到了线性回归,但是本节是关于使用神经网络方法构建线性回归模型的。 + +我们将在本章中使用 TensorFlow。 这是一个流行的深度学习软件包,已广泛用于构建各种实际系统。 在本节中,我们将熟悉其工作原理。 在继续操作之前,请确保已安装它。 可以在这里找到安装说明: + +[https://www.tensorflow.org/get_started/os_setup](https://www.tensorflow.org/get_started/os_setup ) + +确认已安装后,创建一个新的 Python 文件并导入以下软件包: + +```py +import numpy as np +import matplotlib.pyplot as plt +import tensorflow as tf +``` + +我们将生成一些数据点,并查看如何使用这些数据点训练模型。 + +定义要生成的数据点数: + +```py +# Define the number of points to generate +num_points = 1200 +``` + +定义将用于生成数据的参数。 我们将使用线的模型: *y = mx + c* : + +```py +# Generate the data based on equation y = mx + c +data = [] +m = 0.2 +c = 0.5 +for i in range(num_points): + # Generate 'x' + x = np.random.normal(0.0, 0.8) +``` + +产生一些噪音以增加数据的差异: + +```py + # Generate some noise + noise = np.random.normal(0.0, 0.04) +``` + +使用以下公式计算`y`的值: + +```py + # Compute 'y' + y = m*x + c + noise + data.append([x, y]) +``` + +完成迭代后,将数据分为输入和输出变量: + +```py +# Separate x and y +x_data = [d[0] for d in data] +y_data = [d[1] for d in data] +``` + +绘制数据: + +```py +# Plot the generated data +plt.plot(x_data, y_data, 'ro') +plt.title('Input data') +plt.show() +``` + +生成感知器的权重和偏差。 对于权重,我们将使用统一的随机数生成器并将偏差设置为零: + +```py +# Generate weights and biases +W = tf.Variable(tf.random_uniform([1], -1.0, 1.0)) +b = tf.Variable(tf.zeros([1])) +``` + +使用 TensorFlow 变量定义方程式: + +```py +# Define equation for 'y' +y = W * x_data + b +``` + +定义在训练过程中可以使用的损失函数。 优化器将尝试尽可能减小此值。 + +```py +# Define how to compute the loss +loss = tf.reduce_mean(tf.square(y - y_data)) +``` + +定义梯度下降优化器并指定损失函数: + +```py +# Define the gradient descent optimizer +optimizer = tf.train.GradientDescentOptimizer(0.5) +train = optimizer.minimize(loss) +``` + +所有变量均已就位,但尚未初始化。 让我们这样做: + +```py +# Initialize all the variables +init = tf.initialize_all_variables() +``` + +启动 TensorFlow 会话并使用初始化程序运行它: + +```py +# Start the tensorflow session and run it +sess = tf.Session() +sess.run(init) +``` + +开始训练过程: + +```py +# Start iterating +num_iterations = 10 +for step in range(num_iterations): + # Run the session + sess.run(train) +``` + +打印培训过程的进度。 随着迭代的进行,`loss`参数将继续减小: + +```py + # Print the progress + print('\nITERATION', step+1) + print('W =', sess.run(W)[0]) + print('b =', sess.run(b)[0]) + print('loss =', sess.run(loss)) +``` + +绘制生成的数据并将预测模型覆盖在顶部。 在这种情况下,模型是一行: + +```py + # Plot the input data + plt.plot(x_data, y_data, 'ro') + # Plot the predicted output line + plt.plot(x_data, sess.run(W) * x_data + sess.run(b)) +``` + +设置图的参数: + +```py + # Set plotting parameters + plt.xlabel('Dimension 0') + plt.ylabel('Dimension 1') + plt.title('Iteration ' + str(step+1) + ' of ' + str(num_iterations)) + plt.show() +``` + +完整代码在文件`linear_regression.py`中给出。 如果运行代码,则应该看到以下屏幕截图: + +![](img/B15441_20_05.png) + +图 5:输入数据图 + +如果关闭此窗口,您将看到训练过程。 第一次迭代如下所示: + +![](img/B15441_20_06.png) + +图 6:训练过程的第一次迭代图 + +如我们所见,行完全关闭。 关闭此窗口以转到下一个迭代: + +![](img/B15441_20_07.png) + +图 7:训练过程的后续迭代图 + +行似乎更好,但是仍然关闭。 让我们关闭此窗口,然后继续迭代: + +![](img/B15441_20_08.png) + +图 8:训练过程的另一个后续迭代的图 + +看起来行越来越接近实际模型。 如果继续这样迭代,模型将变得更好。 第八次迭代如下所示: + +![](img/B15441_20_09.png) + +图 9:训练过程的第八次迭代图 + +该行似乎非常适合该数据。 然后,您应该将 e 打印出来: + +![](img/B15441_20_10.png) + +图 10:历元的初始输出 + +完成培训后,您将看到以下内容: + +![](img/B15441_20_11.png) + +图 11:历元的最终输出 + +我们可以看到`w`和`b`的值是如何不断调整的,我们还可以看到损耗如何持续减小直到损耗很小的程度,以至于我们不再看到它减小。 有趣的是,我们能够很快取得良好的结果,但是我们为我们的网络解决了一个相当简单的问题。 让我们 崭露头角。 + +# 使用单层神经网络构建图像分类器 + +让我们看看如何使用 TensorFlow 创建单层神经网络,并使用它来构建图像分类器。 我们将使用 MNIST 图像数据集来构建我们的系统。 它是一个包含手写数字图像的数据集。 我们的目标是建立一个可以正确识别每个图像中数字的分类器。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import tensorflow as tf +from tensorflow.examples.tutorials.mnist import input_data +``` + +提取 MNIST 图像数据。 `one_hot`标志指定我们将在标签中使用一键编码。 这表示,如果我们有 *n* 类,则给定数据点的标签将是长度为 *n* 的数组。 该数组中的每个元素对应于一个给定的类。 要指定一个类,相应索引处的值将设置为 *1* ,其他所有值都将为 *0* : + +```py +# Get the MNIST data +mnist = input_data.read_data_sets("./mnist_data", one_hot=True) +``` + +数据库中的图像是 *28x28* 。 我们需要将其转换为一维数组以创建输入层: + +```py +# The images are 28x28, so create the input layer +# with 784 neurons (28x28=784) +x = tf.placeholder(tf.float32, [None, 784]) +``` + +创建具有权重和偏差的单层神经网络。 数据库中有 10 个不同的数字。 输入层中神经元的数量为`784`,输出层中神经元的数量为`10`: + +```py +# Create a layer with weights and biases. +# There are 10 distinct +# digits, so the output layer should have 10 classes +W = tf.Variable(tf.zeros([784, 10])) +b = tf.Variable(tf.zeros([10])) +``` + +创建用于训练的方程式: + +```py +# Create the equation for 'y' using y = W*x + b +y = tf.matmul(x, W) + b +``` + +定义损失函数和梯度下降优化器: + +```py +# Define the entropy loss and the gradient descent optimizer +y_loss = tf.placeholder(tf.float32, [None, 10]) +loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=y, labels=y_loss)) +optimizer = tf.train.GradientDescentOptimizer(0.5).minimize(loss) +``` + +初始化所有变量: + +```py +# Initialize all the variables +init = tf.initialize_all_variables() +``` + +创建一个 TensorFlow 会话并运行它: + +```py +# Create a session +session = tf.Session() +session.run(init) +``` + +开始培训过程。 我们将使用批处理进行训练,在该批处理中,我们在当前批处理上运行优化器,然后继续进行下一个批处理以进行下一次迭代。 每次迭代的第一步是获取下一批要训练的图像: + +```py +# Start training +num_iterations = 1200 +batch_size = 90 +for _ in range(num_iterations): + # Get the next batch of images + x_batch, y_batch = mnist.train.next_batch(batch_size) +``` + +在这批图像上运行优化器: + +```py + # Train on this batch of images + session.run(optimizer, feed_dict = {x: x_batch, y_loss: y_batch}) +``` + +训练过程结束后,使用测试数据集计算准确性: + +```py +# Compute the accuracy using test data +predicted = tf.equal(tf.argmax(y, 1), tf.argmax(y_loss, 1)) +accuracy = tf.reduce_mean(tf.cast(predicted, tf.float32)) +print('\nAccuracy =', session.run(accuracy, feed_dict = { + x: mnist.test.images, + y_loss: mnist.test.labels})) +``` + +完整代码在文件`single_layer.py`中给出。 如果运行代码,它将把数据下载到当前文件夹中名为`mnist_data`的文件夹中。 这是默认选项。 如果要更改它,可以使用输入参数进行更改。 运行代码后,将及以下输出: + +![](img/B15441_20_12.png) + +图 12:精度输出 + +如输出所示,该模型的准确性为 92.1%。 这是一个相当低的分数。 让我们看看如何使用 CNN 来改善。 + +# 使用卷积神经网络构建图像分类器 + +上一节部分中的图像分类器效果不佳。 在 MNIST 数据集上获得 92.1%相对容易。 让我们看看如何使用 CNN 来获得更高的准确性。 我们将使用相同的数据集构建图像分类器,但使用 CNN 而不是单层神经网络。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import argparse +import tensorflow as tf +from tensorflow.examples.tutorials.mnist import input_data +``` + +定义一个函数来为每个图层中的权重创建值: + +```py +def get_weights(shape): + data = tf.truncated_normal(shape, stddev=0.1) + return tf.Variable(data) +``` + +定义一个函数来为每个层中的偏差创建值: + +```py +def get_biases(shape): + data = tf.constant(0.1, shape=shape) + return tf.Variable(data) +``` + +定义一个函数,根据输入形状创建一个层: + +```py +def create_layer(shape): + # Get the weights and biases + W = get_weights(shape) + b = get_biases([shape[-1]]) + return W, b +``` + +定义函数以执行 2D 卷积: + +```py +def convolution_2d(x, W): + return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], + padding='SAME') +``` + +定义一个函数来执行 *2×2* 最大池化操作: + +```py +def max_pooling(x): + return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], + strides=[1, 2, 2, 1], padding='SAME') +``` + +提取 MNIST 图像数据: + +```py +# Get the MNIST data +mnist = input_data.read_data_sets(args.input_dir, one_hot=True) +``` + +使用`784`神经元创建输入层: + +```py +# The images are 28x28, so create the input layer +# with 784 neurons (28x28=784) +x = tf.placeholder(tf.float32, [None, 784]) +``` + +我们将使用利用图像 2D 结构的卷积神经网络。 因此,让我们将`x`重塑为 4D 张量,其中第二维和第三维指定图像尺寸: + +```py +# Reshape 'x' into a 4D tensor +x_image = tf.reshape(x, [-1, 28, 28, 1]) +``` + +创建第一个卷积层,它将为图像中的每个 *5×5* 面片提取`32`特征: + +```py +# Define the first convolutional layer +W_conv1, b_conv1 = create_layer([5, 5, 1, 32]) +``` + +将图像与上一步中计算的权重张量进行卷积,然后向其添加偏差 tenso r。 然后,我们需要将**整流线性单位**( **ReLU** )函数应用到输出: + +```py +# Convolve the image with weight tensor, add the +# bias, and then apply the ReLU function +h_conv1 = tf.nn.relu(convolution_2d(x_image, W_conv1) + b_conv1) +``` + +将 *2×2* 最大池化运算符应用于上一步的输出: + +```py +# Apply the max pooling operator +h_pool1 = max_pooling(h_conv1) +``` + +创建第二个卷积层以为每个 *5×5* 补丁计算`64`特征: + +```py +# Define the second convolutional layer +W_conv2, b_conv2 = create_layer([5, 5, 32, 64]) +``` + +将上一层的输出与在上一步中计算的权重张量进行卷积,然后向其添加偏差张量。 然后,我们需要将 ReLU 函数应用于输出: + +```py +# Convolve the output of previous layer with the +# weight tensor, add the bias, and then apply +# the ReLU function +h_conv2 = tf.nn.relu(convolution_2d(h_pool1, W_conv2) + b_conv2) +``` + +将 *2×2* 最大池化运算符应用于上一步的输出: + +```py +# Apply the max pooling operator +h_pool2 = max_pooling(h_conv2) +``` + +现在将图像尺寸减小为 *7×7* 。 用`1024`神经元创建一个完全连接的层: + +```py +# Define the fully connected layer +W_fc1, b_fc1 = create_layer([7 * 7 * 64, 1024]) +``` + +重塑上一层的输出: + +```py +# Reshape the output of the previous layer +h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64]) +``` + +将上一层的输出与完全连接的层的权重张量相乘,然后向其添加偏置张量。 然后,我们将 ReLU 函数应用于输出: + +```py +# Multiply the output of previous layer by the +# weight tensor, add the bias, and then apply +# the ReLU function +h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) +``` + +为了减少过度拟合,我们需要创建一个辍学层。 让我们为概率值创建一个 TensorFlow 占位符,该占位符指定在删除过程中保留神经元输出的概率: + +```py +# Define the dropout layer using a probability placeholder +# for all the neurons +keep_prob = tf.placeholder(tf.float32) +h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) +``` + +使用与我们数据集中的 10 个类别相对应的`10`输出神经元定义读出层。 计算输出: + +```py +# Define the readout layer (output layer) +W_fc2, b_fc2 = create_layer([1024, 10]) +y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2 +``` + +定义`loss`功能和`optimizer`功能: + +```py +# Define the entropy loss and the optimizer +y_loss = tf.placeholder(tf.float32, [None, 10]) +loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(y_conv, y_loss)) +optimizer = tf.train.AdamOptimizer(1e-4).minimize(loss) +``` + +定义精度的计算方法: + +```py +# Define the accuracy computation +predicted = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_loss, 1)) +accuracy = tf.reduce_mean(tf.cast(predicted, tf.float32)) +``` + +初始化变量后创建并运行会话: + +```py +# Create and run a session +sess = tf.InteractiveSession() +init = tf.initialize_all_variables() +sess.run(init) +``` + +开始训练过程: + +```py +# Start training +num_iterations = 21000 +batch_size = 75 +print('\nTraining the model.') +for i in range(num_iterations): + # Get the next batch of images + batch = mnist.train.next_batch(batch_size) +``` + +每`50`次迭代打印一次精度进度: + +```py + # Print progress + if i % 50 == 0: + cur_accuracy = accuracy.eval(feed_dict = { + x: batch[0], y_loss: batch[1], keep_prob: 1.0}) + print('Iteration', i, ', Accuracy =', cur_accuracy) +``` + +在当前批次的上运行优化器: + +```py + # Train on the current batch + optimizer.run(feed_dict = {x: batch[0], y_loss: batch[1], keep_prob: 0.5}) +``` + +训练过程结束后,使用测试数据集计算准确性: + +```py +# Compute accuracy using test data +print('Test accuracy =', accuracy.eval(feed_dict = { + x: mnist.test.images, y_loss: mnist.test.labels, + keep_prob: 1.0})) +``` + +完整代码在文件`cnn.py`中给出。 如果运行代码,将得到以下输出: + +![](img/B15441_20_13.png) + +图 13:精度输出 + +随着继续迭代,精度不断提高,如以下屏幕截图所示: + +![](img/B15441_20_14.png) + +图 14:精度输出 + +现在我们有了的输出,我们可以看到的卷积神经网络的准确性比简单的神经网络要高得多。 与上一节中未使用 CNN 的准确性相比,这实际上是一个很大的改进。 CNN 可以解救! + +# 摘要 + +在本章中,我们学习了深度学习和 CNN。 我们讨论了什么是 CNN,以及我们为什么需要它们。 我们讨论了 CNN 的体系结构。 我们了解了 CNN 中使用的各种类型的图层。 我们讨论了如何使用 TensorFlow。 我们使用它来构建基于感知器的线性回归器。 我们学习了如何使用单层神经网络构建图像分类器。 然后,我们使用 CNN 构建了图像分类器。 + +在下一章中,我们将了解 CNN 的另一个受欢迎的兄弟– **递归神经网络**( **RNN** )。 像 CNN 一样,RNN 也已流行,并且现在非常流行。 与以前的模型相比,它们取得了令人印象深刻的结果。 在某些情况下,甚至在某些情况下甚至超过了人类的表现。 + +# 参考 + +1. Yann LeCun 对有关 Quora 的问题的答复: [https://www.quora.com/What-are-some-recent-and-potentially-upcoming-breakthroughs-in-deep-learning](https://www.quora.com/What-are-some-recent-and-potentially-upcoming-breakthroughs-in-deep-learning) \ No newline at end of file diff --git a/docs/ai-py/21.md b/docs/ai-py/21.md new file mode 100644 index 0000000000000000000000000000000000000000..3465042ab298d403df62bef1cb9810905dc6377e --- /dev/null +++ b/docs/ai-py/21.md @@ -0,0 +1,478 @@ +# 21 + +# 递归神经网络和其他深度学习模型 + +在本章中,我们将学习深度学习和**递归神经网络**( **RNN** )。 像前几章所介绍的 CNN 一样,RNN 在过去几年中也获得了很大发展。 就 RNN 而言,它们在语音识别领域被大量使用。 当今许多聊天机器人都基于 RNN 技术建立了基础。 使用 RNN 预测金融市场已经取得了一些成功。 例如,我们可能有一个带有单词序列的文本,并且我们有一个目标来预测序列中的下一个单词。 + +我们将讨论 RNN 的体系结构及其组件。 我们将继续使用上一章开始学习的 TensorFlow。 我们将使用 TensorFlow 快速构建 RNN。 我们还将学习如何使用单层神经网络构建 RNN 分类器。 然后,我们将使用 CNN 构建图像分类器。 + +到本章末,您将了解: + +* RNN 的基础 +* RNN 的架构 +* RNN 中的图层类型 +* 语言建模用例的详细信息 +* 如何使用基本算法构建初始 RNN 分类器 +* 如何使用更先进的技术来增强 RNN + +让我们从 RNN 的基础开始。 + +# 递归神经网络的基础 + +RNN 是另一种流行的模型,目前正在获得很大的关注。 正如我们在*第 1 章*,*人工智能导论*中讨论的那样,对一般神经网络(尤其是 RNN)的研究是*联系主义者*部落( 在 Pedro Domingos 的 AI 分类中进行了介绍)。 RNN 通常用于解决自然语言处理( **NLP** )和**自然语言理解**( **NLU** )问题 。 + +RNN 背后的数学有时可能会令人不知所措。 在深入研究 RNN 之前,请牢记以下思想:赛车手不需要完全了解其赛车的机械原理即可使其快速行驶并赢得比赛。 同样,我们不一定需要完全了解 RNN 在幕后的工作方式,以使其对我们有用,有时甚至是令人印象深刻的工作。 Keras 库的创建者 Francois Chollet 描述了**长短期记忆**( **LSTM** )网络,这是 RNN 的一种形式,如下所示: + +> “您不需要了解有关 LSTM 单元的特定体系结构的所有内容;作为人类,理解它不是您的工作。只需记住 LSTM 单元的含义:允许过去的信息 以后再注射。” + +现在让我们进入神经网络的一些基础知识。 顾名思义,*神经*网络从大脑神经元的结构中获得灵感。 神经网络中的神经元大致模拟了人类神经元的结构和功能。 对于一般的大脑,尤其是神经元,我们有很多不了解的地方。 但在基本级别上,神经元接受输入,并且如果达到阈值,它将触发输出。 用数学术语来说,*人工*神经元是数学函数的容器,其唯一的任务是通过将给定的输入应用于函数来传递输出。 + +既然我们已经了解了使神经网络*滴答作响*的原因以及使它们发火的原因,那么让我们了解一些用于触发神经网络的常用功能。 这些功能通常称为,称为*激活功能*,因为它们在达到阈值时就会激活。 您可以将任何类型的功能用作激活功能,但以下是一些用作激活功能的常用功能: + +* 步进功能 +* 乙状结肠功能 +* tanh 功能 +* ReLU 功能 + +以下各节将更详细地描述每一个。 + +## 步进功能 + +步进功能是简单功能。 就这么简单。 如果输出高于某个阈值,则会触发该函数。 否则就不会。 图形化: + +![https://miro.medium.com/max/600/1*0iOzeMS3s-3LTU9hYH9ryg.png](img/B15441_21_01.png) + +图 1:单位步进功能 + +如果 *x* 的值大于或等于零,则输出为 *1* ,如果 *x* 的值小于 *0* 。 零。 可以看出,阶跃函数不可微为零。 神经网络通常使用反向传播和梯度下降来计算不同层的权重。 由于阶跃函数为零时不可微,因此它无法向下进行梯度下降,并且在尝试更新其权重时会失败。 + +为了克服这个问题,我们可以改用 S 型函数。 + +## S 型功能 + +S 型函数(也称为作为逻辑函数)定义如下: + +![https://miro.medium.com/max/500/1*MIeka59unAhS7MQk5e7FOg.png](img/B15441_21_02.png) + +图 2:S 形函数 + +当 *z* (自变量)趋于负无穷大时,函数的值趋于 *0* ;当 *z* 趋于趋近时,函数的值趋于 *1* 到无穷远。 + +乙状结肠功能有一个缺点。 容易出现梯度消失的问题。 从图形中可以看出,S 型函数的值在 0 到 1 之间的较小范围内。S 型函数具有陡峭的梯度。 因此,在许多情况下,输入的大变化会导致输出的小变化。 此问题称为*消失梯度*。 随着网络中层数的增加,该问题呈指数增长,因此很难扩展使用此功能的神经网络。 + +使用 S 形函数的原因之一是因为其输出始终落在 0 到 1 之间。因此,它对于需要预测输出为概率的模型很有用。 概率总是在 0 到 1 的范围内。因此,在这些情况下,S 型是要使用的适当函数。 + +现在我们来看 *tanh* 函数,它克服了 S 型函数的一些问题。 + +## Tanh 功能 + +*tanh(z)*函数是 S 形函数的重新缩放的版本。 其输出范围是 *-1* 至 *-1* ,而不是 *0* 至 *1* 。 + +![Image result for tanh function image"](img/B15441_21_03.png) + +图 3:Tanh 函数 + +之所以使用 *tanh* 函数而不是 Sigmoid 函数的主要原因是因为值以 0 为中心,所以导数更高。 较高的梯度有助于产生更好的学习率,因此可以更快地训练模型。 但是,使用 *tanh* 功能时,仍然存在消失梯度问题。 + +现在,我们将了解另一个功能:ReLU 功能。 + +## ReLU 功能 + +**整流线性单元**( **ReLU** )函数可能是在 CNN 和 RNN 模型中最流行的激活函数。 给定负输入时,该函数返回 0。 给定任何正值时,它将返回该值。 因此,它可以写为: + +*f(x)= max(0,x)* + +图形上看起来像这样: + +![https://miro.medium.com/max/446/1*njuH4XVXf-l9pR_RorUOrA.png](img/B15441_21_04.png) + +图 4:ReLU 功能 + +在 ReLU 变体中,泄漏的 ReLU 实现是最受欢迎的实现之一。 对于正数,它返回与常规 ReLU 相同的值。 但是,它没有返回 *0* 为负值,而是具有恒定的斜率(小于 *1* )。 + +![Image result for leaky relu function image"](img/B15441_21_05.png) + +图 5:泄漏的 ReLU 功能 + +该斜率是函数用户在设置模型时设置的参数。 斜率由*α*标识。 例如,对于*α= 0.3* ,激活函数为: + +*f(x)=最大值(0.3 * x,x)* + +泄漏的 ReLU 具有理论上的优势,即在所有值下都受到 *x* 的影响,因此可以更好地利用所有输入提供的信息。 + +鉴于 ReLU 的特性和优势,它经常是深度学习从业人员和研究人员选择的激活功能。 + +现在,我们已经介绍了 RNN 的一些基本功能,并讨论了它们的一些关键功能,下面让我们深入研究其实际的体系结构。 + +# RNN 的体系结构 + +RNN 背后的主要概念是利用序列中的先前信息。 在传统的神经网络中,假定所有输入和输出彼此独立。 在某些领域和用例中,这种假设是不正确的,我们可以利用这种相互联系的优势。 + +我将以个人为例。 我相信,在很多情况下,我可以根据几个最初的句子来预测我妻子接下来会说些什么。 我倾向于相信自己的预测能力具有很高的准确率。 也就是说,如果您问我的妻子,她可能会告诉您一个完全不同的故事! Google 的电子邮件服务 Gmail 正在使用类似的概念。 如果您是该服务的用户,您会注意到,从 2019 年开始,它在认为可以完成句子时就开始提出建议。 如果猜对了,则只需按 Tab 键即可完成句子。 如果没有,您可以继续输入,并且可能会根据新输入给出不同的建议。 我不了解此服务的实现内部原理,但是可以假定他们正在使用 RNN 技术,因为 RNN 非常善于处理此类问题。 + +RNN 之所以称为递归,是因为这些算法对序列的每个元素执行相同的任务,并且输出取决于先前的计算。 您也可以将 RNN 视为具有“内存”,用于存储有关到目前为止发生的事情和已经计算出的信息。 从理论上讲,RNN 能够从中提取信息的序列长度没有限制。 实际上,它们通常以仅回顾几步的方式实现。 这是通常用于表示 RNN 的图: + +![1: A recurrent neural network and the unfolding in time of the computation involved in its forward computation. Source: LeCun, Bengio, and G. Hinton 2015 ](img/B15441_21_06.png) + +图 6:递归神经网络及其正向计算所涉及的计算时间的展开 + +资料来源:LeCun,Bengio 和 G. Hinton,2015 年,《深度学习》,《自然》 + +上图表示作为完整网络展开或展开的 RNN。 术语*展开*用来表示逐步为整个序列布置网络。 例如,如果将前三个单词用于预测下一个单词,则将将*展开为*网络进入 3 层网络,每个单词一层。 控制 RNN 中发生的计算的公式如下: + +![](img/B15441_21_07.png) + +图 7:3 层递归神经网络 + +资料来源:LeCun,Bengio 和 G. Hinton,2015 年,《深度学习》,《自然》 + +*x* t 是时间步长 *t* 的输入。 在这种情况下, *x* 1 可能是一个热门属性,与句子中的第 2 单词相对应。 + +*s* t 是时间步长 *t* 的隐藏状态。 您可以将其视为网络的*存储器*。 *s* t 是使用先前的隐藏状态和当前步骤的输入来计算的: + +![](img/B15441_21_001.png) + +最常用的函数 *f* 是非线性函数,例如 *tanh* 或 *ReLU* 。 通常,将计算第 1 个 st 隐藏状态所需的 *s-1* 初始化为零。 + +*o* t 是步骤 *t* 的输出。 例如,如果我们想预测句子中的下一个单词,那么它将是整个词汇表中概率的向量: + +![](img/B15441_21_002.png) + +这里有一些注意事项。 您可以将隐藏状态 *s* t 视为网络的内存。 *s* t 捕获有关在所有先前时间步中发生的情况的信息。 仅根据时间 *t* 时的内存计算步骤 *o* t 的输出。 如前所述,由于 *s* t 只能捕获有关有限数量先前步骤的信息,因此它在实践中更为复杂。 + +与传统的深度神经网络在每一层使用不同的参数不同,RNN 共享相同的参数(先前显示的 *U* , *V* 和 *W* ) 所有步骤。 这是因为每个步骤都执行相同的任务,但是输入不同。 这大大减少了需要跟踪的参数总数。 + +上一个图具有每个时间步的输出,但是根据任务的不同,可能不需要此输出。 例如,执行情感分析时,通常对整个句子的情感而不是每个单词感兴趣。 同样,我们可能不需要每个时间步都输入。 RNN 的主要特征是其隐藏状态,该状态捕获有关序列的一些信息。 + +现在是时候深入研究一个具体的例子了。 我们将学习如何使用 RNN 预测句子中的下一个单词。 让我们做一些预测。 + +# 语言建模用例 + +我们的目标是使用 RNN 建立语言模型。 这就是这个意思。 假设我们有一个 *m* 个单词的句子。 语言模型使我们能够预测观察句子(在给定数据集中)的概率为: + +![](img/B15441_21_003.png) + +换句话说,句子的概率是给定单词前面每个单词的概率的乘积。 因此,句子“请让我知道是否有任何问题”的概率等于给出“请让我知道如果您有...的问题”的概率乘以给出“有”的概率“ 请让我知道您是否有...”等等。 + +这有什么用? 为什么将概率分配给给定句子很重要? + +首先,像这样的模型可以用作评分机制。 语言模型可用于选择最可能的下一个单词。 从直觉上讲,最可能出现的下一个单词在语法上可能是正确的。 + +语言建模具有重要应用程序。 因为它可以预测给定前面单词的单词的概率,所以可以将其用于**自然文本生成**( **NTG** )。 给定一个现有的单词序列,从概率最高的单词列表中建议一个单词,然后重复该过程,直到生成完整的句子为止。 + +注意,在前面的等式中,每个单词的概率以所有先前的单词为条件。 在更实际的情况下,由于计算或内存限制,模型可能很难代表长期依赖关系。 因此,大多数模型通常仅限于仅查看少量的先前单词。 + +足够的理论。 现在,我们准备开始编写一些代码,并学习如何训练 RNN 以生成文本。 + +# RNN 培训 + +正如我们在本章的开头所讨论的那样,RNN 的应用在众多行业中广泛而多样。 在我们的案例中,我们仅执行一个简单的示例,以便更牢固地理解 RNN 的基本机制。 + +我们将尝试使用 RNN 建模的输入数据是数学*余弦*函数。 + +因此,首先让我们定义输入数据并将其存储到 NumPy 数组中。 + +```py +import numpy as np +import math +import matplotlib.pyplot as plt +input_data = np.array([math.cos(x) for x in np.arange(200)]) +plt.plot(input_data[:50]) +plt.show +``` + +前面的语句将绘制数据,以便我们可以可视化输入数据的外观。 您应该得到如下输出: + +![](img/B15441_21_09.png) + +图 8:可视化输入数据 + +现在,将输入数据分为两组,以便我们可以将一部分用于训练,另一部分用于验证。 从训练的角度看,这也许不是最佳的分割方法,但是为了使事情变得简单,让我们将数据分割到中间。 + +```py +X = [] +Y = [] +size = 50 +number_of_records = len(input_data) - size +for i in range(number_of_records - 50): + X.append(input_data[i:i+size]) + Y.append(input_data[i+size]) + +X = np.array(X) +X = np.expand_dims(X, axis=2) +Y = np.array(Y) +Y = np.expand_dims(Y, axis=1) +``` + +让我们打印得到的训练数组的形状: + +```py +X.shape, Y.shape +``` + +您应该看到如下输出: + +```py +((100, 50, 1), (100, 1)) +``` + +让我们创建验证集: + +```py +X_valid = [] +Y_valid = [] +for i in range(number_of_records - 50, number_of_records): + X_valid.append(input_data[i:i+size]) + Y_valid.append(input_data[i+size]) + +X_valid = np.array(X_valid) +X_valid = np.expand_dims(X_valid, axis=2) +Y_valid = np.array(Y_valid) +Y_valid = np.expand_dims(Y_valid, axis=1) +``` + +接下来,让我们定义 RNN 将使用的参数。 例如,我们定义隐藏层以包含`100`单位: + +```py +learning_rate = 0.0001 +number_of_epochs = 5 +sequence_length = 50 +hidden_layer_size = 100 +output_layer_size = 1 +back_prop_truncate = 5 +min_clip_value = -10 +max_clip_value = 10 +``` + +让我们定义各层之间连接的权重: + +```py +W1 = np.random.uniform(0, 1, (hidden_layer_size, sequence_length)) +W2 = np.random.uniform(0, 1, (hidden_layer_size, hidden_layer_size)) +W3 = np.random.uniform(0, 1, (output_layer_size, hidden_layer_size)) +``` + +在前面的代码中: + +* `W1`是输入层和隐藏层之间权重的权重矩阵 +* `W2`是隐藏层和输出层之间权重的权重矩阵 +* `W3`是 RNN 层(隐藏层)中共享权重的权重矩阵 + +我们将用于 RNN 的激活函数是 S 型函数。 有关一般的激活函数,尤其是 S 型函数的详细讨论,请参见上一章。 + +```py +def sigmoid(x): + return 1 / (1 + np.exp(-x)) +``` + +现在,我们已经准备就绪,可以开始训练模型了。 我们将迭代`25`时代。 您将在结果中清楚地看到模型和实际数据开始收敛的点。 确保收敛后停止训练。 否则,我们将过度拟合数据,并且我们的模型将使用训练数据生成良好的数字,但对于尚未看到的数据将不会表现良好。 + +运行该程序几次。 一旦看到数据开始收敛,就可以调整“历元数”的值。 + +这是培训期间将执行的步骤的概述: + +1. 检查训练数据上的损失 + * 执行向前通过 + * 计算误差 +2. 检查验证数据上的损失 + * 执行向前通过 + * 计算误差 +3. 开始训练 + * 执行向前通过 + * 向后传播错误 + * 更新权重 + +```py +for epoch in range(number_of_epochs): + # check loss on train + loss = 0.0 + + # do a forward pass to get prediction + for i in range(Y.shape[0]): + x, y = X[i], Y[i] + prev_act = np.zeros((hidden_layer_size, 1)) + for t in range(sequence_length): + new_input = np.zeros(x.shape) + new_input[t] = x[t] + mul_w1 = np.dot(W1, new_input) + mul_w2 = np.dot(W2, prev_act) + add = mul_w2 + mul_w1 + act = sigmoid(add) + mul_w3 = np.dot(W3, act) + prev_act = act + # calculate error + loss_per_record = (y - mul_w3)**2 / 2 + loss += loss_per_record + loss = loss / float(y.shape[0]) + + # check loss on validation + val_loss = 0.0 + for i in range(Y_valid.shape[0]): + x, y = X_valid[i], Y_valid[i] + prev_act = np.zeros((hidden_layer_size, 1)) + for t in range(sequence_length): + new_input = np.zeros(x.shape) + new_input[t] = x[t] + mul_w1 = np.dot(W1, new_input) + mul_w2 = np.dot(W2, prev_act) + add = mul_w2 + mul_w1 + act = sigmoid(add) + mul_w3 = np.dot(W3, act) + prev_act = act + loss_per_record = (y - mul_w3)**2 / 2 + val_loss += loss_per_record + val_loss = val_loss / float(y.shape[0]) + print('Epoch: ', epoch + 1, ', Loss: ', loss, ', Val Loss: ', val_loss) + + # train model + for i in range(Y.shape[0]): + x, y = X[i], Y[i] + + layers = [] + prev_act = np.zeros((hidden_layer_size, 1)) + dW1 = np.zeros(W1.shape) + dW3 = np.zeros(W3.shape) + dW2 = np.zeros(W2.shape) + + dW1_t = np.zeros(W1.shape) + dW3_t = np.zeros(W3.shape) + dW2_t = np.zeros(W2.shape) + + dW1_i = np.zeros(W1.shape) + dW2_i = np.zeros(W2.shape) + + # forward pass + for t in range(sequence_length): + new_input = np.zeros(x.shape) + new_input[t] = x[t] + mul_w1 = np.dot(W1, new_input) + mul_w2 = np.dot(W2, prev_act) + add = mul_w2 + mul_w1 + act = sigmoid(add) + mul_w3 = np.dot(W3, act) + layers.append({'act':act, 'prev_act':prev_act}) + prev_act = act + # derivative of pred + dmul_w3 = (mul_w3 - y) + + # backward pass + for t in range(sequence_length): + dW3_t = np.dot(dmul_w3, np.transpose(layers[t]['act'])) + dsv = np.dot(np.transpose(V), dmul_w3) + + ds = dsv + dadd = add * (1 - add) * ds + + dmul_w2 = dadd * np.ones_like(mul_w2) + dprev_act = np.dot(np.transpose(W2), dmul_w2) + for i in range(t-1, max(-1, t-back_prop_truncate-1), -1): + ds = dsv + dprev_act + dadd = add * (1 - add) * ds + dmul_w2 = dadd * np.ones_like(mul_w2) + dmul_w1 = dadd * np.ones_like(mul_w1) + dW2_i = np.dot(W2, layers[t]['prev_act']) + dprev_act = np.dot(np.transpose(W2), dmul_w2) + new_input = np.zeros(x.shape) + new_input[t] = x[t] + dW1_i = np.dot(U, new_input) + dx = np.dot(np.transpose(U), dmul_w1) + dW1_t += dW1_i + dW2_t += dW2_i + + dW3 += dW3_t + dW1 += dW1_t + dW2 += dW2_t + + if dW1.max() > max_clip_value: + dW1[dW1 > max_clip_value] = max_clip_value + if dW3.max() > max_clip_value: + dW3[dW3 > max_clip_value] = max_clip_value + if dW2.max() > max_clip_value: + dW2[dW2 > max_clip_value] = max_clip_value + + if dW1.min() < min_clip_value: + dW1[dW1 < min_clip_value] = min_clip_value + if dW3.min() < min_clip_value: + dW3[dW3 < min_clip_value] = min_clip_value + if dW.min() < min_clip_value: + dW2[dW2 < min_clip_value] = min_clip_value + + # update + W1 -= learning_rate * dW1 + W3 -= learning_rate * dW3 + W2 -= learning_rate * dW2 +``` + +输出应该看起来像这样: + +![](img/B15441_21_11.png) + +图 9:5 个历元的 RNN 训练输出 + +如您所见,损失和验证损失在每个时期都在不断减少。 我们可能会再运行几个模型,并确保结果收敛。 + +这是一个有 10 个历时的示例: + +![](img/B15441_21_12.png) + +图 10:10 个历元的 RNN 训练输出 + +如您所见,在第 6 个阶段之后,结果已经收敛。 找出最佳时期数是一个*试验和错误*过程。 从这些结果可以看出,大约 6 个纪元是正确的纪元数。 + +在这个例子中,我们几乎已经走到了尽头。 让我们根据预测值绘制初始输入数据集,然后看看如何得出。 以下是相关代码: + +```py +preds = [] +for i in range(Y_valid.shape[0]): + x, y = X_valid[i], Y_valid[i] + prev_act = np.zeros((hidden_layer_size, 1)) + # For each time step... + for t in range(sequence_length): + mul_w1 = np.dot(W_1, x) + mul_w2 = np.dot(W2, prev_act) + add = mul_w2 + mul_w1 + act = sigmoid(add) + mul_w3 = np.dot(W3, act) + prev_act = act + preds.append(mul_w3) + +preds = np.array(preds) +plt.plot(preds[:, 0, 0], 'g') +plt.plot(Y_valid[:, 0], 'r') +plt.show() +``` + +这是的结果: + +![](img/B15441_21_13.png) + +图 11:RNN 训练预测与实际图 + +如我们所见,预测值与初始值是相当一致的,因此这是一个不错的结果。 RNN 本质上是随机的,因此下一次我们运行示例时,对齐方式可能会更好或更坏。 您可能会看到不同的对齐方式。 + +最后,让我们计算**均方根误差**( **RMSE** )分数: + +```py +from sklearn.metrics import mean_squared_error +math.sqrt(mean_squared_error(Y_valid[:, 0], preds[:, 0, 0])) +``` + +RMSE 是方差的平方根。 可以将其解释为无法解释的方差的标准偏差。 它具有与响应变量具有相同单位的有用属性。 RMSE 值越低表示拟合度越好。 + +在这种情况下,我们得到的分数很低,表明我们的模型正在产生不错的预测: + +```py +0.5691944360057564 +``` + +这是一个实际的 RNN 的简单示例。 而且,如您所见,这不是简单的两行程序! 这凸显了一个事实,即 RNN 实施起来并不简单,将其应用于复杂任务是一项真正的任务。 + +此外,RNN 的计算量很大,因此生成准确的预测可能会花费大量时间。 如果我们无法使用合适的硬件,那几乎是肯定的。 + +尽管如此,RNN 已经取得了许多突破,并且数据科学界继续不断地为其寻找新的应用程序。 不仅如此,它们的性能和准确性也在不断提高。 到目前为止,我们将提高您已经设计的神经网络的性能和功能,并提出您自己的示例,说明如何将网络应用于其他领域。 快乐的编码。 + +# 摘要 + +在本章中,我们继续学习深度学习并学习了 RNN 的基础。 然后,我们讨论了 RNN 架构的基本概念是什么,以及为什么这些概念很重要。 在学习了基础知识之后,我们研究了 RNN 的一些潜在用途,并着眼于使用它来实现语言模型。 最初,我们使用基本技术来实现语言模型,然后开始为模型添加越来越多的复杂性以理解更高层次的概念。 + +希望您和我们一样兴奋,进入下一章,我们将学习如何使用强化学习来创建智能代理。 \ No newline at end of file diff --git a/docs/ai-py/22.md b/docs/ai-py/22.md new file mode 100644 index 0000000000000000000000000000000000000000..3527463f35f2576e8eae5217cf66f4fada5d54df --- /dev/null +++ b/docs/ai-py/22.md @@ -0,0 +1,328 @@ +# 22 + +# 通过强化学习创建智能代理 + +在本章中,我们将学习**强化学习**( **RL** )。 我们将讨论 RL 的前提。 我们将讨论 RL 和监督学习之间的区别。 我们将通过一些真实的 RL 实例来了解 RL 如何以各种形式表现出来。 我们将学习 RL 的组成部分以及所涉及的各种概念。 然后,我们将在 Python 中创建一个环境,以了解其在实际中的工作方式。 然后,我们将使用这些概念来构建学习代理。 + +在本章中,我们将介绍以下主题: + +* 了解学习的意义 +* 强化学习与监督学习 +* RL 的真实示例 +* RL 的构成要素 +* 创造环境 +* 建立学习代理 + +在进入 RL 本身之前,我们首先考虑一下学习的真正含义。 毕竟,它将帮助我们在尝试实施之前理解它! + +# 了解学习的意义 + +学习的概念是人工智能的基础。 我们希望机器了解学习的过程,以便他们可以自己完成学习。 人类通过观察周围环境并与周围环境互动来学习。 当您去一个新地方时,您可以快速扫描并查看周围发生的事情。 + +没有人在教你怎么做。 您正在观察周围的环境并与之互动。 通过与环境建立这种联系,我们倾向于收集有关导致不同原因的大量信息。 我们了解因果关系,哪些行动会导致什么结果以及我们需要做些什么才能实现目标。 + +我们在生活中的每一个地方都使用这种学习过程。 我们收集了有关周围环境的所有知识,然后了解我们如何对此做出反应。 让我们考虑一个演说家的例子。 每当好的演说家在公开场合发表演讲时,他们就会知道人群对他们的发言有何反应。 如果人群没有响应,那么演说者会实时更改语音以确保人群参与。 如我们所见,演说者正试图通过他/她的行为来影响环境。 可以说,演说者*从与人群的互动中学到了*,以便采取行动并达到一定的目标。 这种学习过程–观察环境,采取行动,评估该行动的后果,适应和再次采取行动–是人工智能中许多主题所基于的最基本的思想之一。 让我们牢记这一点来谈论 RL。 + +RL 是指学习操作方法并将情况映射到某些动作以最大化回报的过程。 在大多数机器学习范式中,学习代理会被告知要采取哪些行动才能获得一定的结果。 在加强学习的情况下,不会告知学习代理要采取什么行动。 取而代之的是,它必须通过尝试找出哪些行动会产生最高的回报。 这些行动往往会影响立即的报酬以及下一个情况。 这意味着所有后续奖励也将受到影响。 + +思考 RL 的一种好方法是理解我们定义的是学习问题而不是学习方法。 因此,可以说可以解决问题的任何方法都可以视为 RL 方法。 RL 的两个显着特征-反复试验学习和延迟奖励。 RL 代理使用这两个功能从中学习其行为的后果。 + +# 强化学习与监督学习 + +当前许多研究都集中在监督学习上。 RL 似乎有点像监督学习,但事实并非如此。 监督学习的过程是指从标记的样本中学习。 尽管这是一种有用的技术,但不足以从交互中开始学习。 当我们想设计一台机器来导航未知的地形时,这种学习将无济于事。 我们事先没有可用的培训样本。 + +我们需要一个可以通过与未知地形交互来从自身经验中学习的代理。 这是 RL 真正闪耀的地方。 + +让我们考虑一下代理与新环境进行交互以进行学习时的探索阶段。 它可以探索多少? 在这一点上,该代理不知道环境有多大,并且在许多情况下,它将无法探索所有可能性。 那么,代理应该做什么? 它是应该从其有限的经验中吸取教训,还是等到进一步探索后再采取行动? 这是 RL 的主要挑战之一。 为了获得更高的报酬,代理必须支持经过尝试和测试的行为。 但是为了发现此类动作,它应继续尝试以前未选择的较新动作。 多年来,研究人员已经广泛研究了在勘探与开发之间的这种[b] 折衷方案,但它仍然是活跃的[b] 视差镜。 + +# 强化学习的实际示例 + +让我们看看 RL 在现实世界中出现的位置。 这将帮助我们了解它的工作原理以及使用此可以构建哪些可能的应用程序,除非: + +**游戏性** –让我们考虑一下棋类游戏,例如 Go 或 Chess。 为了确定最佳动作,玩家需要考虑各种因素。 可能性的数量如此之大,以至于无法进行暴力搜索。 如果我们要使用传统技术制造一台可以玩这种游戏的机器,则需要指定许多规则来涵盖所有这些可能性。 RL 完全绕过了这个问题。 我们不需要手动指定任何逻辑规则。 学习代理仅通过示例学习并与自己玩游戏。 + +有关此主题的更详尽讨论,请参阅*第 2 章*和*人工智能基本用例*中的*游戏*部分。 + +**机器人技术** –让我们考虑一个机器人的工作,该机器人的工作是探索新建筑物。 它应确保它有足够的功率返回基站。 该机器人必须通过考虑所收集的信息量与安全返回基站的能力之间的权衡来决定是否应该做出决定。 + +有关此主题的更多信息,请跳至*第 2 章*和*人工智能基本用例*中的*装运和仓库管理*部分。 + +**工业控制器** –考虑调度电梯的情况。 一个好的调度程序将花费最少的电量,并为最多的人员服务。 对于此类问题,RL 代理可以学习如何在模拟环境中执行此操作。 然后,他们可以利用这些知识来制定最佳计划。 + +**婴儿** –机器在使用 RL 方面没有垄断权; 刚开始的几个月里,新生儿经历了几乎相同的过程。 他们通过反复尝试来学习,直到学会平衡为止。 有趣的是,婴儿发现了走路(或跑步!)是最有效的方式之后,发现了不同的运动方法。 + +如果仔细观察这些示例,您将看到一些共同的特征。 所有这些都涉及与环境的交互。 即使环境不确定,学习代理(无论是机器,婴儿还是其他设备)也旨在实现某个目标。 代理的动作将改变该环境的未来状态。 随着代理继续与环境交互,这会在以后的时间影响可用的机会。 + +在讨论了 RL 是什么之后,并涵盖了一些真实的示例,现在让我们开始研究 RL。 我们将从讨论 RL 系统的构建块开始。 + +# 强化学习的基础 + +除了代理与环境之间的交互,RL 系统中还有其他因素在起作用: + +![](img/B15441_22_01.png) + +图 1:增强材料 l 的构成 + +通常,RL 代理执行以下步骤: + +1. 有一组与代理和环境有关的状态。 在给定的时间点,代理会观察输入状态以感知环境。 +2. 有政策规定需要采取什么措施。 这些政策具有决策功能。 使用这些策略根据输入状态确定操作。 +3. 代理根据上一步采取行动。 +4. 环境对此动作做出反应。 代理从环境中获得强化,也称为奖励。 +5. 代理计算并记录有关该奖励的信息。 重要的是要注意,将为此状态/动作对收到此奖励,以便在给定特定状态的情况下,将来可将其用于采取更多的奖励行动。 + +RL 系统可以同时执行多个事情–通过执行试错搜索来学习,了解所处环境的模型,然后使用该模型来计划下一步。 在下一节中,我们将动手操作,并开始使用流行的 fr amework 使用 Python 编写的强化学习系统。 + +# 创建环境 + +我们将使用名为 **OpenAI Gym** 的程序包来构建 RL 代理。 您可以在此处了解更多有关的信息: [https://gym.openai.com](https://gym.openai.com) 。 可以通过运行以下命令使用`pip`进行安装: + +```py +$ pip3 install gym +``` + +您可以在此处找到与其安装相关的各种提示和技巧: + +[https://github.com/openai/gym#installation](https://github.com/openai/gym#installation) + +现在您已经安装了它,让我们继续编写一些代码。 + +创建一个新的 Python 文件并导入以下软件包: + +```py +import argparse +import gym +``` + +定义一个函数来解析输入参数。 输入参数将用于指定要运行的环境的类型: + +```py +def build_arg_parser(): + parser = argparse.ArgumentParser(description='Run an environment') + parser.add_argument('--input-env', dest='input_env', required=True, + choices=['cartpole', 'mountaincar', 'pendulum', 'taxi', 'lake'], + help='Specify the name of the environment') + return parser +``` + +定义`main`函数并解析输入参数: + +```py +if __name__=='__main__': + args = build_arg_parser().parse_args() + input_env = args.input_env +``` + +创建一个从输入参数字符串到 OpenAI Gym 软件包中指定的环境名称的映射: + +```py + name_map = {'cartpole': 'CartPole-v0', + 'mountaincar': 'MountainCar-v0', + 'pendulum': 'Pendulum-v0', + 'taxi': 'Taxi-v1', + 'lake': 'FrozenLake-v0'} +``` + +根据输入参数创建环境,并将其重置: + +```py + # Create the environment and reset it + env = gym.make(name_map[input_env]) + env.reset() +``` + +重复`1000`次并在每个步骤中随机执行一个操作: + +```py + # Iterate 1000 times + for _ in range(1000): + # Render the environment + env.render() + # take a random action + env.step(env.action_space.sample()) +``` + +完整代码在文件`run_environment.py`中给出。 如果您想知道如何运行代码,请使用`help`参数运行它,如下图所示: + +![](img/B15441_22_02.png) + +图 2:运行 Python 程序的命令 + +让我们在非常规环境下运行它。 运行以下命令: + +```py +$ python3 run_environment.py --input-env cartpole +``` + +如果运行它,您将看到一个窗口,其中显示**磁极**向右移动。 以下屏幕截图显示了初始位置: + +![](img/B15441_22_03.png) + +图 3:Cartpole 示例输出 + +在下一秒钟左右,您将看到它移动,如以下屏幕截图所示: + +![](img/B15441_22_04.png) + +图 4:Cartpole 示例输出 2 + +在端,您将看到它从窗口中消失,如以下屏幕截图所示: + +![](img/B15441_22_05.png) + +图 5:Cartpole 示例输出 3 + +让我们使用`mountaincar`参数运行它。 运行以下命令: + +```py +$ python3 run_environment.py --input-env mountaincar +``` + +如果运行代码,则最初将看到下图: + +![](img/B15441_22_06.png) + +图 6:山地车示例输出 + +如果让它运行几秒钟,您会看到汽车摆动得更多,从而达到了标志: + +![](img/B15441_22_07.png) + +图 7:山地车示例输出 2 + +它将保持更大的步幅,如下图 所示: + +![](img/B15441_22_08.png) + +图 8:山地车示例输出 3 + +在本节的第一个示例中,没有发生任何令人兴奋的事情。 它只是四处走动。 该示例确实使我们对所使用的 RL 框架有了基本的了解。 第二个示例更令人兴奋。 它实际上有一个目标(触摸标志)。 实现目标通常是如何构造强化问题。 在下一个示例中,我们将继续使事情变得更加有趣,并将我们的目光投向一个稍微复杂的目标。 + +# 建立学习代理 + +在本节的中,我们将基于第一个例子进行研究。 最初,柱子只是在四处移动。 现在,我们将尝试平衡购物车顶部的电线杆,并确保电线杆保持直立。 准备学习更多吗? 让我们开始吧。 + +首先,创建一个新的 Python 文件并导入以下软件包: + +```py +import argparse +import gym +``` + +定义一个函数来解析输入参数: + +```py +def build_arg_parser(): + parser = argparse.ArgumentParser(description='Run an environment') + parser.add_argument('--input-env', dest='input_env', required=True, + choices=['cartpole', 'mountaincar', 'pendulum'], + help='Specify the name of the environment') + return parser +``` + +解析输入参数: + +```py +if __name__=='__main__': + args = build_arg_parser().parse_args() + input_env = args.input_env +``` + +在 OpenAI Gym 程序包中建立从输入参数到环境名称的映射: + +```py + name_map = {'cartpole': 'CartPole-v0', + 'mountaincar': 'MountainCar-v0', + 'pendulum': 'Pendulum-v0'} +``` + +根据输入参数创建环境: + +```py + # Create the environment + env = gym.make(name_map[input_env]) +``` + +通过重置环境开始迭代: + +```py + # Start iterating + for _ in range(20): + # Reset the environment + observation = env.reset() +``` + +对于每次复位,迭代`100`次。 首先渲染环境: + +```py + # Iterate 100 times + for i in range(100): + # Render the environment + env.render() +``` + +打印当前观察值并根据可用的操作空间执行操作: + +```py + # Print the current observation + print(observation) + # Take action + action = env.action_space.sample() +``` + +提取采取当前措施的后果: + +```py + # Extract the observation, reward, status and + # other info based on the action taken + observation, reward, done, info = env.step(action) +``` + +检查我们是否实现了目标: + +```py + # Check if it's done + if done: + print('Episode finished after {} timesteps'.format(i+1)) + break +``` + +完整代码在文件`balancer.py`中给出。 如果您想知道如何运行代码,请使用`help`参数运行它,如以下屏幕截图所示: + +![](img/B15441_22_09.png) + +图 9:运行 Python 平衡器示例的命令 + +让我们在`cartpole`环境中运行代码。 运行以下命令: + +```py +$ python3 balancer.py --input-env cartpole +``` + +如果运行代码,您将看到小柱平衡自身: + +![](img/B15441_22_10.png) + +图 10:Cartpole 示例输出 4 + +如果让它运行几秒钟,您将看到小刀杆仍然站立,如以下屏幕截图所示: + +![](img/B15441_22_11.png) + +图 11:Cartpole 示例输出 5 + +您应该会看到许多打印出来的信息。 如果您看其中的一集,它将看起来像这样: + +![](img/B15441_22_12.png) + +图 12:情节输出 + +不同的情节需要完成不同的步骤。 如果您滚动查看打印出来的信息,您将能够看到。 希望,当您运行本示例时,您至少在大多数情况下都会看到棘突。 凭借在本章中获得的知识,我认为我们还没有准备好在 Go 的游戏中击败 AlphaZero。 但是我们了解了如何构建此类系统的基础知识。 + +# 摘要 + +在本章中,我们了解了 RL 系统。 我们讨论了 RL 的前提以及如何设置它。 我们讨论了 RL 和监督学习之间的区别。 我们浏览了一些真实的 RL 示例,并了解了各种系统如何以不同形式使用它。 + +我们讨论了 RL 的构建块以及诸如代理,环境,策略,奖励等概念。 然后,我们在 Python 中创建了一个环境,以查看其运行情况。 最后,我们使用这些概念来构建 RL 代理。 + +在下一章中,我们将进入一个截然不同的主题,并学习大数据技术如何帮助我们使机器学习系统更强大,更高效。 \ No newline at end of file diff --git a/docs/ai-py/23.md b/docs/ai-py/23.md new file mode 100644 index 0000000000000000000000000000000000000000..c03d6705fb5f3fd49eb8bd4f18c78aa1eba2f94c --- /dev/null +++ b/docs/ai-py/23.md @@ -0,0 +1,417 @@ +# 23 + +# 人工智能和大数据 + +在本章中,我们将学习什么是大数据以及如何在人工智能的背景下使用大数据技术。 我们将讨论大数据如何帮助加速机器学习流程。 我们还将讨论使用大数据技术什么时候是一个好主意,何时使用它们是过时的,并通过一些示例来加深我们的理解。 我们将学习使用大数据的机器学习管道的组成部分以及所涉及的各种挑战,并且将在 Python 中创建一个环境,以了解其在实践中的工作方式。 在本章的最后,我们将介绍: + +* 大数据基础 +* 大数据的三个 V +* 适用于人工智能和机器学习的大数据 +* 使用大数据的机器学习管道 +* 阿帕奇 Hadoop +* Apache Spark +* Apache Impala +* NoSQL 数据库 + +让我们从大数据的基础开始。 + +# 大数据基础 + +是您今天定期进行的一项活动,而您十年前很少做,而且肯定二十年前从未做过。 但是,如果被告知您再也做不到,您会感到完全受阻。 您可能已经在今天或至少在本周进行了几次。 我在说什么 想猜一猜吗? 我说的是执行 Google 搜索。 + +Google 已经存在了很短的时间,但是现在我们非常依赖它。 它扰乱并颠覆了包括杂志出版,电话簿,报纸等在内的众多行业。 如今,每当我们遇到知识难题时,我们都会用 Google 抓它。 由于我们通过手机永久链接到互联网,因此尤其如此。 Google 几乎已经成为我们的延伸。 + +但是,您是否已停止考虑所有这些令人难以置信的知识发现背后的机制? 实际上,我们触手可及的位置上有数十亿个文档,可以在几毫秒内访问它们,就像魔术一样,最相关的文档通常显示在前几个结果中,通常会为我们提供所需的答案。 有时我会觉得 Google 正在读我的想法,并且在开始键入关键字之前就知道我想要什么。 + +Google 提供这些答案的基本技术之一就是通常所说的大数据。 但是什么是大数据? 这个名字本身并没有给我们太多。 *数据*可以是存储在计算机上的任何数据,而*大*是相对术语。 在定义什么是*大数据*之前,让我先介绍一下我对该主题最喜欢的观察之一: + +![](img/B15441_23_01.png) + +图 1:大数据类比 + +这句话使人们明白,大数据对不同的人意味着不同的事物。 这种情况开始改变,但是传统数据库在我们可以存储多少信息以及可扩展的最大范围上有一个上限。 如果您正在使用 Hadoop,Spark,NoSQL 数据库和图形数据库等新技术,那么您正在使用大数据。 没有什么是无限的,但是这些技术可以横向扩展(而不是纵向扩展),因此可以更有效地扩展规模。 没有任何东西可以无限扩展,但是在传统技术(如传统数据库)与新技术(如 Hadoop 和 Spark)之间存在根本差异。 随着供应商和诸如 Snowflake 之类的工具的出现,也开始使用普通的 SQL 处理 PB 级数据库。 + +随着业务的增长和对资源的需求的增加,如果他们使用的是传统体系结构,则他们将通过升级到更大,功能更强的计算机来扩展。 可以预见,这种增长方式将达到极限。 硬件制造商制造的机器只有这么大。 + +相反,如果企业正在使用新技术并且遇到瓶颈,则可以通过*向外扩展*并向其中添加新机器而不是更换硬件来突破瓶颈。 由于我们正在增加火力,因此这个新增加的不必一定更大。 它通常与旧机器相同。 这种增长方法更具可扩展性。 我们可能有成千上万的机器协同工作来解决问题。 + +由一组计算资源进行的协作通常称为*群集*。 集群是统一的*节点*的集合,它们在一个共同的目标中进行协作,例如存储和分析大量结构化或非结构化数据。 这些集群通常在低成本商品硬件上运行开源分布式处理软件。 + +这样的集群在低成本的商用计算机上运行 Hadoop 的开源分布式处理软件。 群集通常称为*,不共享*系统,因为节点之间唯一共享的资源是连接它们的网络。 + +集群体系结构可以大大提高数据分析的处理速度。 该架构还具有高度可扩展性:当集群的处理能力因某个工作负载而无法承受时,可以轻松添加其他节点以提高吞吐量。 此外,集群体系结构本质上是容错的。 + +每个数据组件都冗余地复制到多个节点上,以确保不会因为单个节点故障而丢失数据。 数据丢失并非不可能,但极不可能。 + +![](img/B15441_23_02.png) + +图 2:集群和节点 + +为了更好地理解大数据,让我们继续研究利用大数据技术的最佳示例之一。 让我们继续分析 Google 搜索。 出于显而易见的原因,Google 搜索的许多实际内部运作都是专有的。 我们正在推测,但很可能只有 Google 内部的一些员工才能知道 Google 工作方式的确切细节。 + +可能没有人完全了解或了解 Google 搜索的每个细节。 我们其他人会根据 Google 时不时发布的各种信息做出有根据的猜测。 + +推测它可能如何工作仍然是有益的(而且很有趣)。 Google 的界面使它看起来很简单,除了用一组单词接收请求并根据该请求返回响应外,似乎并没有做其他事情。 我仍然记得我第一次看到 Google 搜索页面,并且感到非常困惑,无法确定页面是否已完成加载。 那是在拨号调制解调器时代,雅虎比 Google 更大。 如果您最近访问过 Yahoo 主页并且看到它仍然很忙,您将理解我的困境。 但是,通过简单的 Google 搜索请求,幕后发生了很多事情。 更进一步,将采取以下步骤: + +1. 接收查询 +2. 查询解析 +3. 建立和规范字序 +4. 在 Google 索引中查找信息。 在执行此查找时,通过考虑以下因素来个性化结果: + * 个人喜好 + * 以前的搜索记录 + * Google 知道的关于您的个人信息(很多) +5. 对结果进行排名(对结果进行排名的大部分工作已在发送查询之前完成) +6. 发回回应 + +这是一个示例查询,找到了多少个结果以及返回结果所花费的时间: + +![](img/B15441_23_03.png) + +图 3:Google 搜索,显示的结果数 + +一个重要的考虑因素是,当您提交查询时,Google 不会搜索互联网。 到进行查询时,为快速响应已为您完成了许多工作。 在以下各节中,我们将讨论一些在后台进行的活动。 + +## 爬行 + +为了让 Google 提供结果,它需要知道可用的内容。 并没有神奇的中央存储库,它具有所有网页和网站,因此 Google 会不断搜索新的网站和页面,并将它们添加到其已知页面列表中。 此发现过程称为爬网。 + +有些页面在 Google 的网站列表中,因为 Google 以前已经对其进行了爬网。 当 Google 跟踪从已知页面到新页面的链接时,就会发现某些页面。 网站所有者或代理商提交网站以使 Google 知道该网站及其站点地图时,还会发现其他页面。 + +## 索引 + +一旦发现页面并将其添加到 Google 的网站列表中,Google 就会尝试了解其内容。 此过程称为索引编制。 Google 分析网站的内容,并对网站中的图像和视频进行分类。 + +然后将结果存储在 Google 索引中。 您可以想象,该索引确实符合大数据的定义。 + +通过创建此索引,Google 会搜索其互联网索引,而不是搜索互联网。 这似乎是一个很小的细节,但这是 Google 搜索如此快速的重要原因。 + +这样想吧。 当您去图书馆时,可以通过以下两种方式之一来查找书籍。 您可以转到卡片目录(假设它是一所旧学校图书馆),也可以在过道周围走动,直到找到所需的书。 您认为哪种方法会更快? + +将典型库中包含的信息与 Google Universe 中包含的数据进行比较,我们很快就会意识到反对索引而不是原始数据的重要性。 + +Google 的工作方式与图书馆卡目录类似,不同之处在于 Google 的“目录”或索引包含指向大量网页的指针,并且比本地图书馆目录中的索引大得多。 根据 Google 自己的文档,他们承认其索引至少为 100 PB,并且可能是该索引的许多倍。 更多信息可以在这里找到: + +[https://www.google.com/search/howsearchworks/crawling-indexing/](https://www.google.com/search/howsearchworks/crawling-indexing/ ) + +如果您好奇的话,这里有一个很酷的网站,它将为您提供有关被索引的网页数量的实时估计。 试试看: + +[http://www.worldwidewebsize.com/](http://www.worldwidewebsize.com/ ) + +## 排名 + +当您使用搜索引擎寻找问题的答案时,Google 会使用许多因素来提供最相关的答案。 最重要的之一是 *PageRank* 的概念。 我们将让读者进一步了解该概念。 Google 使用其他标准来确定相关性,包括: + +* 用户语言设置 +* 以前的搜索记录 +* 地理位置 +* 设备(台式机或电话)的类型,品牌和型号 + +例如,搜索*药房*将为班加罗尔的用户提供不同于伦敦用户的结果。 + +正如我们在上一节中所看到的,Google 做了很多工作来使搜索快速进行。 但这并不是 Google 提供其魔力的全部。 Google 还可以如何加快搜索速度? + +## 全球数据中心 + +与可口可乐配方一样,Google 喜欢将其*秘密调味料* 成分保密,因此 Google 不会发布有关其拥有多少个数据中心以及它们位于何处的详细信息。 可以肯定的是,他们有很多,而且遍布世界各地。 + +无论如何,当您向 Google 提交查询时,智能网络路由器都会将您的搜索查询扩展到距离您最近的且可用于执行搜索的数据中心。 + +更多信息可以在这里找到: + +[https://netvantagemarketing.com/blog/how-does-google-return-results-so-damn-fast/](https://netvantagemarketing.com/blog/how-does-google-return-results-so-damn-fast/ ) + +## 分布式查找 + +因此,Google 有许多数据中心,这些数据中心中的每一个都容纳数百个(如果不是数千个)商用服务器。 这些计算机已连接并以同步方式一起工作以执行用户请求。 当搜索查询到达数据中心时,主服务器接收到该请求,然后将其分解为较小的批处理并将查找作业分配给多个从属节点。 这些从属节点中的每一个都分配有 Google Web 索引的分区,并分配了针对给定查询返回最相关结果的任务。 这些结果将返回到主服务器,然后由主服务器进一步过滤,组织和排序合并的结果,然后再将其发送回请求者。 + +当查询提交给 Google 时,它利用了其中一些服务器的功能。 这些服务器可以处理同时查找。 + +## 定制软件 + +支持这些服务器的绝大部分(即使不是全部)关键软件都是由 Google 工程师量身定制的,专门供 Google 使用,包括: + +* 自定义蜘蛛爬网网站 +* 专有内部数据库 +* 定制编程语言 +* 专有文件系统称为 **Google 文件系统**( **GFS** )或 Colossus + +希望您觉得自己对大数据有了更好的了解。 现在让我们增加一些混乱,让我们看看为什么当进入大数据时,大小并不是唯一重要的事情。 + +# 大数据的三个 V + +不久以前习惯了在 90 天左右后清除应用程序生成的任何日志。 最近,公司意识到他们正在丢弃信息的金块。 此外,存储已经变得足够便宜,以至于保留这些日志都不费吹灰之力。 而且,云,互联网和技术的普遍发展现在可以创建更多的数据。 从智能手机到 IoT 小工具,工业传感器和监控摄像头,用于存储和传输数据的设备数量在世界范围内呈指数级增长,导致数据量激增。 + +![](img/B15441_23_04.png) + +图 4:大数据的三个 V + +根据 IBM 的数据,2012 年每天产生 2.5 EB 的数据。无论以何种方式衡量,这都是一个很大的数字。 同样,大约 75%的数据是非结构化的,来自文本,语音和视频等来源。 + +这么多正在创建的新数据没有被构造的事实带来了关于大数据的另一个好处。 数据量不是使大数据难以处理的唯一特征。 在处理数据时,至少要考虑另外两个项目,这会增加处理的复杂性。 它们通常被称为速度和变化。 让我们仔细看看通常所说的三个 V。 + +## 音量 + +由于明显的原因,卷是最常与大数据关联的特性。 让我们来看一些其他示例,以及在这种情况下大数据的含义。 + +在其他服务中,Facebook 存储图像。 没关系吧? 但是,让我们回顾一下有关 Facebook 的一些统计数据: + +* Facebook 的用户数量超过中国的用户数量 +* 每天约有 50 万人创建新的个人资料 +* Facebook 正在存储约 2500 亿张图像 + +那是大数据,那是数量。 对于 Facebook 和我们周围的整个世界,这些数字将继续增长。 考虑一下,当今世界上的手机数量已经超过了世界人数,并且每个人都在不断收集数据并将其发送到世界各地的 Google,Apple 和 Facebook。 大数据挑战将不断扩大。 堆积在现在变得流行的物联网和智能家居设备上,也许我们现在会开始不知所措。 + +## 速度 + +速度是衡量数据输入速度的指标。处理 TB 和 PB 的数据是一项挑战。 另一个更大的挑战是处理以下事实:该数据可能不是静态的,并且会持续增长。 在生成时,我们不仅必须接收它,而且在某些情况下,可能还需要对其进行处理,转换,分析和用于训练。 + +如果我们只需要担心数据量,就可以潜在地使用传统技术来处理数据并最终完成工作。 数据进入的速度要求以快速有效的方式处理数据,最重要的是,以快速的速度使用它来从中获取见解。 + +坚持使用 Facebook 示例。 2500 亿张图片肯定是一个大数目,而其他统计数据当然也可以视为大数目。 但这不是一个静态数字。 + +回想一下 2012 年的每一分钟: + +* 发表了 510,000 条评论 +* 293,000 个状态已更新 +* 上传了 136,000 张照片 + +很难理解这一卷的庞大性。 每天,这相当于大约: + +* 每天 7.35 亿条评论 +* 每天 4.21 亿状态更新 +* 每天上传 1.95 亿张图片 + +如今,Facebook 对其指标的要求越来越严格,因此很难获得最新的统计数据。 有关 Facebook 流量和其他热门网站的更多有趣统计信息,请参见以下网址: + +[http://thesocialskinny.com/100-social-media-statistics-for-2012/](http://thesocialskinny.com/100-social-media-statistics-for-2012/ ) + +当涉及到数据时,Facebook 必须将其摄取,处理,索引,存储,然后再进行检索。 这些数字会略有不同,具体取决于您相信的来源以及数据的最新程度。 Facebook 最近由于政治原因而成为新闻。 忘记周围的所有争论。 您能想象他们手头上存在的技术问题,根据我们上面描述的数量和速度来确定图像是否令人反感,有争议,政治,真实或不真实等等。 + +可以想象,这些数字一直在增加。 就像在物理学中一样,您不仅具有速度,还可能具有加速度。 + +## 品种 + +在过去的好日子中,大多数数据存储在整齐的列和行中。 随着我们收集越来越多的数据,情况已不再如此。 我们正在收集和存储照片,传感器数据,IoT 设备信息,推文,加密的数据包,语音,视频等。 这些都彼此非常不同。 这些数据不是过去几天的整齐的行和列以及数据库联接。 其中大部分是非结构化的。 这意味着它不容易存储在数据库中并建立索引。 + +为了更好地理解,让我们从一个简单的示例开始。 假设您是即将发生的重要案件的原告律师。 作为发现的一部分,辩护团队最近向您发送了一堆在线和物理文件(在法律背景下,*发现*是原告和辩方交换他们将在审判期间出示的证据的过程) 这样他们每个人都可以提出反驳)。 这些文件包括语音邮件,电话录音,电子邮件,录像内容,物理文件,电子邮件(带有附件)和计算机文件。 + +法律上要求辩护人出示与案件有关的所有文件,但不需要他们为您整理文件。 他们向您发送额外的信息以增加混乱的情况并不少见。 辩护人只希望您错过一些关键信息,而这些关键信息可能是破解您的案件并为您的客户取得积极成果的关键。 法庭肯定会在您需要陈述案件的时间上设定最后期限和里程碑。 即使您在数量和速度上都没有处理 Facebook 级别的数字,在这种情况下,您数据的多样性比大多数 Facebook 数据更为复杂。 这就是使您的工作困难的原因。 + +我们不会详细介绍如何解决该项目,但让我们重点介绍一些必须解决的任务,其中机器学习和大数据将帮助您解决: + +1. 扫描物理文件并执行 OCR。 +2. 转录语音邮件,电话录音和录像内容。 索引它们。 我们正在尝试将数据格式标准化为机器可读的计算机文件,然后对其进行统一扫描和搜索。 +3. 从电子邮件中分离附件。 + +1. 编制索引并使所有信息可搜索(有开源和专有工具可以帮助此过程,例如 Solr,Sinequa,Elastic Search 和 Amazon Kendra)。 + +在本部分中,我们确定了,这些方法可以使用大数据工具和各种见识来筛查所有这些疯狂问题,从而使用机器学习来解决问题,辨别模式并确定机会。 在以下各节中,我们将学习可用于实现此目的的实际工具。 + +# 大数据与机器学习 + +大数据技术已被世界各地的技术公司成功利用。 当今的企业了解大数据的力量,他们意识到与机器学习结合使用时,其功能甚至会更加强大。 + +机器学习系统与大数据技术相结合,以多种方式帮助企业,包括比以往任何时候都更具战略性地管理,分析和使用捕获的数据。 + +随着公司捕获并生成不断增长的数据量,这既是挑战,也是巨大的机遇。 幸运的是,这两种技术是共生的。 企业不断提出新的模型,这些模型增加了最终工作负载的计算需求。 大数据方面的新进展使这些新用例的处理变得容易并得到促进。 数据科学家发现,当前的体系结构可以处理增加的工作量。 因此,他们提出了分析此数据的新方法,并从可用的当前数据中获得了越来越深刻的见解。 + +精心设计的算法可以在大型数据集上蓬勃发展,这意味着数据越多,学习越有效。 随着大数据的出现以及计算能力的提高,机器学习继续以加速的速度发展。 随着大数据分析在机器学习领域的稳定增长,机器和设备将变得越来越智能,并继续承担以前为人类保留的任务。 + +这些公司正在创建数据管道,然后为机器学习模型提供数据,以不断改善运营。 以亚马逊为例。 像 Google 一样,Amazon 可能对其某些统计数据保持沉默。 幸运的是,可以获得一些信息。 2017 年,亚马逊向全球运送了 50 亿件物品。 每天有 1300 万件商品,或者每秒至少有 150 件商品。 如果我们要使用传统的数据技术来处理大量数据并将机器学习模型应用于该数据,那么在我们能够进行有用的推断之前,我们将收到更多的交易。 通过使用大数据技术,Amazon 可以处理这些交易并使用机器学习从其运营中获得有用的见解。 + +一个具体的例子是 Amazon 复杂的欺诈检测算法。 抓获欺诈行为的案例非常重要。 理想情况下,他们希望在发生之前将其捕获。 考虑到其交易量,许多检查需要同时进行。 有趣的是,在某些情况下,Amazon 不会尝试最小化欺诈数量,而是选择最大化客户满意度和服务可用性。 例如,当人们使用预付卡时,AWS 服务中发生的许多欺诈行为都会发生。 一种最小化欺诈的简单解决方案是禁止使用预付卡,但亚马逊仍然接受这种付款方式,取而代之的是,即使用户选择使用此付款方式,亚马逊也敦促其数据科学家提出最小化欺诈的解决方案。 + +在欺诈这个话题上,有趣的是,亚马逊在其 2019 年 re:Invent 会议上宣布了一项名为 **Fraud Detector** 的新服务。 它使用了与 Amazon 用来捕获欺诈的相同技术,并允许该服务的用户在自己的操作和交易中防止欺诈。 有关服务的更多信息,可以在这里找到: + +[https://aws.amazon.com/fraud-detector/](https://aws.amazon.com/fraud-detector/ ) + +在前面的部分中,我们了解了大数据技术本身是如何强大的。 本节的重点是要理解机器学习是大数据集群可以成功进行并以大规模并行方式处理的工作负载之一。 许多大数据堆栈(例如 Hadoop 堆栈)具有内置的机器学习组件(例如 Mahout),但是我们不限于仅使用这些组件来训练机器学习模型。 这些堆栈可以与其他同类最佳的 ML 库结合使用,例如 Scikit-Learn,Theano,Torch,Caffe 和 TensorFlow。 现在我们已经讨论了大数据如何帮助我们创建机器学习模型,让我们进一步了解一些当今最流行的大 数据工具。 + +## Apache Hadoop + +Hadoop 是 Apache Software Foundation 旗下的一种流行的开源框架,它促进了多台计算机的联网,以处理,转换和分析大型数据集。 我们将在下一节中进一步讨论。 随着名为 **Apache Spark** 的新 Hadoop 组件的出现,还有其他处理该数据的方法,但是最初,许多工作负载是通过使用 *MapReduce* 编程范例来处理的。 + +Hadoop 框架被许多财富 500 强公司使用,包括 Facebook,Netflix 和其他公司。 + +Hadoop 可以与各种其他技术解决方案和第三方软件(包括机器学习工具)集成。 + +Hadoop 无疑是构建可伸缩大数据解决方案的最佳开源工具。 它可以处理海量的分布式数据集,并且可以轻松地与其他技术和框架集成。 让我们了解 Hadoop 生态系统中的一些核心概念。 + +### MapReduce + +Hadoop 核心高度依赖 MapReduce。 MapReduce 是一种编程设计模式,用于使用并行,分布式集群计算来处理,转换和生成大型数据集。 MapReduce 模型通过协调分布式资源,并行运行多个作业以及同步节点之间的通信和数据传输来完成大规模数据集的处理。 通过跨节点复制数据,它可以很好地处理各个节点何时出现故障。 + +MapReduce 仅在多节点群集体系结构模型的上下文中才有意义。 MapReduce 背后的想法是,许多问题可以分为两个步骤。 映射和归约。 + +MapReduce 实现由一个 map 组件和一个 reduce 组件组成,reduce 组件执行映射,过滤和/或排序(例如,将不同家用物品的图像映射到队列中,每种类型的物品一个队列),reduce 组件完成摘要 操作(继续示例,计算每个队列中的图像数量,为我们提供家用物品的频率)。 + +### Apache Hive + +有许多组件组成了 Hadoop 生态系统,并且有涵盖该主题的完整书籍。 例如,我们推荐 Manish Kumar 和 Chanchal Singh 的 *Mastering Hadoop 3* 。 其中许多组件不在本书讨论范围之内,但是在结束本主题之前,我们应该介绍的 Hadoop 的一个重要组件是 Apache Hive。 + +Apache Hive 是构建在 Apache Hadoop 之上的数据仓库软件组件,支持数据查询和分析。 Apache Hive 支持类似 SQL 的界面,以获取 Hadoop 支持的各种数据库和文件系统中存储的数据。 如果没有 Hive,则需要实现复杂的 Java 代码以提供必要的 MapReduce 逻辑。 Apache Hive 提供了一种抽象,以支持对底层 Java 的类似 SQL 的查询(HiveQL),而无需实现复杂的低级 Java 代码。 + +由于大多数数据仓库应用程序都支持基于 SQL 的查询语言,因此 Hive 促进并支持将基于 SQL 的应用程序移植到 Hadoop 中。 Hive 最初是由 Facebook 开发的,但现在已集成到 Hadoop 生态系统中,并被众多财富 500 强公司使用。 + +## Apache Spark + +Apache Spark 是,是另一个流行的开源框架,隶属于 Apache Software Foundation。 对于许多用例,可以使用 Spark 代替 Hadoop 来解决相同的问题。 + +Hadoop 是最早开发的,因此,它有很多心得和实现。 但是在许多情况下,Spark 可能是更好的选择。 这样做的主要原因是 Hadoop 通过将文件读写到磁盘上的 HDFS 来处理其大部分处理,而 Spark 使用称为 RDD 或弹性分布式数据集的概念在 RAM 中处理数据。 + +### 弹性分布式数据集 + +**弹性分布式数据集**( **RDD** )是 Spark 的基础组件。 RDD 具有以下特征: + +* 不变性 +* 分散式 +* 始终驻留在内存中,而不驻留在磁盘存储中 + +RDD 中的数据集被划分为逻辑分区,这些逻辑分区冗余地存储在集群中的各个节点上。 这些对象可以是用户定义的类。 最初,只能使用 Scala 语言创建 RDD,但现在也支持 Java 和 Python。 + +更正式地说,RDD 是不可变的分区记录集。 RDD 是通过一系列确定性操作生成的。 RDD 是一组弹性的内存对象,可以并行且大规模地进行操作。 + +可以使用以下两种方法之一来创建 RDD:: + +1. 通过并行化驱动程序中的现有集合 +2. 通过引用来自外部磁盘存储的数据集,例如共享文件系统,S3,HBase,HDFS 或任何其他支持 Hadoop 兼容输入格式的外部数据源 + +Spark 利用 RDD 的概念来实现更快,更有效的 MapReduce 操作。 使用第二版 Spark,现在还支持更简单的数据结构,从而简化了数据集的处理。 这些是 DataFrames。 + +### 数据帧 + +Spark 中的新抽象是 DataFrames。 最初通过引入 Spark 2.0 作为 RDD 的替代接口来支持 DataFrames 。 这两个接口有些相似。 DataFrame 将数据组织到命名列中。 从概念上讲,它等效于关系数据库中的表或 Python 的 pandas 包或 R 中的 DataFrames。这使 DataFrames 比 RDD 易于使用。 RDD 不支持类似的列级标题引用集。 + +DataFrame 可以从多种数据源生成,包括: + +* Hive 中的结构化数据文件(例如,Parquet,ORC,JSON) +* 蜂巢表 +* 外部数据库(通过 JDBC) +* 现有的 RDD + +DataFrame API 在 Scala,Java,Python 和 R 中可用。 + +### SparkSQL + +SparkSQL 是,它可以激发出 Hive 对 Hadoop 的作用。 它允许 Spark 框架的用户查询数据框架,就像在传统的关系数据库中查询 SQL 表一样。 + +SparkSQL 添加了一个抽象级别,以允许查询存储在 RDD,DataFrame 和外部源中的数据,从而为所有这些数据源创建统一的接口。 提供这种统一的接口使开发人员可以轻松地创建复杂的 SQL 查询,以与异类的各种源(例如 HDFS 文件,S3,传统数据库等)相对。 + +更具体地说,SparkSQL 使用户能够: + +* 对导入的数据和现有的 RDD 运行 SQL 查询 +* 从 Apache Parquet 文件,ORC 文件和 Hive 表导入数据 +* 将 RDD 和 DataFrame 输出到 Hive 表或 Apache Parquet 文件 + +SparkSQL 具有列式存储,基于成本的优化器和代码生成以加快查询速度。 它可以使用 Spark 引擎支持数千个节点。 + +SparkSQL 无需修改即可运行 Hive 查询。 它重写了 Hive 前端和元存储,从而与现有 Hive 数据完全兼容。 + +## Apache Impala + +在某些方面,例如 Apache Spark,Apache Impala 是用于存储在运行的集群中的数据的开源**大规模并行处理**( **MPP** )SQL 引擎。 在 Apache Hadoop 上。 它在 Apache Hadoop 之上运行。 + +Impala 为 Hadoop 提供了可扩展的并行 SQL 数据库引擎,使开发人员可以对存储在 Apache HBase,Amazon S3 和 HDFS 中的数据创建并运行低延迟 SQL 查询,而无需在读取数据之前先进行移动或转换。 + +Impala 可以轻松与 Hadoop 集成,并支持 MapReduce,Apache Hive 和 Apache Pig 以及 Hadoop 堆栈中的其他工具所使用的相同文件和数据格式,元数据以及安全和资源管理框架。 + +分析人员和数据科学家都使用 Impala。 人们通过 SQL,BI 工具以及机器学习库,软件包和工具对 Hadoop 中存储的数据进行分析。 结果是大规模数据处理(通过 MapReduce)。 + +可以使用相同的数据和元数据在同一系统上启动查询,而不必将数据集迁移到专用系统中,也不必转换为专有文件格式即可执行简单的分析。 + +支持的功能包括: + +* HDFS 和 Apache HBase 是受支持的格式 +* 读取 Hadoop 文件格式,例如: + * 文字(CSV,JSON,XML) + * 木地板 + * 序列文件 + * 欧元 + * 挥发性有机物 + * RC 文件 +* 支持 Hadoop 安全性(例如 Kerberos 身份验证) +* 使用 Apache Sentry 的细粒度,基于角色的授权 +* 使用 Apache Hive 中的元数据,ODBC 驱动程序和 SQL 语法 + +有关 Impala 及其历史的更多信息,可以在 Impala 文档中找到: + +[https://impala.apache.org/](https://impala.apache.org/ ) + +以及这里: + +[https://zh.wikipedia.org/wiki/Apache_Impala](https://en.wikipedia.org/wiki/Apache_Impala ) + +现在,让我们分析另一个可以大大增强大型数据集处理能力的重要技术。 现在,我们将尝试了解什么是 NoSQL 数据库。 + +# NoSQL 数据库 + +在深入研究 NoSQL 数据库的特定类型之前,我们首先了解什么是 NoSQL 数据库。 这不是一个好名字,但是很难提出更好的选择。 顾名思义,NoSQL 数据库是不是 SQL 数据库的任何数据库。 它包含各种数据库技术,这些技术必须针对能够应对更大工作量和更大,更多样化数据集的产品的市场需求而构建。 + +数据是新的石油,它存在于各种各样的地方。 日志文件,音频,视频,点击流,IoT 数据和电子邮件是需要处理和分析的数据的一些示例。 传统的 SQL 数据库在使用数据之前需要结构化的架构。 此外,它们的构建并不是为了利用当今容易获得的商品存储和处理能力。 + +## NoSQL 数据库的类型 + +**文档数据库** –文档数据库用于存储半结构化数据。 他们使用与复杂的数据结构(称为文档)配对的密钥。 文档可以包含许多类型的数据结构,例如原始值(如整数,布尔值和字符串),不同的键值对或键数组对,甚至是嵌套文档。 + +**图形数据库** –图形数据库将图形结构用于具有节点,边和属性的语义查询,以表示和存储数据。 图形数据库的重点是数据中的关系。 一些示例使用图数据库的案例: + +* Facebook 中包含的信息以及网络中朋友之间的关系。 +* 交易,客户和银行帐户。 即,使用*帐户 X* 的*客户 A* 向具有*帐户 Y* 的*客户 B* 汇款。 +* 一个家庭的祖先信息。 该案例中的关系示例包括配偶,兄弟姐妹,父母,孩子,叔叔等。 + +图形数据库的示例是: + +* Neo4J +* 吉拉夫 +* 老虎图 +* 亚马逊海王星 +* 蔚蓝宇宙 + +**键值数据库** –键值数据库是 NoSQL 数据库的最简单类型。 使用*属性*名称(或*键*)及其值存储数据库中的每个单项。 键值存储的一些示例是: + +* 涟漪 +* 岩石数据库 +* 阿帕奇点燃 +* 伯克利 DB +* ArangoDB +* 雷迪斯 + +**宽列数据库** –宽列数据库已针对大型数据集的查询进行了优化,并将数据列而不是行存储在一起。 这些类型的数据库的示例是 Cassandra 和 HBase。 + +现在,让我们进一步详细介绍 NoSQL 数据库的一些最流行的实现。 + +## Apache Cassandra + +Apache Cassandra 是,一个开放源代码的分布式 NoSQL 数据库。 它可以处理大量数据。 我利用了可以使用许多商品服务器进行处理的水平架构。 它具有无单点故障的体系结构,因此可提供高可用性。 Cassandra 依赖可以跨越多个数据中心和区域的集群。 它使用异步无主复制,同时为用户提供低延迟的操作。 + +Avinash Lakshman(亚马逊 DynamoDB 的作者之一)和 Prashant Malik 最初在 Facebook 上开发了 Cassandra。 Facebook 将 Cassandra 作为开源项目发布,并在 2010 年成为 Apache 的顶级项目。 + +## MongoDB + +MongoDB 是面向文档的水平可伸缩数据库。 它使用 JSON 数据格式存储数据。 它通常用于存储网站数据,并且在内容管理和缓存应用程序中也很流行。 它支持复制和高可用性配置,以最大程度地减少数据丢失。 + +根据查询的类型,它可能是一个高性能的系统。 它是用 C ++编写的。 它完全支持索引,具有丰富的查询语言,可以进行配置以提供跨数据中心的高可用性。 + +## Redis + +Redis 是的另一个开源 NoSQL 数据库。 这是一个键值存储。 它支持键中的哈希,集,字符串,排序集和列表。 因此,Redis 也被称为数据结构服务器。 Redis 支持运行原子操作,例如增加散列中存在的值,设置交集计算,字符串附加,差和联合。 Redis 利用内存中的数据集来实现高性能。 Redis 支持大多数最受欢迎的编程语言,例如 Python,Java,Scala,C ++等。 + +## Neo4j + +Neo4j 是 Neo4j,Inc.开发的图形数据库管理系统。它最初于 2010 年 2 月发布。它在本机图形存储和处理中支持符合 ACID 的事务。 Neo4j 可能是最受欢迎的图形数据库。 + +Neo4j 具有开源版本和付费版本。 Neo4j 的开源版本可通过 GPL3 许可的开源“社区版”获得,而通过开源商业许可证,您可以拥有在线备份和高可用性扩展。 Neo 还根据开源商业条款向这些扩展提供了 Neo4j 许可。 + +Neo4j 用 Java 实现,并通过交易 HTTP 端点或二进制“螺栓”协议使用 Cypher Query Language 支持最受欢迎的语言。 + +还有许多其他流行的数据库实现。 我们几乎没有吃过那个宇宙的表面。 我们衷心希望您能为本书中介绍的概念更好地应对下一个数据科学项目。 + +# 摘要 + +在本章中,我们首先围绕大数据奠定了核心和基本概念的基础。 然后,我们了解了与大数据有关的许多不同技术。 当涉及到大数据技术 Hadoop 时,我们了解了其中的所有“老爸”。 我们还了解了当今市场上当前最流行的大数据工具,即 Spark。 + +最后,我们了解了大数据实现中常用的另一种技术,即 NoSQL 数据库。 NoSQL 数据库引擎支持财富 500 强公司中许多最大的工作量,并在当今最常见的网站中服务数百万个页面。 + +对于当今机器学习中存在的所有令人惊奇和令人兴奋的应用程序,我们坚信,我们只是在探索一切可能的事物。 我们衷心希望您能更好地掌握机器学习中涉及的概念,但更重要的是,我们希望您的好奇心得到激发,这本书激发您毕生对这种美丽事物的兴趣。 话题。 + +我们很高兴看到您使用本章和本书中所包含的知识做什么。 希望您发现这本书引人入胜,有趣并且有用。 我们希望您阅读和阅读时一样开心。 我们希望您在所有努力中继续取得成功。 \ No newline at end of file diff --git a/docs/ai-py/img/978-1-78995-575-0.png b/docs/ai-py/img/978-1-78995-575-0.png new file mode 100644 index 0000000000000000000000000000000000000000..aa04aa94639e65f5706605f5ffe17e2a3bd9c13b Binary files /dev/null and b/docs/ai-py/img/978-1-78995-575-0.png differ diff --git a/docs/ai-py/img/978-1-83864-535-9.png b/docs/ai-py/img/978-1-83864-535-9.png new file mode 100644 index 0000000000000000000000000000000000000000..f6c3f95339e8f80cdaaaef5798dc1acd5aad157d Binary files /dev/null and b/docs/ai-py/img/978-1-83864-535-9.png differ diff --git a/docs/ai-py/img/978-1-83882-341-2.png b/docs/ai-py/img/978-1-83882-341-2.png new file mode 100644 index 0000000000000000000000000000000000000000..9a04f480b7befe961f462a0520937d09e585c850 Binary files /dev/null and b/docs/ai-py/img/978-1-83882-341-2.png differ diff --git a/docs/ai-py/img/B15441_01_01.png b/docs/ai-py/img/B15441_01_01.png new file mode 100644 index 0000000000000000000000000000000000000000..9ced35d1640e09c6b1f42d2428022455cffa426c Binary files /dev/null and b/docs/ai-py/img/B15441_01_01.png differ diff --git a/docs/ai-py/img/B15441_01_02.png b/docs/ai-py/img/B15441_01_02.png new file mode 100644 index 0000000000000000000000000000000000000000..4df4e1274ee801551c9631cbf8849b21c4e1d142 Binary files /dev/null and b/docs/ai-py/img/B15441_01_02.png differ diff --git a/docs/ai-py/img/B15441_01_03.png b/docs/ai-py/img/B15441_01_03.png new file mode 100644 index 0000000000000000000000000000000000000000..6e2195a6b8e07c0ab8030bf1039a2fa19dd8fdef Binary files /dev/null and b/docs/ai-py/img/B15441_01_03.png differ diff --git a/docs/ai-py/img/B15441_01_04.png b/docs/ai-py/img/B15441_01_04.png new file mode 100644 index 0000000000000000000000000000000000000000..aeb5ff053f49ae257267e85984e0748cfe5a7dc4 Binary files /dev/null and b/docs/ai-py/img/B15441_01_04.png differ diff --git a/docs/ai-py/img/B15441_01_05.png b/docs/ai-py/img/B15441_01_05.png new file mode 100644 index 0000000000000000000000000000000000000000..4763f3829100d96b29b287554e88d44d2c8c4e3b Binary files /dev/null and b/docs/ai-py/img/B15441_01_05.png differ diff --git a/docs/ai-py/img/B15441_01_06.png b/docs/ai-py/img/B15441_01_06.png new file mode 100644 index 0000000000000000000000000000000000000000..16822ea1578d94967d81a6bfb83dbb100ea12b32 Binary files /dev/null and b/docs/ai-py/img/B15441_01_06.png differ diff --git a/docs/ai-py/img/B15441_01_07.png b/docs/ai-py/img/B15441_01_07.png new file mode 100644 index 0000000000000000000000000000000000000000..4ede073cf03fc4a8a5791b35ed0d79fc2e7eb030 Binary files /dev/null and b/docs/ai-py/img/B15441_01_07.png differ diff --git a/docs/ai-py/img/B15441_01_08.png b/docs/ai-py/img/B15441_01_08.png new file mode 100644 index 0000000000000000000000000000000000000000..3ab7c9534e9a13f6ef0e54caa8aa7a92a7490fa3 Binary files /dev/null and b/docs/ai-py/img/B15441_01_08.png differ diff --git a/docs/ai-py/img/B15441_01_09.png b/docs/ai-py/img/B15441_01_09.png new file mode 100644 index 0000000000000000000000000000000000000000..8089f6d70281d50302102a0b2ba5c532465d8c5a Binary files /dev/null and b/docs/ai-py/img/B15441_01_09.png differ diff --git a/docs/ai-py/img/B15441_01_10.png b/docs/ai-py/img/B15441_01_10.png new file mode 100644 index 0000000000000000000000000000000000000000..8cbc7cb6dbb661c538442eb0498cd8ebf162ae3e Binary files /dev/null and b/docs/ai-py/img/B15441_01_10.png differ diff --git a/docs/ai-py/img/B15441_01_11.png b/docs/ai-py/img/B15441_01_11.png new file mode 100644 index 0000000000000000000000000000000000000000..e6ebbc640a656333b732f6144025c50d797096bd Binary files /dev/null and b/docs/ai-py/img/B15441_01_11.png differ diff --git a/docs/ai-py/img/B15441_03_01.png b/docs/ai-py/img/B15441_03_01.png new file mode 100644 index 0000000000000000000000000000000000000000..0cb41bafa83cca84ceb179cc979209ef40d8659f Binary files /dev/null and b/docs/ai-py/img/B15441_03_01.png differ diff --git a/docs/ai-py/img/B15441_04_001.png b/docs/ai-py/img/B15441_04_001.png new file mode 100644 index 0000000000000000000000000000000000000000..67439aca84511821be60a4700b685f22ada3de02 Binary files /dev/null and b/docs/ai-py/img/B15441_04_001.png differ diff --git a/docs/ai-py/img/B15441_04_002.png b/docs/ai-py/img/B15441_04_002.png new file mode 100644 index 0000000000000000000000000000000000000000..d70651727c55809f6b4c7309c587db8eb0cdba36 Binary files /dev/null and b/docs/ai-py/img/B15441_04_002.png differ diff --git a/docs/ai-py/img/B15441_04_003.png b/docs/ai-py/img/B15441_04_003.png new file mode 100644 index 0000000000000000000000000000000000000000..b3850eabca38ea30c25183fc87f50b36da607e35 Binary files /dev/null and b/docs/ai-py/img/B15441_04_003.png differ diff --git a/docs/ai-py/img/B15441_04_004.png b/docs/ai-py/img/B15441_04_004.png new file mode 100644 index 0000000000000000000000000000000000000000..d4c5b389adea2b3f618405b0a907f56bcb7b60f6 Binary files /dev/null and b/docs/ai-py/img/B15441_04_004.png differ diff --git a/docs/ai-py/img/B15441_04_005.png b/docs/ai-py/img/B15441_04_005.png new file mode 100644 index 0000000000000000000000000000000000000000..0aa16b44132fec219d29bc2b879cfac009f6dfa8 Binary files /dev/null and b/docs/ai-py/img/B15441_04_005.png differ diff --git a/docs/ai-py/img/B15441_04_01.png b/docs/ai-py/img/B15441_04_01.png new file mode 100644 index 0000000000000000000000000000000000000000..ca6a39af2639a118e704d3764e569b381fb4526d Binary files /dev/null and b/docs/ai-py/img/B15441_04_01.png differ diff --git a/docs/ai-py/img/B15441_04_02.png b/docs/ai-py/img/B15441_04_02.png new file mode 100644 index 0000000000000000000000000000000000000000..bab51adc58dcf086ba7899d5240442c00e1bcad7 Binary files /dev/null and b/docs/ai-py/img/B15441_04_02.png differ diff --git a/docs/ai-py/img/B15441_04_03.png b/docs/ai-py/img/B15441_04_03.png new file mode 100644 index 0000000000000000000000000000000000000000..073598ca13d3d7a13ea4b888d5bc759465187bd6 Binary files /dev/null and b/docs/ai-py/img/B15441_04_03.png differ diff --git a/docs/ai-py/img/B15441_04_04.png b/docs/ai-py/img/B15441_04_04.png new file mode 100644 index 0000000000000000000000000000000000000000..a99f4fa1264d5e7c595ba6101bd63be377362561 Binary files /dev/null and b/docs/ai-py/img/B15441_04_04.png differ diff --git a/docs/ai-py/img/B15441_04_05.png b/docs/ai-py/img/B15441_04_05.png new file mode 100644 index 0000000000000000000000000000000000000000..4696fe2e388fb6d6a38ddec1ac8726fefe9cfd9b Binary files /dev/null and b/docs/ai-py/img/B15441_04_05.png differ diff --git a/docs/ai-py/img/B15441_04_06.png b/docs/ai-py/img/B15441_04_06.png new file mode 100644 index 0000000000000000000000000000000000000000..36834e9c2b58a54fc7da434f2995a0e3545975c1 Binary files /dev/null and b/docs/ai-py/img/B15441_04_06.png differ diff --git a/docs/ai-py/img/B15441_04_07.png b/docs/ai-py/img/B15441_04_07.png new file mode 100644 index 0000000000000000000000000000000000000000..037c7ea57e77b8158332770858519a611ea6dcc0 Binary files /dev/null and b/docs/ai-py/img/B15441_04_07.png differ diff --git a/docs/ai-py/img/B15441_04_08.png b/docs/ai-py/img/B15441_04_08.png new file mode 100644 index 0000000000000000000000000000000000000000..4cfb39a888eca5ef3590a44fd0718c7951e66273 Binary files /dev/null and b/docs/ai-py/img/B15441_04_08.png differ diff --git a/docs/ai-py/img/B15441_04_09.png b/docs/ai-py/img/B15441_04_09.png new file mode 100644 index 0000000000000000000000000000000000000000..0e993b409ffc4f31ad6dedde10a94c7051c9133e Binary files /dev/null and b/docs/ai-py/img/B15441_04_09.png differ diff --git a/docs/ai-py/img/B15441_04_10.png b/docs/ai-py/img/B15441_04_10.png new file mode 100644 index 0000000000000000000000000000000000000000..6afb0000c7e14243a0753978e6db846cb9158c3e Binary files /dev/null and b/docs/ai-py/img/B15441_04_10.png differ diff --git a/docs/ai-py/img/B15441_04_11.png b/docs/ai-py/img/B15441_04_11.png new file mode 100644 index 0000000000000000000000000000000000000000..21397ca67b7ba0129f6c05b55fa82f22e357c53c Binary files /dev/null and b/docs/ai-py/img/B15441_04_11.png differ diff --git a/docs/ai-py/img/B15441_04_12.png b/docs/ai-py/img/B15441_04_12.png new file mode 100644 index 0000000000000000000000000000000000000000..104e6b2e25806e7c63f0c89d885dd71a909accfe Binary files /dev/null and b/docs/ai-py/img/B15441_04_12.png differ diff --git a/docs/ai-py/img/B15441_04_13.png b/docs/ai-py/img/B15441_04_13.png new file mode 100644 index 0000000000000000000000000000000000000000..7b10b0677af66a75e37f244a1e98d4d5906ab28f Binary files /dev/null and b/docs/ai-py/img/B15441_04_13.png differ diff --git a/docs/ai-py/img/B15441_04_14.png b/docs/ai-py/img/B15441_04_14.png new file mode 100644 index 0000000000000000000000000000000000000000..cc296fe907e070969de263ceb7eb0ddef7e51795 Binary files /dev/null and b/docs/ai-py/img/B15441_04_14.png differ diff --git a/docs/ai-py/img/B15441_04_15.png b/docs/ai-py/img/B15441_04_15.png new file mode 100644 index 0000000000000000000000000000000000000000..c9f36b6b1fc8d9fb59eca1c01e6f3af509640bb3 Binary files /dev/null and b/docs/ai-py/img/B15441_04_15.png differ diff --git a/docs/ai-py/img/B15441_05_001.png b/docs/ai-py/img/B15441_05_001.png new file mode 100644 index 0000000000000000000000000000000000000000..7a5e982c7f6e8b096a48bfd75725f04e4daab2f6 Binary files /dev/null and b/docs/ai-py/img/B15441_05_001.png differ diff --git a/docs/ai-py/img/B15441_05_002.png b/docs/ai-py/img/B15441_05_002.png new file mode 100644 index 0000000000000000000000000000000000000000..bd338f93b8dd6b7358f7ff5d9907cd44e238c1d3 Binary files /dev/null and b/docs/ai-py/img/B15441_05_002.png differ diff --git a/docs/ai-py/img/B15441_05_01.png b/docs/ai-py/img/B15441_05_01.png new file mode 100644 index 0000000000000000000000000000000000000000..db358f105bb54937b563e21c3f3666a66475478a Binary files /dev/null and b/docs/ai-py/img/B15441_05_01.png differ diff --git a/docs/ai-py/img/B15441_05_02.png b/docs/ai-py/img/B15441_05_02.png new file mode 100644 index 0000000000000000000000000000000000000000..e0d3868b9e8234ffdc58e9530e115c1b035091ca Binary files /dev/null and b/docs/ai-py/img/B15441_05_02.png differ diff --git a/docs/ai-py/img/B15441_05_03.png b/docs/ai-py/img/B15441_05_03.png new file mode 100644 index 0000000000000000000000000000000000000000..da7d8482e1782fbf7a238adaf64942d54c97f2ab Binary files /dev/null and b/docs/ai-py/img/B15441_05_03.png differ diff --git a/docs/ai-py/img/B15441_05_04.png b/docs/ai-py/img/B15441_05_04.png new file mode 100644 index 0000000000000000000000000000000000000000..ae4bf7517f33f6034ff21b58643ac9ca4c9ecc8e Binary files /dev/null and b/docs/ai-py/img/B15441_05_04.png differ diff --git a/docs/ai-py/img/B15441_05_05.png b/docs/ai-py/img/B15441_05_05.png new file mode 100644 index 0000000000000000000000000000000000000000..0d2527c0ed8b0f437ff640a81b5e68ce27bf0b62 Binary files /dev/null and b/docs/ai-py/img/B15441_05_05.png differ diff --git a/docs/ai-py/img/B15441_05_06.png b/docs/ai-py/img/B15441_05_06.png new file mode 100644 index 0000000000000000000000000000000000000000..24ac2bc06a9c20dbf423785d01c77e2703b33c5d Binary files /dev/null and b/docs/ai-py/img/B15441_05_06.png differ diff --git a/docs/ai-py/img/B15441_05_07.png b/docs/ai-py/img/B15441_05_07.png new file mode 100644 index 0000000000000000000000000000000000000000..a5170ab546fe4ce248b7a4dd4cad820cd150d1f5 Binary files /dev/null and b/docs/ai-py/img/B15441_05_07.png differ diff --git a/docs/ai-py/img/B15441_05_08.png b/docs/ai-py/img/B15441_05_08.png new file mode 100644 index 0000000000000000000000000000000000000000..c9be34aa51c837d62eeee805b85d9835d6952ee8 Binary files /dev/null and b/docs/ai-py/img/B15441_05_08.png differ diff --git a/docs/ai-py/img/B15441_05_09.png b/docs/ai-py/img/B15441_05_09.png new file mode 100644 index 0000000000000000000000000000000000000000..4ec15e3964029cf99119f58043e996b14b65e854 Binary files /dev/null and b/docs/ai-py/img/B15441_05_09.png differ diff --git a/docs/ai-py/img/B15441_05_10.png b/docs/ai-py/img/B15441_05_10.png new file mode 100644 index 0000000000000000000000000000000000000000..eb8e56aaf5d00357ebad2bfa8c636949942c50e9 Binary files /dev/null and b/docs/ai-py/img/B15441_05_10.png differ diff --git a/docs/ai-py/img/B15441_06_01.png b/docs/ai-py/img/B15441_06_01.png new file mode 100644 index 0000000000000000000000000000000000000000..62e82122ddeab39d89886642b12dfa615db1cfdb Binary files /dev/null and b/docs/ai-py/img/B15441_06_01.png differ diff --git a/docs/ai-py/img/B15441_06_02.png b/docs/ai-py/img/B15441_06_02.png new file mode 100644 index 0000000000000000000000000000000000000000..bacabec931e1e99e1abeabff80a648b4563f6e01 Binary files /dev/null and b/docs/ai-py/img/B15441_06_02.png differ diff --git a/docs/ai-py/img/B15441_06_03.png b/docs/ai-py/img/B15441_06_03.png new file mode 100644 index 0000000000000000000000000000000000000000..3cb48ec8aa12074e4ed5a5accd7fefb30cf06fac Binary files /dev/null and b/docs/ai-py/img/B15441_06_03.png differ diff --git a/docs/ai-py/img/B15441_06_04.png b/docs/ai-py/img/B15441_06_04.png new file mode 100644 index 0000000000000000000000000000000000000000..bc7f94fa98a78a0b35ecee33dbf2abb65768598a Binary files /dev/null and b/docs/ai-py/img/B15441_06_04.png differ diff --git a/docs/ai-py/img/B15441_06_05.png b/docs/ai-py/img/B15441_06_05.png new file mode 100644 index 0000000000000000000000000000000000000000..8c537eb9d2c24c4b08ff9229456e2be82445ddb9 Binary files /dev/null and b/docs/ai-py/img/B15441_06_05.png differ diff --git a/docs/ai-py/img/B15441_06_06.png b/docs/ai-py/img/B15441_06_06.png new file mode 100644 index 0000000000000000000000000000000000000000..1ff0c2bce27632c3e66e4ddc125eee6a76d25e19 Binary files /dev/null and b/docs/ai-py/img/B15441_06_06.png differ diff --git a/docs/ai-py/img/B15441_06_07.png b/docs/ai-py/img/B15441_06_07.png new file mode 100644 index 0000000000000000000000000000000000000000..fb2a6b416fb3eb9fa6bd68378be8a00952b99491 Binary files /dev/null and b/docs/ai-py/img/B15441_06_07.png differ diff --git a/docs/ai-py/img/B15441_06_08.png b/docs/ai-py/img/B15441_06_08.png new file mode 100644 index 0000000000000000000000000000000000000000..757a91f4d303c5fc5dc98e6d2138b4c6a063d929 Binary files /dev/null and b/docs/ai-py/img/B15441_06_08.png differ diff --git a/docs/ai-py/img/B15441_06_09.png b/docs/ai-py/img/B15441_06_09.png new file mode 100644 index 0000000000000000000000000000000000000000..fa648ca7b41940a2038f74e2926a7fa7e01b791b Binary files /dev/null and b/docs/ai-py/img/B15441_06_09.png differ diff --git a/docs/ai-py/img/B15441_06_10.png b/docs/ai-py/img/B15441_06_10.png new file mode 100644 index 0000000000000000000000000000000000000000..67a6b23de886e1fc9620e0c4290daaa167cf6344 Binary files /dev/null and b/docs/ai-py/img/B15441_06_10.png differ diff --git a/docs/ai-py/img/B15441_06_11.png b/docs/ai-py/img/B15441_06_11.png new file mode 100644 index 0000000000000000000000000000000000000000..1ad3d705a87eff6ddbf377119d407d798622eaa7 Binary files /dev/null and b/docs/ai-py/img/B15441_06_11.png differ diff --git a/docs/ai-py/img/B15441_06_12.png b/docs/ai-py/img/B15441_06_12.png new file mode 100644 index 0000000000000000000000000000000000000000..1173210ae56100f1246cc66d6aac6611d6631ae1 Binary files /dev/null and b/docs/ai-py/img/B15441_06_12.png differ diff --git a/docs/ai-py/img/B15441_06_13.png b/docs/ai-py/img/B15441_06_13.png new file mode 100644 index 0000000000000000000000000000000000000000..b69b028711ce5e3ce14722ebbb1d54aed13d9603 Binary files /dev/null and b/docs/ai-py/img/B15441_06_13.png differ diff --git a/docs/ai-py/img/B15441_06_14.png b/docs/ai-py/img/B15441_06_14.png new file mode 100644 index 0000000000000000000000000000000000000000..45438ed880699f0d92a7cfc9d113bbdd4e208d5d Binary files /dev/null and b/docs/ai-py/img/B15441_06_14.png differ diff --git a/docs/ai-py/img/B15441_06_15.png b/docs/ai-py/img/B15441_06_15.png new file mode 100644 index 0000000000000000000000000000000000000000..9e28d1bbefe4ec79472fb484a13d0ff104c9c267 Binary files /dev/null and b/docs/ai-py/img/B15441_06_15.png differ diff --git a/docs/ai-py/img/B15441_06_16.png b/docs/ai-py/img/B15441_06_16.png new file mode 100644 index 0000000000000000000000000000000000000000..acbcb75074637e71d649dcb3b878c879f1c791a2 Binary files /dev/null and b/docs/ai-py/img/B15441_06_16.png differ diff --git a/docs/ai-py/img/B15441_06_17.png b/docs/ai-py/img/B15441_06_17.png new file mode 100644 index 0000000000000000000000000000000000000000..95c5d38f49d94efefc0203dfd5b0e7aadc6fe140 Binary files /dev/null and b/docs/ai-py/img/B15441_06_17.png differ diff --git a/docs/ai-py/img/B15441_06_18.png b/docs/ai-py/img/B15441_06_18.png new file mode 100644 index 0000000000000000000000000000000000000000..9f3358a2d8ce51e0c6d62200c62a20e0db829285 Binary files /dev/null and b/docs/ai-py/img/B15441_06_18.png differ diff --git a/docs/ai-py/img/B15441_07_01.png b/docs/ai-py/img/B15441_07_01.png new file mode 100644 index 0000000000000000000000000000000000000000..81fd63f1266258e035d38be67a89ca90652e63d2 Binary files /dev/null and b/docs/ai-py/img/B15441_07_01.png differ diff --git a/docs/ai-py/img/B15441_07_02.png b/docs/ai-py/img/B15441_07_02.png new file mode 100644 index 0000000000000000000000000000000000000000..b54c29f1ea208e9733558a69de44f826bd751c61 Binary files /dev/null and b/docs/ai-py/img/B15441_07_02.png differ diff --git a/docs/ai-py/img/B15441_07_03.png b/docs/ai-py/img/B15441_07_03.png new file mode 100644 index 0000000000000000000000000000000000000000..0ee2a13a52c08fc6f65f7beacf496682ce3e25e2 Binary files /dev/null and b/docs/ai-py/img/B15441_07_03.png differ diff --git a/docs/ai-py/img/B15441_07_04.png b/docs/ai-py/img/B15441_07_04.png new file mode 100644 index 0000000000000000000000000000000000000000..a10914c9794a11f81bcd384e8ffe8d5cc5f5e429 Binary files /dev/null and b/docs/ai-py/img/B15441_07_04.png differ diff --git a/docs/ai-py/img/B15441_07_05.png b/docs/ai-py/img/B15441_07_05.png new file mode 100644 index 0000000000000000000000000000000000000000..8a4d61fed29e3f43c96e6cb106158f42f17ff05e Binary files /dev/null and b/docs/ai-py/img/B15441_07_05.png differ diff --git a/docs/ai-py/img/B15441_07_06.png b/docs/ai-py/img/B15441_07_06.png new file mode 100644 index 0000000000000000000000000000000000000000..d258c447fb38c36cced7552265e292a692d833a0 Binary files /dev/null and b/docs/ai-py/img/B15441_07_06.png differ diff --git a/docs/ai-py/img/B15441_07_07.png b/docs/ai-py/img/B15441_07_07.png new file mode 100644 index 0000000000000000000000000000000000000000..136d56434f5149d822fe73a2866d5d95b82a321a Binary files /dev/null and b/docs/ai-py/img/B15441_07_07.png differ diff --git a/docs/ai-py/img/B15441_07_08.png b/docs/ai-py/img/B15441_07_08.png new file mode 100644 index 0000000000000000000000000000000000000000..e2f08e6fd1d6021045e4ad16f1928ed8a98f2e74 Binary files /dev/null and b/docs/ai-py/img/B15441_07_08.png differ diff --git a/docs/ai-py/img/B15441_07_09.png b/docs/ai-py/img/B15441_07_09.png new file mode 100644 index 0000000000000000000000000000000000000000..30a91f0b1cd4b9d48b3ed67ed944fd7a7ce14ab7 Binary files /dev/null and b/docs/ai-py/img/B15441_07_09.png differ diff --git a/docs/ai-py/img/B15441_07_10.png b/docs/ai-py/img/B15441_07_10.png new file mode 100644 index 0000000000000000000000000000000000000000..39d7c7b72eaade6e51a9a149632fe4a83fca9231 Binary files /dev/null and b/docs/ai-py/img/B15441_07_10.png differ diff --git a/docs/ai-py/img/B15441_07_11.png b/docs/ai-py/img/B15441_07_11.png new file mode 100644 index 0000000000000000000000000000000000000000..157e9595ca6284c66d2cc1f6d9a6f5c31ef6d9f3 Binary files /dev/null and b/docs/ai-py/img/B15441_07_11.png differ diff --git a/docs/ai-py/img/B15441_08_01.png b/docs/ai-py/img/B15441_08_01.png new file mode 100644 index 0000000000000000000000000000000000000000..a1e61312c7de4431f4311342583273946ff6fc23 Binary files /dev/null and b/docs/ai-py/img/B15441_08_01.png differ diff --git a/docs/ai-py/img/B15441_08_02.png b/docs/ai-py/img/B15441_08_02.png new file mode 100644 index 0000000000000000000000000000000000000000..46373f1d68b2fa02ba9d04bf809fb36711d9ff0f Binary files /dev/null and b/docs/ai-py/img/B15441_08_02.png differ diff --git a/docs/ai-py/img/B15441_08_03.png b/docs/ai-py/img/B15441_08_03.png new file mode 100644 index 0000000000000000000000000000000000000000..b1f0a8b3c4406df286a9f01681a1fb2cde16fd05 Binary files /dev/null and b/docs/ai-py/img/B15441_08_03.png differ diff --git a/docs/ai-py/img/B15441_08_04.png b/docs/ai-py/img/B15441_08_04.png new file mode 100644 index 0000000000000000000000000000000000000000..2b36a392055dc0587c83f43c2468836e8715ef72 Binary files /dev/null and b/docs/ai-py/img/B15441_08_04.png differ diff --git a/docs/ai-py/img/B15441_08_05.png b/docs/ai-py/img/B15441_08_05.png new file mode 100644 index 0000000000000000000000000000000000000000..1a2f29ec1c96580658a68ec37073626f8e6c0291 Binary files /dev/null and b/docs/ai-py/img/B15441_08_05.png differ diff --git a/docs/ai-py/img/B15441_08_06.png b/docs/ai-py/img/B15441_08_06.png new file mode 100644 index 0000000000000000000000000000000000000000..ecc69a488a6807185341dbdf33c6621c80512793 Binary files /dev/null and b/docs/ai-py/img/B15441_08_06.png differ diff --git a/docs/ai-py/img/B15441_08_07.png b/docs/ai-py/img/B15441_08_07.png new file mode 100644 index 0000000000000000000000000000000000000000..3aeb2f1eb18af888fddb5ee99ad88ef03ff1f3b7 Binary files /dev/null and b/docs/ai-py/img/B15441_08_07.png differ diff --git a/docs/ai-py/img/B15441_08_08.png b/docs/ai-py/img/B15441_08_08.png new file mode 100644 index 0000000000000000000000000000000000000000..b6b17fba4b405fbcd5ac326591840983cfca640e Binary files /dev/null and b/docs/ai-py/img/B15441_08_08.png differ diff --git a/docs/ai-py/img/B15441_08_09.png b/docs/ai-py/img/B15441_08_09.png new file mode 100644 index 0000000000000000000000000000000000000000..838765fd62eaaaddfc64bdd4af96b65666bf5441 Binary files /dev/null and b/docs/ai-py/img/B15441_08_09.png differ diff --git a/docs/ai-py/img/B15441_08_10.png b/docs/ai-py/img/B15441_08_10.png new file mode 100644 index 0000000000000000000000000000000000000000..f7fd97945e3ce1e19ec0d008e8fad98e68f0947c Binary files /dev/null and b/docs/ai-py/img/B15441_08_10.png differ diff --git a/docs/ai-py/img/B15441_08_11.png b/docs/ai-py/img/B15441_08_11.png new file mode 100644 index 0000000000000000000000000000000000000000..7527b111f9b12bfe3ab01b69da8d7a85ef30e034 Binary files /dev/null and b/docs/ai-py/img/B15441_08_11.png differ diff --git a/docs/ai-py/img/B15441_09_01.png b/docs/ai-py/img/B15441_09_01.png new file mode 100644 index 0000000000000000000000000000000000000000..95b2069c8f3ca3bda65f6d6238358f7cc7bdf749 Binary files /dev/null and b/docs/ai-py/img/B15441_09_01.png differ diff --git a/docs/ai-py/img/B15441_09_02.png b/docs/ai-py/img/B15441_09_02.png new file mode 100644 index 0000000000000000000000000000000000000000..342d53bdf1b8accd1dabb1de4b3d98dd3e86c59e Binary files /dev/null and b/docs/ai-py/img/B15441_09_02.png differ diff --git a/docs/ai-py/img/B15441_09_03.png b/docs/ai-py/img/B15441_09_03.png new file mode 100644 index 0000000000000000000000000000000000000000..96f1be347b61ea3a3ede3659b5c0fbff52400915 Binary files /dev/null and b/docs/ai-py/img/B15441_09_03.png differ diff --git a/docs/ai-py/img/B15441_09_04.png b/docs/ai-py/img/B15441_09_04.png new file mode 100644 index 0000000000000000000000000000000000000000..846d6bd7bbbfd7453373c8c7b203bf613e0ece54 Binary files /dev/null and b/docs/ai-py/img/B15441_09_04.png differ diff --git a/docs/ai-py/img/B15441_09_05.png b/docs/ai-py/img/B15441_09_05.png new file mode 100644 index 0000000000000000000000000000000000000000..4e90fce3857aa90607f434fb864271aa5a4c93ec Binary files /dev/null and b/docs/ai-py/img/B15441_09_05.png differ diff --git a/docs/ai-py/img/B15441_09_06.png b/docs/ai-py/img/B15441_09_06.png new file mode 100644 index 0000000000000000000000000000000000000000..69b14fca669ba0fae34fab32414e8ef844358f86 Binary files /dev/null and b/docs/ai-py/img/B15441_09_06.png differ diff --git a/docs/ai-py/img/B15441_09_07.png b/docs/ai-py/img/B15441_09_07.png new file mode 100644 index 0000000000000000000000000000000000000000..2ef075fc67788cdb3ff5d568006a2e69de4f336f Binary files /dev/null and b/docs/ai-py/img/B15441_09_07.png differ diff --git a/docs/ai-py/img/B15441_09_08.png b/docs/ai-py/img/B15441_09_08.png new file mode 100644 index 0000000000000000000000000000000000000000..8ab8f04b1fc7e8354f354214138d823807b91895 Binary files /dev/null and b/docs/ai-py/img/B15441_09_08.png differ diff --git a/docs/ai-py/img/B15441_10_01.png b/docs/ai-py/img/B15441_10_01.png new file mode 100644 index 0000000000000000000000000000000000000000..9070b61b2d61e1b7deec037907ed238d9edcaca1 Binary files /dev/null and b/docs/ai-py/img/B15441_10_01.png differ diff --git a/docs/ai-py/img/B15441_10_02.png b/docs/ai-py/img/B15441_10_02.png new file mode 100644 index 0000000000000000000000000000000000000000..761cdddba827a6da7ac9226c5b99b9277f9b0aab Binary files /dev/null and b/docs/ai-py/img/B15441_10_02.png differ diff --git a/docs/ai-py/img/B15441_10_03.png b/docs/ai-py/img/B15441_10_03.png new file mode 100644 index 0000000000000000000000000000000000000000..c819d5ae0dfbee125915768e1636f241acb8cd33 Binary files /dev/null and b/docs/ai-py/img/B15441_10_03.png differ diff --git a/docs/ai-py/img/B15441_10_04.png b/docs/ai-py/img/B15441_10_04.png new file mode 100644 index 0000000000000000000000000000000000000000..adf6cecc2ed6bc258d48739b660da3905529c1e4 Binary files /dev/null and b/docs/ai-py/img/B15441_10_04.png differ diff --git a/docs/ai-py/img/B15441_10_05.png b/docs/ai-py/img/B15441_10_05.png new file mode 100644 index 0000000000000000000000000000000000000000..e3adf541d369b19d8a94ad4eadc71ba253f1a523 Binary files /dev/null and b/docs/ai-py/img/B15441_10_05.png differ diff --git a/docs/ai-py/img/B15441_10_06.png b/docs/ai-py/img/B15441_10_06.png new file mode 100644 index 0000000000000000000000000000000000000000..da4617d21a641994aeee053c36fa1eade6bfd7e5 Binary files /dev/null and b/docs/ai-py/img/B15441_10_06.png differ diff --git a/docs/ai-py/img/B15441_10_07.png b/docs/ai-py/img/B15441_10_07.png new file mode 100644 index 0000000000000000000000000000000000000000..ae30294d55e0e471bf16f398921b05d8482046b7 Binary files /dev/null and b/docs/ai-py/img/B15441_10_07.png differ diff --git a/docs/ai-py/img/B15441_10_08.png b/docs/ai-py/img/B15441_10_08.png new file mode 100644 index 0000000000000000000000000000000000000000..e6ebe929d562761e77979487792853fe5ee86541 Binary files /dev/null and b/docs/ai-py/img/B15441_10_08.png differ diff --git a/docs/ai-py/img/B15441_10_09.png b/docs/ai-py/img/B15441_10_09.png new file mode 100644 index 0000000000000000000000000000000000000000..d324ee0dca14d39a7e1a6b4d85946913f3ae1ef9 Binary files /dev/null and b/docs/ai-py/img/B15441_10_09.png differ diff --git a/docs/ai-py/img/B15441_10_10.png b/docs/ai-py/img/B15441_10_10.png new file mode 100644 index 0000000000000000000000000000000000000000..b9f7af0ffe67680dbc1a2321f49ce518125e4bb6 Binary files /dev/null and b/docs/ai-py/img/B15441_10_10.png differ diff --git a/docs/ai-py/img/B15441_10_11.png b/docs/ai-py/img/B15441_10_11.png new file mode 100644 index 0000000000000000000000000000000000000000..c6717cb09f588e14fe04caec8535811339293e39 Binary files /dev/null and b/docs/ai-py/img/B15441_10_11.png differ diff --git a/docs/ai-py/img/B15441_10_12.png b/docs/ai-py/img/B15441_10_12.png new file mode 100644 index 0000000000000000000000000000000000000000..f70984a586a362efb72cd2183f3e92827fb2b660 Binary files /dev/null and b/docs/ai-py/img/B15441_10_12.png differ diff --git a/docs/ai-py/img/B15441_10_13.png b/docs/ai-py/img/B15441_10_13.png new file mode 100644 index 0000000000000000000000000000000000000000..98ce01bcfe0bf52136347ba03eff194c1da24cb8 Binary files /dev/null and b/docs/ai-py/img/B15441_10_13.png differ diff --git a/docs/ai-py/img/B15441_11_01.png b/docs/ai-py/img/B15441_11_01.png new file mode 100644 index 0000000000000000000000000000000000000000..d84f3942f4bd7e20bbb62df9bb62abb3dfe548a0 Binary files /dev/null and b/docs/ai-py/img/B15441_11_01.png differ diff --git a/docs/ai-py/img/B15441_11_02.png b/docs/ai-py/img/B15441_11_02.png new file mode 100644 index 0000000000000000000000000000000000000000..d5991683b247ba5a1c14aca869a4af12e24bb69e Binary files /dev/null and b/docs/ai-py/img/B15441_11_02.png differ diff --git a/docs/ai-py/img/B15441_11_03.png b/docs/ai-py/img/B15441_11_03.png new file mode 100644 index 0000000000000000000000000000000000000000..d343ae15f6e35229b2773361716f139a22832ade Binary files /dev/null and b/docs/ai-py/img/B15441_11_03.png differ diff --git a/docs/ai-py/img/B15441_11_04.png b/docs/ai-py/img/B15441_11_04.png new file mode 100644 index 0000000000000000000000000000000000000000..5fd82830ddb2d37c5355c0cc9beddeaaf7847988 Binary files /dev/null and b/docs/ai-py/img/B15441_11_04.png differ diff --git a/docs/ai-py/img/B15441_11_05.png b/docs/ai-py/img/B15441_11_05.png new file mode 100644 index 0000000000000000000000000000000000000000..a31d06a14ab3850e9b4509aefda938f62710307a Binary files /dev/null and b/docs/ai-py/img/B15441_11_05.png differ diff --git a/docs/ai-py/img/B15441_11_06.png b/docs/ai-py/img/B15441_11_06.png new file mode 100644 index 0000000000000000000000000000000000000000..1c9ed95e1190d10f9429f78fd0d416e376b7de8b Binary files /dev/null and b/docs/ai-py/img/B15441_11_06.png differ diff --git a/docs/ai-py/img/B15441_11_07.png b/docs/ai-py/img/B15441_11_07.png new file mode 100644 index 0000000000000000000000000000000000000000..94eb4b2ffe5c424b1b32bb2c89fe93155bd4b3ef Binary files /dev/null and b/docs/ai-py/img/B15441_11_07.png differ diff --git a/docs/ai-py/img/B15441_11_08.png b/docs/ai-py/img/B15441_11_08.png new file mode 100644 index 0000000000000000000000000000000000000000..d02afcde2c773bc856311c8702d739749a913579 Binary files /dev/null and b/docs/ai-py/img/B15441_11_08.png differ diff --git a/docs/ai-py/img/B15441_11_09.png b/docs/ai-py/img/B15441_11_09.png new file mode 100644 index 0000000000000000000000000000000000000000..89c4f634d94c65ac62e3fefb05641bc1f0bb0e91 Binary files /dev/null and b/docs/ai-py/img/B15441_11_09.png differ diff --git a/docs/ai-py/img/B15441_11_10.png b/docs/ai-py/img/B15441_11_10.png new file mode 100644 index 0000000000000000000000000000000000000000..ad8621d4e06b38c2c86df623dd1361675452105e Binary files /dev/null and b/docs/ai-py/img/B15441_11_10.png differ diff --git a/docs/ai-py/img/B15441_11_11.png b/docs/ai-py/img/B15441_11_11.png new file mode 100644 index 0000000000000000000000000000000000000000..eeff2b66a09ff30a72c834e7e8522335068bf899 Binary files /dev/null and b/docs/ai-py/img/B15441_11_11.png differ diff --git a/docs/ai-py/img/B15441_11_12.png b/docs/ai-py/img/B15441_11_12.png new file mode 100644 index 0000000000000000000000000000000000000000..d61a24dff7277da352547fbc3e54d740e470f277 Binary files /dev/null and b/docs/ai-py/img/B15441_11_12.png differ diff --git a/docs/ai-py/img/B15441_11_13.png b/docs/ai-py/img/B15441_11_13.png new file mode 100644 index 0000000000000000000000000000000000000000..17a9cda63623ef4265b1f8f1dac5241fee80dfe4 Binary files /dev/null and b/docs/ai-py/img/B15441_11_13.png differ diff --git a/docs/ai-py/img/B15441_11_14.png b/docs/ai-py/img/B15441_11_14.png new file mode 100644 index 0000000000000000000000000000000000000000..f81e3b4692d2a784436c32652f07dcbff512f13f Binary files /dev/null and b/docs/ai-py/img/B15441_11_14.png differ diff --git a/docs/ai-py/img/B15441_13_01.png b/docs/ai-py/img/B15441_13_01.png new file mode 100644 index 0000000000000000000000000000000000000000..732ace33ce4ea6381ca8940aa34a16ecdc4b447b Binary files /dev/null and b/docs/ai-py/img/B15441_13_01.png differ diff --git a/docs/ai-py/img/B15441_13_02.png b/docs/ai-py/img/B15441_13_02.png new file mode 100644 index 0000000000000000000000000000000000000000..97e86db2a51880ed33c1cfea707d949238484e96 Binary files /dev/null and b/docs/ai-py/img/B15441_13_02.png differ diff --git a/docs/ai-py/img/B15441_13_03.png b/docs/ai-py/img/B15441_13_03.png new file mode 100644 index 0000000000000000000000000000000000000000..62e04f14f21fd3043e7e65811609f41a325cc4f1 Binary files /dev/null and b/docs/ai-py/img/B15441_13_03.png differ diff --git a/docs/ai-py/img/B15441_13_04.png b/docs/ai-py/img/B15441_13_04.png new file mode 100644 index 0000000000000000000000000000000000000000..d1b26f0f099a0decb81e78f5ca453330bd0c1ecc Binary files /dev/null and b/docs/ai-py/img/B15441_13_04.png differ diff --git a/docs/ai-py/img/B15441_13_05.png b/docs/ai-py/img/B15441_13_05.png new file mode 100644 index 0000000000000000000000000000000000000000..9181fa305508a230211a16b4359642378d1b98f3 Binary files /dev/null and b/docs/ai-py/img/B15441_13_05.png differ diff --git a/docs/ai-py/img/B15441_13_06.png b/docs/ai-py/img/B15441_13_06.png new file mode 100644 index 0000000000000000000000000000000000000000..5bc909dd040548515a3fb44db9b2c2e98c847cef Binary files /dev/null and b/docs/ai-py/img/B15441_13_06.png differ diff --git a/docs/ai-py/img/B15441_13_07.png b/docs/ai-py/img/B15441_13_07.png new file mode 100644 index 0000000000000000000000000000000000000000..c7b867a705250bcbd4079dc0c36814313467a44b Binary files /dev/null and b/docs/ai-py/img/B15441_13_07.png differ diff --git a/docs/ai-py/img/B15441_13_08.png b/docs/ai-py/img/B15441_13_08.png new file mode 100644 index 0000000000000000000000000000000000000000..c908129708a9267d86967123ac523f3d66062357 Binary files /dev/null and b/docs/ai-py/img/B15441_13_08.png differ diff --git a/docs/ai-py/img/B15441_14_01.png b/docs/ai-py/img/B15441_14_01.png new file mode 100644 index 0000000000000000000000000000000000000000..0aa6cd7a9f1b2cd00140f2f6a8ac420c5fd43616 Binary files /dev/null and b/docs/ai-py/img/B15441_14_01.png differ diff --git a/docs/ai-py/img/B15441_14_02.png b/docs/ai-py/img/B15441_14_02.png new file mode 100644 index 0000000000000000000000000000000000000000..41f4d1fbed1197d742811f9646d88acf5a7c07b5 Binary files /dev/null and b/docs/ai-py/img/B15441_14_02.png differ diff --git a/docs/ai-py/img/B15441_14_03.png b/docs/ai-py/img/B15441_14_03.png new file mode 100644 index 0000000000000000000000000000000000000000..a946d03c3bf1c0c994760c1b3b2fc9fcc81a6038 Binary files /dev/null and b/docs/ai-py/img/B15441_14_03.png differ diff --git a/docs/ai-py/img/B15441_14_04.png b/docs/ai-py/img/B15441_14_04.png new file mode 100644 index 0000000000000000000000000000000000000000..cb0b79e91f1551139e7f4a24aefd7c81ac00b73c Binary files /dev/null and b/docs/ai-py/img/B15441_14_04.png differ diff --git a/docs/ai-py/img/B15441_14_05.png b/docs/ai-py/img/B15441_14_05.png new file mode 100644 index 0000000000000000000000000000000000000000..c9122a774b18b48e35d9d9507c194edc21f7e42f Binary files /dev/null and b/docs/ai-py/img/B15441_14_05.png differ diff --git a/docs/ai-py/img/B15441_14_06.png b/docs/ai-py/img/B15441_14_06.png new file mode 100644 index 0000000000000000000000000000000000000000..7a95c1e040b45399b3949933d44fca5a7e0a8c6d Binary files /dev/null and b/docs/ai-py/img/B15441_14_06.png differ diff --git a/docs/ai-py/img/B15441_14_07.png b/docs/ai-py/img/B15441_14_07.png new file mode 100644 index 0000000000000000000000000000000000000000..c7d1f23acf8af4e28f28da92645daa93a897ffba Binary files /dev/null and b/docs/ai-py/img/B15441_14_07.png differ diff --git a/docs/ai-py/img/B15441_14_08.png b/docs/ai-py/img/B15441_14_08.png new file mode 100644 index 0000000000000000000000000000000000000000..06fe7e655f1151da3dd72f76725330ff166ed38f Binary files /dev/null and b/docs/ai-py/img/B15441_14_08.png differ diff --git a/docs/ai-py/img/B15441_15_01.png b/docs/ai-py/img/B15441_15_01.png new file mode 100644 index 0000000000000000000000000000000000000000..67af000a2d6309c8034783bc3c8a55b20562f141 Binary files /dev/null and b/docs/ai-py/img/B15441_15_01.png differ diff --git a/docs/ai-py/img/B15441_15_02.png b/docs/ai-py/img/B15441_15_02.png new file mode 100644 index 0000000000000000000000000000000000000000..1a3588591fd96fcd949d9af9eae2833715b7d8a4 Binary files /dev/null and b/docs/ai-py/img/B15441_15_02.png differ diff --git a/docs/ai-py/img/B15441_15_03.png b/docs/ai-py/img/B15441_15_03.png new file mode 100644 index 0000000000000000000000000000000000000000..7e0ebbe44c1cccc83767d3350b7e335936b762f8 Binary files /dev/null and b/docs/ai-py/img/B15441_15_03.png differ diff --git a/docs/ai-py/img/B15441_15_04.png b/docs/ai-py/img/B15441_15_04.png new file mode 100644 index 0000000000000000000000000000000000000000..b598d025864ad0d33c2535667e7a30e046a79b63 Binary files /dev/null and b/docs/ai-py/img/B15441_15_04.png differ diff --git a/docs/ai-py/img/B15441_15_05.png b/docs/ai-py/img/B15441_15_05.png new file mode 100644 index 0000000000000000000000000000000000000000..96160d6b3e6169432b04ff714432afd99fa560c5 Binary files /dev/null and b/docs/ai-py/img/B15441_15_05.png differ diff --git a/docs/ai-py/img/B15441_15_06.png b/docs/ai-py/img/B15441_15_06.png new file mode 100644 index 0000000000000000000000000000000000000000..ab372b25e34ab58bffe72587d2a1c63c41122458 Binary files /dev/null and b/docs/ai-py/img/B15441_15_06.png differ diff --git a/docs/ai-py/img/B15441_15_07.png b/docs/ai-py/img/B15441_15_07.png new file mode 100644 index 0000000000000000000000000000000000000000..9c81bb0e81b3cb677f7a5288b777b560f8b287d6 Binary files /dev/null and b/docs/ai-py/img/B15441_15_07.png differ diff --git a/docs/ai-py/img/B15441_15_08.png b/docs/ai-py/img/B15441_15_08.png new file mode 100644 index 0000000000000000000000000000000000000000..69d82e77e3a99ce53d137a60cc75de7f9f5f2075 Binary files /dev/null and b/docs/ai-py/img/B15441_15_08.png differ diff --git a/docs/ai-py/img/B15441_15_09.png b/docs/ai-py/img/B15441_15_09.png new file mode 100644 index 0000000000000000000000000000000000000000..dfb01cb7940b03d3adbc9a3f481952fefb0a4fa5 Binary files /dev/null and b/docs/ai-py/img/B15441_15_09.png differ diff --git a/docs/ai-py/img/B15441_15_10.png b/docs/ai-py/img/B15441_15_10.png new file mode 100644 index 0000000000000000000000000000000000000000..f9074f86186c6a26400ed69c3ecfd611985295e5 Binary files /dev/null and b/docs/ai-py/img/B15441_15_10.png differ diff --git a/docs/ai-py/img/B15441_15_11.png b/docs/ai-py/img/B15441_15_11.png new file mode 100644 index 0000000000000000000000000000000000000000..9bb71cf8d652a50e3d2b850518e19bde39fca4ac Binary files /dev/null and b/docs/ai-py/img/B15441_15_11.png differ diff --git a/docs/ai-py/img/B15441_16_01.png b/docs/ai-py/img/B15441_16_01.png new file mode 100644 index 0000000000000000000000000000000000000000..91dcb795f8b40e942b9ed79011931413eb2386fa Binary files /dev/null and b/docs/ai-py/img/B15441_16_01.png differ diff --git a/docs/ai-py/img/B15441_16_02.png b/docs/ai-py/img/B15441_16_02.png new file mode 100644 index 0000000000000000000000000000000000000000..0b3fddd6833ae248994e2f5642f85413347ab5aa Binary files /dev/null and b/docs/ai-py/img/B15441_16_02.png differ diff --git a/docs/ai-py/img/B15441_16_03.png b/docs/ai-py/img/B15441_16_03.png new file mode 100644 index 0000000000000000000000000000000000000000..667808243c0d7353e320ecbbb820f1a806badefb Binary files /dev/null and b/docs/ai-py/img/B15441_16_03.png differ diff --git a/docs/ai-py/img/B15441_16_04.png b/docs/ai-py/img/B15441_16_04.png new file mode 100644 index 0000000000000000000000000000000000000000..23c7b159788417afb988193b8c3f9aa06c2cb78e Binary files /dev/null and b/docs/ai-py/img/B15441_16_04.png differ diff --git a/docs/ai-py/img/B15441_16_05.png b/docs/ai-py/img/B15441_16_05.png new file mode 100644 index 0000000000000000000000000000000000000000..779742baca8dd783446f3f20c6940d1d174b3191 Binary files /dev/null and b/docs/ai-py/img/B15441_16_05.png differ diff --git a/docs/ai-py/img/B15441_16_06.png b/docs/ai-py/img/B15441_16_06.png new file mode 100644 index 0000000000000000000000000000000000000000..ac221f4337e5c403e0c61e61570e552243caf976 Binary files /dev/null and b/docs/ai-py/img/B15441_16_06.png differ diff --git a/docs/ai-py/img/B15441_16_07.png b/docs/ai-py/img/B15441_16_07.png new file mode 100644 index 0000000000000000000000000000000000000000..06c9130a25c6ba4f121d988b46d1b1a20a2c3211 Binary files /dev/null and b/docs/ai-py/img/B15441_16_07.png differ diff --git a/docs/ai-py/img/B15441_16_08.png b/docs/ai-py/img/B15441_16_08.png new file mode 100644 index 0000000000000000000000000000000000000000..62e4c0e8fb43a1138b654e71e9ae3d1bb75b3133 Binary files /dev/null and b/docs/ai-py/img/B15441_16_08.png differ diff --git a/docs/ai-py/img/B15441_16_09.png b/docs/ai-py/img/B15441_16_09.png new file mode 100644 index 0000000000000000000000000000000000000000..cbbd7cb9459e5a5521220cec5557becb1f304288 Binary files /dev/null and b/docs/ai-py/img/B15441_16_09.png differ diff --git a/docs/ai-py/img/B15441_16_10.png b/docs/ai-py/img/B15441_16_10.png new file mode 100644 index 0000000000000000000000000000000000000000..e2d908aaa0575b7c492b0db7fa5fea8a171203a2 Binary files /dev/null and b/docs/ai-py/img/B15441_16_10.png differ diff --git a/docs/ai-py/img/B15441_16_11.png b/docs/ai-py/img/B15441_16_11.png new file mode 100644 index 0000000000000000000000000000000000000000..535f556dc3650ae6fcd5844f68f78cdf4bc52bbf Binary files /dev/null and b/docs/ai-py/img/B15441_16_11.png differ diff --git a/docs/ai-py/img/B15441_17_01.png b/docs/ai-py/img/B15441_17_01.png new file mode 100644 index 0000000000000000000000000000000000000000..7291cbcfb6402bb7277a475fe2a931a6bd4a0261 Binary files /dev/null and b/docs/ai-py/img/B15441_17_01.png differ diff --git a/docs/ai-py/img/B15441_17_02.png b/docs/ai-py/img/B15441_17_02.png new file mode 100644 index 0000000000000000000000000000000000000000..5aa6e8a62b7992443257a6bc11021c4c6c9d8e28 Binary files /dev/null and b/docs/ai-py/img/B15441_17_02.png differ diff --git a/docs/ai-py/img/B15441_17_03.png b/docs/ai-py/img/B15441_17_03.png new file mode 100644 index 0000000000000000000000000000000000000000..80f6e0a79e1dd42dabf772f9e3033ac32a173a4e Binary files /dev/null and b/docs/ai-py/img/B15441_17_03.png differ diff --git a/docs/ai-py/img/B15441_17_04.png b/docs/ai-py/img/B15441_17_04.png new file mode 100644 index 0000000000000000000000000000000000000000..9ea2cf22b4163c793d7fa3e335a2c3f0cddec9b8 Binary files /dev/null and b/docs/ai-py/img/B15441_17_04.png differ diff --git a/docs/ai-py/img/B15441_17_05.png b/docs/ai-py/img/B15441_17_05.png new file mode 100644 index 0000000000000000000000000000000000000000..051a04662c794ae5e90dbd80b152746a948c9a05 Binary files /dev/null and b/docs/ai-py/img/B15441_17_05.png differ diff --git a/docs/ai-py/img/B15441_17_06.png b/docs/ai-py/img/B15441_17_06.png new file mode 100644 index 0000000000000000000000000000000000000000..5b810f1d9e46f384ba4ed569323fa6ec506813b4 Binary files /dev/null and b/docs/ai-py/img/B15441_17_06.png differ diff --git a/docs/ai-py/img/B15441_17_07.png b/docs/ai-py/img/B15441_17_07.png new file mode 100644 index 0000000000000000000000000000000000000000..7da77cd22ff72ff0a7a584d7293476905dc7ad81 Binary files /dev/null and b/docs/ai-py/img/B15441_17_07.png differ diff --git a/docs/ai-py/img/B15441_17_08.png b/docs/ai-py/img/B15441_17_08.png new file mode 100644 index 0000000000000000000000000000000000000000..c56872a53c6ea8f31df84fee2d054127029de0fa Binary files /dev/null and b/docs/ai-py/img/B15441_17_08.png differ diff --git a/docs/ai-py/img/B15441_17_09.png b/docs/ai-py/img/B15441_17_09.png new file mode 100644 index 0000000000000000000000000000000000000000..302463b43cce62e9abae352d33e080321a0a4621 Binary files /dev/null and b/docs/ai-py/img/B15441_17_09.png differ diff --git a/docs/ai-py/img/B15441_17_10.png b/docs/ai-py/img/B15441_17_10.png new file mode 100644 index 0000000000000000000000000000000000000000..965f47403b98e9bec97bc30a8ece723126bbefd6 Binary files /dev/null and b/docs/ai-py/img/B15441_17_10.png differ diff --git a/docs/ai-py/img/B15441_17_11.png b/docs/ai-py/img/B15441_17_11.png new file mode 100644 index 0000000000000000000000000000000000000000..3cff938f5f7ae1335965989e992a9a40c756fa6a Binary files /dev/null and b/docs/ai-py/img/B15441_17_11.png differ diff --git a/docs/ai-py/img/B15441_17_12.png b/docs/ai-py/img/B15441_17_12.png new file mode 100644 index 0000000000000000000000000000000000000000..2ab2f7e0ad12b7d3406367c4fd5b4a2a0aca67b3 Binary files /dev/null and b/docs/ai-py/img/B15441_17_12.png differ diff --git a/docs/ai-py/img/B15441_17_13.png b/docs/ai-py/img/B15441_17_13.png new file mode 100644 index 0000000000000000000000000000000000000000..9c22646cd7771ac2da7ed00cfb25c646a13af13b Binary files /dev/null and b/docs/ai-py/img/B15441_17_13.png differ diff --git a/docs/ai-py/img/B15441_17_14.png b/docs/ai-py/img/B15441_17_14.png new file mode 100644 index 0000000000000000000000000000000000000000..677d55ba89ff5a45a0d6e4842b12147c5c19f837 Binary files /dev/null and b/docs/ai-py/img/B15441_17_14.png differ diff --git a/docs/ai-py/img/B15441_17_15.png b/docs/ai-py/img/B15441_17_15.png new file mode 100644 index 0000000000000000000000000000000000000000..1ed402ddb95b69d9e99a8eb75a5395f11e485ddc Binary files /dev/null and b/docs/ai-py/img/B15441_17_15.png differ diff --git a/docs/ai-py/img/B15441_17_16.png b/docs/ai-py/img/B15441_17_16.png new file mode 100644 index 0000000000000000000000000000000000000000..12282020aabc9cd99eb40aeb4561c0acb3e8ed88 Binary files /dev/null and b/docs/ai-py/img/B15441_17_16.png differ diff --git a/docs/ai-py/img/B15441_17_17.png b/docs/ai-py/img/B15441_17_17.png new file mode 100644 index 0000000000000000000000000000000000000000..11754435812a71bf453492308cfee594c5a63542 Binary files /dev/null and b/docs/ai-py/img/B15441_17_17.png differ diff --git a/docs/ai-py/img/B15441_18_01.png b/docs/ai-py/img/B15441_18_01.png new file mode 100644 index 0000000000000000000000000000000000000000..c7246c45cb1b8d3185d0873b122ce728e61d91a1 Binary files /dev/null and b/docs/ai-py/img/B15441_18_01.png differ diff --git a/docs/ai-py/img/B15441_18_02.png b/docs/ai-py/img/B15441_18_02.png new file mode 100644 index 0000000000000000000000000000000000000000..f368ce4953ac914914115092a9cfdd66e14c588e Binary files /dev/null and b/docs/ai-py/img/B15441_18_02.png differ diff --git a/docs/ai-py/img/B15441_18_03.png b/docs/ai-py/img/B15441_18_03.png new file mode 100644 index 0000000000000000000000000000000000000000..3d25d60b9075b16e35a5632beb06a4926f8a0231 Binary files /dev/null and b/docs/ai-py/img/B15441_18_03.png differ diff --git a/docs/ai-py/img/B15441_18_04.png b/docs/ai-py/img/B15441_18_04.png new file mode 100644 index 0000000000000000000000000000000000000000..fcc5a14eabe20292fc8a012ecace5585b2647d2c Binary files /dev/null and b/docs/ai-py/img/B15441_18_04.png differ diff --git a/docs/ai-py/img/B15441_18_05.png b/docs/ai-py/img/B15441_18_05.png new file mode 100644 index 0000000000000000000000000000000000000000..30c78703e7535d40918be681ef7411a9b6a1a793 Binary files /dev/null and b/docs/ai-py/img/B15441_18_05.png differ diff --git a/docs/ai-py/img/B15441_18_06.png b/docs/ai-py/img/B15441_18_06.png new file mode 100644 index 0000000000000000000000000000000000000000..8c86cc58b41d38facf8e0cc7a75be460df7bda01 Binary files /dev/null and b/docs/ai-py/img/B15441_18_06.png differ diff --git a/docs/ai-py/img/B15441_18_07.png b/docs/ai-py/img/B15441_18_07.png new file mode 100644 index 0000000000000000000000000000000000000000..e9423086b06936a38e2cf0d882573332dbf032e2 Binary files /dev/null and b/docs/ai-py/img/B15441_18_07.png differ diff --git a/docs/ai-py/img/B15441_18_08.png b/docs/ai-py/img/B15441_18_08.png new file mode 100644 index 0000000000000000000000000000000000000000..9eb4618e1d0a3fa5703be21607f8c0f1997f586a Binary files /dev/null and b/docs/ai-py/img/B15441_18_08.png differ diff --git a/docs/ai-py/img/B15441_18_09.png b/docs/ai-py/img/B15441_18_09.png new file mode 100644 index 0000000000000000000000000000000000000000..7cea2565f7b1393f404aa9154ce7ffa0f1407cee Binary files /dev/null and b/docs/ai-py/img/B15441_18_09.png differ diff --git a/docs/ai-py/img/B15441_18_10.png b/docs/ai-py/img/B15441_18_10.png new file mode 100644 index 0000000000000000000000000000000000000000..0b6c2820156738978a883e19407cee3aad68317d Binary files /dev/null and b/docs/ai-py/img/B15441_18_10.png differ diff --git a/docs/ai-py/img/B15441_18_11.png b/docs/ai-py/img/B15441_18_11.png new file mode 100644 index 0000000000000000000000000000000000000000..90b3d1fdc233702175b346a1bcd422207dd53aea Binary files /dev/null and b/docs/ai-py/img/B15441_18_11.png differ diff --git a/docs/ai-py/img/B15441_18_12.png b/docs/ai-py/img/B15441_18_12.png new file mode 100644 index 0000000000000000000000000000000000000000..4258c9bb9a51953afbbb557382e0739e4fd55892 Binary files /dev/null and b/docs/ai-py/img/B15441_18_12.png differ diff --git a/docs/ai-py/img/B15441_18_13.png b/docs/ai-py/img/B15441_18_13.png new file mode 100644 index 0000000000000000000000000000000000000000..59ffc82cd1dd1631b6cadf27eacb63cf325347f0 Binary files /dev/null and b/docs/ai-py/img/B15441_18_13.png differ diff --git a/docs/ai-py/img/B15441_18_14.png b/docs/ai-py/img/B15441_18_14.png new file mode 100644 index 0000000000000000000000000000000000000000..83c22108a1dd09b44ebf748fa8d1ee5c9d708f4c Binary files /dev/null and b/docs/ai-py/img/B15441_18_14.png differ diff --git a/docs/ai-py/img/B15441_18_15.png b/docs/ai-py/img/B15441_18_15.png new file mode 100644 index 0000000000000000000000000000000000000000..1efa560afb37f66202ee6a99e6a0ada1a3416ac2 Binary files /dev/null and b/docs/ai-py/img/B15441_18_15.png differ diff --git a/docs/ai-py/img/B15441_19_01.png b/docs/ai-py/img/B15441_19_01.png new file mode 100644 index 0000000000000000000000000000000000000000..d95d703d8f428da60007047434da31dc067b5245 Binary files /dev/null and b/docs/ai-py/img/B15441_19_01.png differ diff --git a/docs/ai-py/img/B15441_19_02.png b/docs/ai-py/img/B15441_19_02.png new file mode 100644 index 0000000000000000000000000000000000000000..c5d9c36bfa5109ac9b4e2c5a6579e371be86c1cf Binary files /dev/null and b/docs/ai-py/img/B15441_19_02.png differ diff --git a/docs/ai-py/img/B15441_19_03.png b/docs/ai-py/img/B15441_19_03.png new file mode 100644 index 0000000000000000000000000000000000000000..02e7b6efbd15f1d9038d867b92ac5f39f09dfb79 Binary files /dev/null and b/docs/ai-py/img/B15441_19_03.png differ diff --git a/docs/ai-py/img/B15441_19_04.png b/docs/ai-py/img/B15441_19_04.png new file mode 100644 index 0000000000000000000000000000000000000000..473813888712a6e54387948c7a52c30c7af76b8a Binary files /dev/null and b/docs/ai-py/img/B15441_19_04.png differ diff --git a/docs/ai-py/img/B15441_19_05.png b/docs/ai-py/img/B15441_19_05.png new file mode 100644 index 0000000000000000000000000000000000000000..c888962aa62e9725edbe8783f20990a6d4927503 Binary files /dev/null and b/docs/ai-py/img/B15441_19_05.png differ diff --git a/docs/ai-py/img/B15441_19_06.png b/docs/ai-py/img/B15441_19_06.png new file mode 100644 index 0000000000000000000000000000000000000000..24d71809eb3ba9693e5bfa71ea8f87d6c4a9e33f Binary files /dev/null and b/docs/ai-py/img/B15441_19_06.png differ diff --git a/docs/ai-py/img/B15441_19_07.png b/docs/ai-py/img/B15441_19_07.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c9d790e2e09d65ee755154b4b8e96ee1d50793 Binary files /dev/null and b/docs/ai-py/img/B15441_19_07.png differ diff --git a/docs/ai-py/img/B15441_19_08.png b/docs/ai-py/img/B15441_19_08.png new file mode 100644 index 0000000000000000000000000000000000000000..e89aa0f4f0f8962b4566fe477e6ff083e4c52c24 Binary files /dev/null and b/docs/ai-py/img/B15441_19_08.png differ diff --git a/docs/ai-py/img/B15441_19_09.png b/docs/ai-py/img/B15441_19_09.png new file mode 100644 index 0000000000000000000000000000000000000000..a2ab9ada8332fcea575ed4c9fd093379627192ec Binary files /dev/null and b/docs/ai-py/img/B15441_19_09.png differ diff --git a/docs/ai-py/img/B15441_19_10.png b/docs/ai-py/img/B15441_19_10.png new file mode 100644 index 0000000000000000000000000000000000000000..21a30a1be877298da23d559f77a4dfdda95756e5 Binary files /dev/null and b/docs/ai-py/img/B15441_19_10.png differ diff --git a/docs/ai-py/img/B15441_19_11.png b/docs/ai-py/img/B15441_19_11.png new file mode 100644 index 0000000000000000000000000000000000000000..c1f29844bf362176e1101274778d3484b630202b Binary files /dev/null and b/docs/ai-py/img/B15441_19_11.png differ diff --git a/docs/ai-py/img/B15441_19_12.png b/docs/ai-py/img/B15441_19_12.png new file mode 100644 index 0000000000000000000000000000000000000000..4fdd777c343380ca93b29e12adab1f1f75298d63 Binary files /dev/null and b/docs/ai-py/img/B15441_19_12.png differ diff --git a/docs/ai-py/img/B15441_19_13.png b/docs/ai-py/img/B15441_19_13.png new file mode 100644 index 0000000000000000000000000000000000000000..b7c19055869be8a33f14227124de64189e95ba13 Binary files /dev/null and b/docs/ai-py/img/B15441_19_13.png differ diff --git a/docs/ai-py/img/B15441_19_14.png b/docs/ai-py/img/B15441_19_14.png new file mode 100644 index 0000000000000000000000000000000000000000..edec34bc18ccbf3009271cba432ca11b1888ba64 Binary files /dev/null and b/docs/ai-py/img/B15441_19_14.png differ diff --git a/docs/ai-py/img/B15441_19_15.png b/docs/ai-py/img/B15441_19_15.png new file mode 100644 index 0000000000000000000000000000000000000000..39683361a6d1b3800473000039caf3aeb878687d Binary files /dev/null and b/docs/ai-py/img/B15441_19_15.png differ diff --git a/docs/ai-py/img/B15441_19_16.png b/docs/ai-py/img/B15441_19_16.png new file mode 100644 index 0000000000000000000000000000000000000000..4846fbbdedc6ba2b3ecef8368dca802abe2c14c6 Binary files /dev/null and b/docs/ai-py/img/B15441_19_16.png differ diff --git a/docs/ai-py/img/B15441_19_17.png b/docs/ai-py/img/B15441_19_17.png new file mode 100644 index 0000000000000000000000000000000000000000..01c02e519b180df72ddb39367825e02601beb97a Binary files /dev/null and b/docs/ai-py/img/B15441_19_17.png differ diff --git a/docs/ai-py/img/B15441_19_18.png b/docs/ai-py/img/B15441_19_18.png new file mode 100644 index 0000000000000000000000000000000000000000..2eeeecc9ffbe6ac48e31944d3e8422007b0b7a2f Binary files /dev/null and b/docs/ai-py/img/B15441_19_18.png differ diff --git a/docs/ai-py/img/B15441_19_19.png b/docs/ai-py/img/B15441_19_19.png new file mode 100644 index 0000000000000000000000000000000000000000..4c26e849800fe40eedd5988aa71fb859f3fe3b36 Binary files /dev/null and b/docs/ai-py/img/B15441_19_19.png differ diff --git a/docs/ai-py/img/B15441_20_01.png b/docs/ai-py/img/B15441_20_01.png new file mode 100644 index 0000000000000000000000000000000000000000..2936fe4932c9c623b4d931eada1f05dc0617f36f Binary files /dev/null and b/docs/ai-py/img/B15441_20_01.png differ diff --git a/docs/ai-py/img/B15441_20_02.png b/docs/ai-py/img/B15441_20_02.png new file mode 100644 index 0000000000000000000000000000000000000000..ab131cd17602c5a9873cf4915c0a1bd88884769c Binary files /dev/null and b/docs/ai-py/img/B15441_20_02.png differ diff --git a/docs/ai-py/img/B15441_20_03.png b/docs/ai-py/img/B15441_20_03.png new file mode 100644 index 0000000000000000000000000000000000000000..09371dfe32ac268478516b2ea9c9aca6722f0d9c Binary files /dev/null and b/docs/ai-py/img/B15441_20_03.png differ diff --git a/docs/ai-py/img/B15441_20_04.png b/docs/ai-py/img/B15441_20_04.png new file mode 100644 index 0000000000000000000000000000000000000000..6b7dd082ac98a8c5f1ebb571961062bb78b3ebbf Binary files /dev/null and b/docs/ai-py/img/B15441_20_04.png differ diff --git a/docs/ai-py/img/B15441_20_05.png b/docs/ai-py/img/B15441_20_05.png new file mode 100644 index 0000000000000000000000000000000000000000..8f0004866c0c3e6f493d53f2c2b29e4ef9aac686 Binary files /dev/null and b/docs/ai-py/img/B15441_20_05.png differ diff --git a/docs/ai-py/img/B15441_20_06.png b/docs/ai-py/img/B15441_20_06.png new file mode 100644 index 0000000000000000000000000000000000000000..eed79248cf4b6cec5f15a2292abb73073c08c746 Binary files /dev/null and b/docs/ai-py/img/B15441_20_06.png differ diff --git a/docs/ai-py/img/B15441_20_07.png b/docs/ai-py/img/B15441_20_07.png new file mode 100644 index 0000000000000000000000000000000000000000..9bf70571e789f29d9046a0e4081a89d963aee422 Binary files /dev/null and b/docs/ai-py/img/B15441_20_07.png differ diff --git a/docs/ai-py/img/B15441_20_08.png b/docs/ai-py/img/B15441_20_08.png new file mode 100644 index 0000000000000000000000000000000000000000..0f4ce08e8b20b4c4978b9cfda676d85a8074c63a Binary files /dev/null and b/docs/ai-py/img/B15441_20_08.png differ diff --git a/docs/ai-py/img/B15441_20_09.png b/docs/ai-py/img/B15441_20_09.png new file mode 100644 index 0000000000000000000000000000000000000000..6effc19d037679ce45d7d58222edbb0a5efd1651 Binary files /dev/null and b/docs/ai-py/img/B15441_20_09.png differ diff --git a/docs/ai-py/img/B15441_20_10.png b/docs/ai-py/img/B15441_20_10.png new file mode 100644 index 0000000000000000000000000000000000000000..1f88bbcfddb1f54b87aabe3feac19cc807d9590b Binary files /dev/null and b/docs/ai-py/img/B15441_20_10.png differ diff --git a/docs/ai-py/img/B15441_20_11.png b/docs/ai-py/img/B15441_20_11.png new file mode 100644 index 0000000000000000000000000000000000000000..75bbac8a8c99fd2e2d4cd90a1e73d19ce1669121 Binary files /dev/null and b/docs/ai-py/img/B15441_20_11.png differ diff --git a/docs/ai-py/img/B15441_20_12.png b/docs/ai-py/img/B15441_20_12.png new file mode 100644 index 0000000000000000000000000000000000000000..9bbaf52a9bdb2bb8b1e1f4f339d960c1ab22c797 Binary files /dev/null and b/docs/ai-py/img/B15441_20_12.png differ diff --git a/docs/ai-py/img/B15441_20_13.png b/docs/ai-py/img/B15441_20_13.png new file mode 100644 index 0000000000000000000000000000000000000000..a27eb4fe7b75b7aa513f823f25479f7b13ba26e6 Binary files /dev/null and b/docs/ai-py/img/B15441_20_13.png differ diff --git a/docs/ai-py/img/B15441_20_14.png b/docs/ai-py/img/B15441_20_14.png new file mode 100644 index 0000000000000000000000000000000000000000..458106e0b2fb412f93003c031cd235414f989355 Binary files /dev/null and b/docs/ai-py/img/B15441_20_14.png differ diff --git a/docs/ai-py/img/B15441_21_001.png b/docs/ai-py/img/B15441_21_001.png new file mode 100644 index 0000000000000000000000000000000000000000..4e5e2b7dcd35f3e91080a9606d8c66df2125c81d Binary files /dev/null and b/docs/ai-py/img/B15441_21_001.png differ diff --git a/docs/ai-py/img/B15441_21_002.png b/docs/ai-py/img/B15441_21_002.png new file mode 100644 index 0000000000000000000000000000000000000000..8159150cedeb25bec50f28edf15095f574f2cd07 Binary files /dev/null and b/docs/ai-py/img/B15441_21_002.png differ diff --git a/docs/ai-py/img/B15441_21_003.png b/docs/ai-py/img/B15441_21_003.png new file mode 100644 index 0000000000000000000000000000000000000000..b3166d6015d3312e3472aee116d71f965d4f6d4a Binary files /dev/null and b/docs/ai-py/img/B15441_21_003.png differ diff --git a/docs/ai-py/img/B15441_21_01.png b/docs/ai-py/img/B15441_21_01.png new file mode 100644 index 0000000000000000000000000000000000000000..6c68c4b0c418c9015eb4c7ed212907fe67984063 Binary files /dev/null and b/docs/ai-py/img/B15441_21_01.png differ diff --git a/docs/ai-py/img/B15441_21_02.png b/docs/ai-py/img/B15441_21_02.png new file mode 100644 index 0000000000000000000000000000000000000000..3e2cc81e32f8c8ab3f1f7b11fcbea1b116d3385e Binary files /dev/null and b/docs/ai-py/img/B15441_21_02.png differ diff --git a/docs/ai-py/img/B15441_21_03.png b/docs/ai-py/img/B15441_21_03.png new file mode 100644 index 0000000000000000000000000000000000000000..3b31526d65e828c6446bfcf5c288e06dd644e038 Binary files /dev/null and b/docs/ai-py/img/B15441_21_03.png differ diff --git a/docs/ai-py/img/B15441_21_04.png b/docs/ai-py/img/B15441_21_04.png new file mode 100644 index 0000000000000000000000000000000000000000..b0a3a1731640f9787e6e4891b47a4c1168e7d42e Binary files /dev/null and b/docs/ai-py/img/B15441_21_04.png differ diff --git a/docs/ai-py/img/B15441_21_05.png b/docs/ai-py/img/B15441_21_05.png new file mode 100644 index 0000000000000000000000000000000000000000..0717a0db22ddfb3a8ef575cddde20f41fe04387f Binary files /dev/null and b/docs/ai-py/img/B15441_21_05.png differ diff --git a/docs/ai-py/img/B15441_21_06.png b/docs/ai-py/img/B15441_21_06.png new file mode 100644 index 0000000000000000000000000000000000000000..2ac8299c5a3ea6a92622f4b8690d5b68da206de3 Binary files /dev/null and b/docs/ai-py/img/B15441_21_06.png differ diff --git a/docs/ai-py/img/B15441_21_07.png b/docs/ai-py/img/B15441_21_07.png new file mode 100644 index 0000000000000000000000000000000000000000..1fe1e210ccae9f1f4a1e1d6acec35babc5e7a3c6 Binary files /dev/null and b/docs/ai-py/img/B15441_21_07.png differ diff --git a/docs/ai-py/img/B15441_21_09.png b/docs/ai-py/img/B15441_21_09.png new file mode 100644 index 0000000000000000000000000000000000000000..39dd64e81fa5fac987ce3fc790c9b2b0d2175044 Binary files /dev/null and b/docs/ai-py/img/B15441_21_09.png differ diff --git a/docs/ai-py/img/B15441_21_11.png b/docs/ai-py/img/B15441_21_11.png new file mode 100644 index 0000000000000000000000000000000000000000..c328b7371654aa030c336115b690fe497cba6ee5 Binary files /dev/null and b/docs/ai-py/img/B15441_21_11.png differ diff --git a/docs/ai-py/img/B15441_21_12.png b/docs/ai-py/img/B15441_21_12.png new file mode 100644 index 0000000000000000000000000000000000000000..577819a367808b3853c5ee159ab3b6e91a6340ba Binary files /dev/null and b/docs/ai-py/img/B15441_21_12.png differ diff --git a/docs/ai-py/img/B15441_21_13.png b/docs/ai-py/img/B15441_21_13.png new file mode 100644 index 0000000000000000000000000000000000000000..bf28b2872833208bbb6a192e29a6405c4bc36a4b Binary files /dev/null and b/docs/ai-py/img/B15441_21_13.png differ diff --git a/docs/ai-py/img/B15441_22_01.png b/docs/ai-py/img/B15441_22_01.png new file mode 100644 index 0000000000000000000000000000000000000000..2a2d7f0a08812391e086c11b18eaa03509d140e0 Binary files /dev/null and b/docs/ai-py/img/B15441_22_01.png differ diff --git a/docs/ai-py/img/B15441_22_02.png b/docs/ai-py/img/B15441_22_02.png new file mode 100644 index 0000000000000000000000000000000000000000..3d995ae159fc743fe902ba7230ff0acf9f5f3ecd Binary files /dev/null and b/docs/ai-py/img/B15441_22_02.png differ diff --git a/docs/ai-py/img/B15441_22_03.png b/docs/ai-py/img/B15441_22_03.png new file mode 100644 index 0000000000000000000000000000000000000000..69a78ba04a01ef7e7cce86141cee81b3b76f25fa Binary files /dev/null and b/docs/ai-py/img/B15441_22_03.png differ diff --git a/docs/ai-py/img/B15441_22_04.png b/docs/ai-py/img/B15441_22_04.png new file mode 100644 index 0000000000000000000000000000000000000000..b741a6c4581e7169ede879d2946472f77e4e7d43 Binary files /dev/null and b/docs/ai-py/img/B15441_22_04.png differ diff --git a/docs/ai-py/img/B15441_22_05.png b/docs/ai-py/img/B15441_22_05.png new file mode 100644 index 0000000000000000000000000000000000000000..3237b3d7c0cc88e06d6d3196183afbddcd483882 Binary files /dev/null and b/docs/ai-py/img/B15441_22_05.png differ diff --git a/docs/ai-py/img/B15441_22_06.png b/docs/ai-py/img/B15441_22_06.png new file mode 100644 index 0000000000000000000000000000000000000000..108050170b68a86bbd5cd13b4fd36b4eeadd8a98 Binary files /dev/null and b/docs/ai-py/img/B15441_22_06.png differ diff --git a/docs/ai-py/img/B15441_22_07.png b/docs/ai-py/img/B15441_22_07.png new file mode 100644 index 0000000000000000000000000000000000000000..ca6421edbed434605d92d5cf648084737208271e Binary files /dev/null and b/docs/ai-py/img/B15441_22_07.png differ diff --git a/docs/ai-py/img/B15441_22_08.png b/docs/ai-py/img/B15441_22_08.png new file mode 100644 index 0000000000000000000000000000000000000000..500d25f2f6d03d88fc563da4cad7b25d640dd2ed Binary files /dev/null and b/docs/ai-py/img/B15441_22_08.png differ diff --git a/docs/ai-py/img/B15441_22_09.png b/docs/ai-py/img/B15441_22_09.png new file mode 100644 index 0000000000000000000000000000000000000000..ccf61a5dcf236cf23e98b703d92081ad099edae8 Binary files /dev/null and b/docs/ai-py/img/B15441_22_09.png differ diff --git a/docs/ai-py/img/B15441_22_10.png b/docs/ai-py/img/B15441_22_10.png new file mode 100644 index 0000000000000000000000000000000000000000..b524abf357d2e4cbe6076e97560e7fac2970d228 Binary files /dev/null and b/docs/ai-py/img/B15441_22_10.png differ diff --git a/docs/ai-py/img/B15441_22_11.png b/docs/ai-py/img/B15441_22_11.png new file mode 100644 index 0000000000000000000000000000000000000000..c96d09c91417cef14a9ed7808e9de6f81586f989 Binary files /dev/null and b/docs/ai-py/img/B15441_22_11.png differ diff --git a/docs/ai-py/img/B15441_22_12.png b/docs/ai-py/img/B15441_22_12.png new file mode 100644 index 0000000000000000000000000000000000000000..f62d1b7e4f6d17b0be0d052cda6f9c55bd11833d Binary files /dev/null and b/docs/ai-py/img/B15441_22_12.png differ diff --git a/docs/ai-py/img/B15441_23_01.png b/docs/ai-py/img/B15441_23_01.png new file mode 100644 index 0000000000000000000000000000000000000000..bc4d76eafd9698ff87ebf79caf723763b3e990f5 Binary files /dev/null and b/docs/ai-py/img/B15441_23_01.png differ diff --git a/docs/ai-py/img/B15441_23_02.png b/docs/ai-py/img/B15441_23_02.png new file mode 100644 index 0000000000000000000000000000000000000000..e004351549763299943ee4526d9ed977370b4e73 Binary files /dev/null and b/docs/ai-py/img/B15441_23_02.png differ diff --git a/docs/ai-py/img/B15441_23_03.png b/docs/ai-py/img/B15441_23_03.png new file mode 100644 index 0000000000000000000000000000000000000000..15232c98c99a27320549df68a48ebaa9f4063c81 Binary files /dev/null and b/docs/ai-py/img/B15441_23_03.png differ diff --git a/docs/ai-py/img/B15441_23_04.png b/docs/ai-py/img/B15441_23_04.png new file mode 100644 index 0000000000000000000000000000000000000000..3b2ee2916d66886ac76baee6ca33fa5142cacb4f Binary files /dev/null and b/docs/ai-py/img/B15441_23_04.png differ diff --git a/docs/ai-py/img/B15441_MockupCover_2.1.png b/docs/ai-py/img/B15441_MockupCover_2.1.png new file mode 100644 index 0000000000000000000000000000000000000000..4d5387f3be259afe7890f6a63e39aabfe3e694d1 Binary files /dev/null and b/docs/ai-py/img/B15441_MockupCover_2.1.png differ diff --git a/docs/ai-py/img/Image839.png b/docs/ai-py/img/Image839.png new file mode 100644 index 0000000000000000000000000000000000000000..3756118e421805c691744ee0f208315039e24c82 Binary files /dev/null and b/docs/ai-py/img/Image839.png differ diff --git a/docs/ai-py/img/Image846.png b/docs/ai-py/img/Image846.png new file mode 100644 index 0000000000000000000000000000000000000000..6562f2294b312e029222bf9339f50e5ea39f7ab3 Binary files /dev/null and b/docs/ai-py/img/Image846.png differ diff --git a/docs/ai-py/img/Information_Box_Icon.png b/docs/ai-py/img/Information_Box_Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..99b916187dbd9d589bcbcd2278ed0c9b6d2d0d4e Binary files /dev/null and b/docs/ai-py/img/Information_Box_Icon.png differ diff --git a/docs/ai-py/img/cover.png b/docs/ai-py/img/cover.png new file mode 100644 index 0000000000000000000000000000000000000000..d909b843312e22b932191177e4c1c1c68d1c5857 Binary files /dev/null and b/docs/ai-py/img/cover.png differ diff --git a/docs/ai-py/img/lightbulb.png b/docs/ai-py/img/lightbulb.png new file mode 100644 index 0000000000000000000000000000000000000000..c6b1619f5d1274a7a169bc9ef9b35b729fac037f Binary files /dev/null and b/docs/ai-py/img/lightbulb.png differ diff --git a/docs/ai-py/img/quote.png b/docs/ai-py/img/quote.png new file mode 100644 index 0000000000000000000000000000000000000000..576e6a1c752fea7140f6c1a50c55a914f872f939 Binary files /dev/null and b/docs/ai-py/img/quote.png differ