16 聊天机器人
在本章中,我们将学习聊天机器人。 我们将了解它们是什么以及如何使用它们。 您还将学习如何创建自己的聊天机器人。 我们将涵盖以下主题:
- 聊天机器人的未来
- 聊天机器人的现状
- 基本的聊天机器人概念
- 流行的聊天机器人平台
- DialogFlow:
- 设置 DialogFlow
- 使用小部件将聊天机器人集成到网站中
- 使用 Python 将聊天机器人集成到网站中
- 在 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 是一个很好的聊天机器人听起来很自然的有力例子。 您应该可以在此处观看它的演示。
顺便说一下,这项技术通常是可用的,如果您有 Android 手机或 iPhone,则应该可以使用它。
毫无疑问,聊天机器人将是多产的—在我们的家中,在我们的汽车中,在可穿戴设备中,在呼叫中心以及在电话中。 根据一项估计,聊天机器人的全球市场预计将从 2019 年的 42 亿美元增长到 2024 年的 157 亿美元,年复合增长率为 30.2%。 与其他技术一样,使用该技术成长的年轻人永远不会知道没有聊天机器人为我们服务并改善我们的生活会是什么样子。
本节讨论了聊天机器人在未来几年内的外观。 在下一节中,我们将回到现实,并就如何利用现有的聊天机器人技术使用当今可用的工具创建出色的应用提供一些建议。
聊天机器人的现状
在的上一节中,我们讨论了随着人工智能技术的发展,未来几年可能实现的目标。 与任何技术一样,我们不应该等到一切都变得完美为止。 在本节以及本章的其余部分,我们将重点介绍当今可行的方法以及使您自己的应用尽可能有用和用户友好的最佳实践。
为了利用当今可用的现有技术,并且鉴于仍然需要使用域数据和特定意图来对当前的聊天机器人进行专门编程,因此,我们应该谨慎设计一个好的设计和好的计划来对我们的聊天机器人进行编程。 从为聊天机器人明确定义的目标开始,避免尝试提出广泛的解决方案。 当前,与定义为“万事通”的聊天机器人相比,在定义明确且狭窄的域空间中发挥作用的聊天机器人具有更好的表现和有用的机会。 设计用于在在线商务体验期间提供支持的聊天机器人不能用于诊断汽车问题,而必须在该域中重新编程。 将聊天机器人明确地放在特定的目标和空间上,这样很可能会为用户创造更好的体验。
为了说明这一点,我将分享一个个人故事。 几年前,我参观了迈阿密的一家餐馆。 如您所知,英语是美国最常见的语言,但在迈阿密却不那么普遍。 我们看了看菜单,点了饮料,然后点了开胃菜。 现在该订购主菜了。 我决定与服务员开始一些闲聊。 我忘记了我的具体问题,但这与“您喜欢在迈阿密生活如何?”类似。 脸上惊慌失措的表情告诉我,他不明白这个问题,无论有多少次,我试图解释,他都不会。 为了使他放松,我切换到西班牙语并完成了我们的小聊天。
这里的要点是服务生知道“餐馆英语”以及完成餐厅交易所需的所有短语和互动。 但是其他一切都超出了他的舒适范围。 同样,当我们在以下各节中开发聊天机器人时,只要我们停留在预期的域中,它就可以与我们的用户进行通信。 如果聊天机器人被开发用于预订餐厅,那么如果用户的意图是进行医疗诊断,它将无法提供帮助。
今天的聊天机器人仍然有些狭窄。 目前,我们使用 Alexa,Siri 和 Google Home 可以做的事情很多,它们只能帮助我们完成特定的任务。 他们还不能很好地处理某些人类特质,例如同理心,讽刺和批判性思维。 在目前的状态下,聊天机器人将能够以人类为中心的方式帮助我们完成重复性的交易任务。
但是,即使我们应该尝试使聊天机器人在域上保持尽可能紧密,但这并不意味着我们不应该在机器人中注入一些“个性”。 Alexa 有时可能会显得厚脸皮和幽默,因此您应该为自己的机器人而努力。 这样可以提高与机器人的互动度。
人们在聊天时,通常希望对对话有一定程度的共同兴趣,因此,对话将以这样的方式进行:将存在回答后续问题的答案,以及有助于和促进对话的答案 。 使用一点语将使您的机器人更真实,更有吸引力。
在深入研究自己的聊天机器人的设计之前,让我们介绍一些在开发过程中对我们有帮助的基础概念。
聊天机器人概念
在开发代码之前,让我们设定一个基准并访问一些与聊天机器人相关的有用定义。
智能体
智能体是一个可以处理所有对话并路由所有必要的操作的系统。 这是一种自然语言理解模块,需要经常接受训练以适应特定于使用的需求。
意图
当两个人进行通信时,他们俩都有他们开始进行通信的原因。 这可能很简单,例如与朋友追赶并找出他们在做什么。
可能其中一个正在试图出售某物等等。 这些“意图”可分为三大类:
- 演讲者正在尝试娱乐:例如,有人告诉您开玩笑。
- 演讲者正在尝试通知:有人问几点了,或者温度是多少? 他们收到了答案。
- 演讲者试图说服:议程是试图出售一些东西。
对于大多数聊天机器人,它们的作用是执行命令和执行任务。 由于的原因,他们需要执行的第一个任务是确定调用他们的人的意图。 意图具有诸如上下文,训练阶段,动作和参数以及响应之类的元素。
上下文
上下文是用来给予讨论的连贯性和流畅性,保留了对话中已经使用的关键概念。
实体
这基本上是将一组单词归为一个已定义的实体。 例如,笔,铅笔,纸,橡皮和笔记本可以称为文具。 因此,DialogFlow 提供了已经过训练的预建实体,或者我们可以构建自定义实体并对其进行训练。 这有助于减少训练短语的冗余。
集成
像 DialogFlow 和 Lex 这样的聊天机器人平台可以与大多数最受欢迎的对话和消息传递平台集成,例如 Google Assistant,Facebook Messenger,Kik 和 Viber 等。
履行
履行是一项连接服务,可让您根据最终用户的表达来执行操作,并将动态响应发送回用户。 例如,如果用户正在寻找员工详细信息,则您的服务可以从数据库中获取详细信息,并立即对用户结果进行响应。
语气
每当我们与某人进行对话时,使用稍有不同的方式提出相同的问题是完全正常的。 例如,我们可能会问“今天过得怎么样?”的问题,但是有很多方法可以问相同的问题。 示例包括:
- 告诉我你的一天
- 你今天过得还好么?
- 你今天过的好吗?
- 工作进展如何?
人类自然会善于从话语中解释含义,并回答发问者想问的问题而不是他们实际问的问题。 例如,对问题“您过得愉快吗?”的简单解释。 将要求答案为是或否。 但是作为人类,我们有足够的技巧来理解的真正含义可能是“告诉我您的一天”。
许多聊天机器人平台都变得越来越好,不再要求我们拼出每一个发音,而是能够进行一些“模糊”匹配,并且不需要为每个单独的组合输入内容。
唤醒词
像 Alexa 或 Siri 这样的许多聊天机器人保持休眠状态,直到它们被“唤醒”并准备好接收命令为止。 要唤醒它们,需要一个“唤醒词”。 对于 Alexa,最常用的唤醒词是“Alexa”。 对于 Siri,默认唤醒字为“Hey Siri”。 并且,对于 StarShip 企业版,唤醒词是“Computer”。
启动词
聊天机器人唤醒后,很多时候我们希望该聊天机器人为我们执行一个动作,因此我们需要“启动”该动作。 一些启动词的示例是:
order
(订购)tell me
(告诉我)add
(添加)turn on
(打开)
您可以将启动词视为命令。
插槽值或实体
插槽值是字,将被转换为参数。 让我们看几个例子:
Order *milk*
Tell me the capital of *Italy*
Add *bread* to the shopping list
Turn on the *oven*
插槽值带有下划线。 插槽值可以具有插槽类型。 就像参数可以具有参数类型(整数,字符串等)一样。 某些插槽类型是内置的,还可以创建自定义插槽类型。 插槽类型的一些示例是:
- 国名
- 电子邮件地址
- 电话号码
- 日期
一些聊天机器人平台将插槽类型称为实体。
错误计划和默认情况
设计良好的聊天机器人应始终优雅地处理无法预料的情况。 当聊天机器人没有针对特定交互的程序化答案时,它应该能够显示默认行为,以尽可能优雅地处理无法预料的情况。 例如,如果聊天机器人的功能是预订美国境内的国内航班,但用户请求飞往加拿大温哥华的包机,则聊天机器人应能够优雅地告诉用户他们仅服务于美国城市,然后再次询问目的地 。
Webhooks
Webhook 是,HTTP 推送 API 或 Web 回调。 它也称为反向 API,因为一旦事件发生,它就会将数据从应用发送到应用使用者。 它消除了消费者不断轮询应用的需求。
既然我们已经介绍了与聊天机器人更好地协作所需的基本概念,让我们考虑如何创建一个有用的,“结构良好”的聊天机器人。
一个结构合理的聊天机器人
为了使聊天机器人有用并使其高效,它必须具有某些特质。 我们将具有这些特质的聊天机器人称为“结构良好”的聊天机器人。 我们列出并定义以下质量:
适应性
自适应聊天机器人是一种可以理解并适应收到的所有语音的聊天机器人。 即使对于未明确编程的语音,也应该有一个优美的响应,以使聊天机器人用户重回正轨,或者利用此机会将会议转移给实时操作员。
个性化设置
作为人类,我们喜欢感到与众不同。 我们喜欢听到自己的名字,也喜欢别人记住我们的一些小事(我们孩子的名字,母校等等)。 个性化的聊天机器人会记住以前的交互以及他们收集的有关单个用户的信息。
可用性
应该有一个聊天机器人可以帮助用户。 这超出了传统平台的可用性。 当然,我们的聊天机器人应该随时准备提供帮助,并在需要时随时进行访问。 但这也适用于我们能够以多快的速度获取聊天机器人来帮助我们实现意图。 想一想在传统交互式语音响应(IVR)系统中的导航树,在这些导航树中,我们必须按很多数字,然后他们才知道我们要执行的意图。 此类系统的可用性较低。
相关性
与相关的聊天机器人可以使聊天机器人的用户感知到他们确实在进行正常的对话。
我们几乎已经准备好继续开发自己的聊天机器人。 但是,在此之前,我们应该考虑主要的聊天机器人平台,这些平台是我们聊天机器人的开发和发行的基础。
聊天机器人平台
一些使用最广泛的聊天机器人是由主要供应商(例如 Google,AWS 和 Microsoft)开发的平台实现的。 在为聊天机器人选择技术栈时,应仔细考虑他们的服务产品。 这三大供应商均提供可靠且可扩展的云计算服务,这些服务将帮助您根据需要实现和自定义聊天机器人。 到目前为止,可以轻松创建基于文本或语音的机器人的最著名平台如下:
- DialogFlow(Google,以前为 Api.ai)
- Azure Bot 服务(Microsoft)
- Lex(AWS)
- Wit.ai(Facebook)
- Waston(IBM)
当然可以使用此处列出的平台以及其他流行的平台编写功能强大的聊天机器人。 但是我们现在将更深入地研究一个平台,尤其是获得更深入的了解。 在聊天机器人服务中,DialogFlow 对于初学者来说是一个不错的选择。 我们将在下一节中讨论 DialogFlow,并在本章的其余部分中使用该平台进行聊天机器人的开发。
使用 DialogFlow 创建聊天机器人
Google 在机器学习和自然语言处理(NLP)中有着广泛的研究历史。 这项研究的大部分内容都反映在他们的 DialogFlow 工具中。 DialogFlow 与 Google Cloud 语音转文本 API 以及其他第三方服务(例如 Google Assistant,Amazon Alexa 和 Facebook Messenger)集成在一起。
无需编写任何代码即可创建提供很多功能的聊天机器人。 最初,我们将回顾如何使用 Google Cloud Platform(GCP)控制台,仅通过即可不使用代码来配置聊天机器人。 在本章的以下各节中,我们将演示如何将聊天机器人与其他服务集成。 本章后面的部分将需要对 Python 语言有基本的了解。
入门步骤
- 使用 Google 帐户在这里免费注册。
- 为了使聊天机器人正常工作,您需要接受 DialogFlow 所请求的所有权限。 这样,您就可以管理 GCP 服务中的数据,并与 Google 助手集成。
- 您可以通过这里访问 DialogFlow 控制台。
- 最后,通过选择主要语言(以后可以添加其他语言)和 Google Project 标识符来创建新智能体。 要启用结算和其他设置,需要使用 Google Cloud Console 中的项目名称。 如果您没有现有项目,请不要担心:创建一个新项目。
现在您已经注册并可以使用,让我们开始设置 DialogFlow。
DialogFlow 设置
首次登录 DialogFlow 时,将要求您允许某些权限。 建议您允许这些权限。 否则,以下练习将无法正常进行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MXJ7E6po-1681568818800)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_16_01.png)]
图 1:Google Cloud Console 访问 DialogFlow 控制台的权限
DialogFlow 中的开发利用了我们之前讨论的两个主要概念-意向和上下文。 目的标识用户向聊天机器人发出语音的目的。 上下文使对话变得连贯和流畅。
单击意图选项卡后,您应该看到如下屏幕:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f1qTCbmt-1681568818801)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_16_02.png)]
图 2:DialogFlow 聊天机器人意图创建
正如我们先前看到的,聊天机器人开发中的另一个重要概念是插槽类型。 在 DialogFlow 中,我们将插槽类型称为实体。 通过实体,可以识别对话中的常见或重复出现的参数。 实体可以是内置的或定制的。 实体的使用使聊天机器人更具通用性和灵活性。 单击实体标签时,您应该看到以下屏幕:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-93guXiQS-1681568818801)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_16_03.png)]
图 3:DialogFlow 聊天机器人实体创建
让我们从仅使用意图的基本示例开始。 首先,我们将创建智能体,然后通过 DialogFlow 接口定义一些意图。 可以通过编程方式创建这些意图,但是为了使示例保持简单,我们将使用图形化界面来创建意图。 首先,让我们设置后备意图。 如果没有其他意图被调用,则将被调用:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DbW3u07U-1681568818801)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_16_04.png)]
图 4:创建 DialogFlow 后备意图
正如您在“图 4”中看到的那样,只需编写立即尝试表格即可获得答案。 最初,当尚未创建任何意图时,聊天机器人将使用后备意图。 具有后备意图可防止对话陷入停顿。
当我们浏览默认回退意图时,我们看到响应的完整列表。 如您所见,已经定义了许多响应。 当意图匹配时,聊天机器人引擎将随机选择一个项目作为答案。
让我们创建我们的第一个意图。 我们可以使用控制台执行此操作。 确保您还填写了训练短语表格。 这些是我们期望用户触发这些意图的句子。 我们在构造这些句子时越精确,越全面,那么聊天机器人在识别意图方面将越成功。
现在,我们可以通过插入更多意图为聊天机器人添加更多功能来进行操作。 我们可以使用右侧的助手连续测试我们的聊天机器人。
希望很明显,仅使用意图就可以创建功能强大的聊天机器人。 DialogFlow 正在为我们完成大部分繁重的工作。 为了使聊天机器人更加强大,我们可以开始向意图添加上下文。 我们可以通过在从一个意图转到另一个意图的同时添加参数,同时保持对话的上下文,来使我们的聊天机器人更加灵活。 在本教程的下一部分中,我们将看到如何将聊天机器人集成到网站中。
使用小部件将聊天机器人集成到网站中
可以通过两种方法将 DialogFlow 聊天机器人集成到网站中:
- 通过小部件
- 使用 Python
我们将从访问第一种方法开始,这是更简单的方法。 此方法使用iframe
将 DialogFlow 集成到网页中。 要使用此方法,请从左侧菜单中选择Integrations
,并确保已启用Web Demo
。 复制 HTML 代码并将其粘贴到网页中,这样就可以在您的网站上使用聊天机器人了:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iXxA2MDJ-1681568818802)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_16_05.png)]
图 5:使用iframe
将聊天机器人集成到网站中
在下面的部分中,我们将考虑第二种方法-使用 Python。 如我们所见,集成聊天机器人的小部件方法非常简单。 但是,我们几乎无法控制机器人的实现方式。 使用 Python 将聊天机器人集成到站点中,可以在如何部署聊天机器人方面为开发人员提供更多的控制权和灵活性。
使用 Python 将聊天机器人集成到网站中
调用 DialogFlow 聊天机器人的另一种方法是使用 Python。 首先,我们需要安装运行代码所需的包要求:
代码语言:javascript复制$ pip3 install DialogFlow
$ pip3 install google-api-core
该代码初始化一个以意图为输入的客户端会话,最后返回一个响应,即所谓的fulfillment
,并将相应的置信度作为一个十进制值。 我们要获取答案的句子保存在名为text_to_be_analyzed
的变量中。 通过添加句子来编辑脚本。 使用 Python,很容易创建更多的自定义逻辑。 对于示例,您可以捕获意图,然后触发自定义操作:
# 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,我们首先需要安装它:
代码语言:javascript复制$ pip3 install Flask
要了解有关 Flask 的更多信息,您可以访问这里。
使用 Flask 创建 Webhook
首先,我们可以创建一个基本的 Flask 应用:
代码语言:javascript复制# 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()
使用以下命令测试应用:
代码语言:javascript复制$ python app.py or FLASK_APP=hello.py flask run
如果您看到前面的输出,这将确认该应用的初始版本正在运行。 到目前为止,我们仅使用本地服务器,因此其他外部客户端无法通过互联网访问该服务器。 要将其集成为 DialogFlow 的 Webhook,我们需要将其部署在可以通过互联网访问的服务器上。 那就是 ngrok 工具出现的地方。可以在这里下载该工具。
要运行 ngrok,请使用以下命令:
代码语言:javascript复制$ ngrok http <port_number>
例如:
代码语言:javascript复制$ ngrok http 5000
收到的输出应该是这样的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lXKsDobf-1681568818802)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_16_06.png)]
图 6:ngrok 初始化输出
在下一节中,我们将研究如何在 DialogFlow 中设置 Webhook。
如何在 DialogFlow 中设置 Webhook
要在 DialogFlow 中设置 Webhook,请在左侧栏中选择实现,然后选择输入 ngrok 生成的 Webhook URL:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N6vIpCmN-1681568818802)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_16_07.png)]
图 7:Dialogflow Webhook 设置
确保将后缀/webhook
添加到 URL 的末尾。 它看起来应该像这样:
https://886b89bc.ngrok.io/webhook
不:
https:// 886b89bc.ngrok.io/
我们将在/webhook
路由而不是索引路由上处理请求。
如果该 URL 没有 webhook 后缀,则应出现以下错误:
代码语言:javascript复制Webhook call failed. Error: 405 Method Not Allowed.
更正 URL 以包括后缀,这样可以解决该错误。
接下来,需要启用 Webhook 以支持意图并获取服务器数据。 在下一节中,我们将介绍如何做到这一点。
为意图启用 Webhook
要为意图启用 webhooks ,请打开需要启用 webhook 的意图,向下滚动到页面底部,然后启用选项为此意图启用 webhook 调用:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I5EmO2Y0-1681568818803)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_16_08.png)]
图 8:意图的 Dialogflow Webhook 启用
触发意图后,它会向 Webhook 发送请求,然后将响应发送回去。 现在,我们可以继续设置训练阶段。
为意图设置训练短语
训练短语是帮助聊天机器人确定被调用意图的语音。 这是一个应如何设置的示例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nNkvmRTd-1681568818803)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_16_09.png)]
图 9:意图的 Dialogflow 训练短语设置
接下来,我们需要设置参数和操作。
设置意图的参数和动作
需要先设置操作和参数,然后才能在 Webhook 中使用来处理请求。
在当前的示例中,get_results
被设置为动作。 每当意图使用POST
请求调用 Webhook 时,get_results
将作为动作收到。 如果存在可以调用 Webhook 的多个意图,则将使用该动作进行区分,并由此生成不同的响应。
我们还可以将参数传递到我们的 webhook。 为此,我们可以定义参数名称及其值。 在此示例中,我们将非常简单地开始,但最终我们将允许用户从餐厅订购食物。 因此,例如,用户可能会说:“我想订购汉堡和薯条”,而聊天机器人会将这种话语传递给后端,以进行验证,存储和处理:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nb3xTMgE-1681568818803)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_16_10.png)]
图 10:Dialogflow 操作和参数设置
动作和参数是可选值。 即使未传递操作和参数,Webhook 仍将运行。 为了区分没有动作的意图,可以在请求 JSON 中检查意图名称。
通过 Webhook 建立响应
Webhook 响应应该使用有效的 JSON 响应构造。 这样,DialogFlow 将能够在前端正确显示消息。
可以使用 Python 构造响应。 可以使用以下响应类型:
- 简单响应
- 基本卡片
- 意见建议
- 清单卡片
- 浏览轮播
- 轮播响应
以下代码为 DialogFlow 生成一个简单的 JSON 响应,并带有fulfillment
文本:
# 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 /实现)机制。 用户应该看到以下意图响应:
代码语言:javascript复制This is a webhook response.
您可以看到我们已经使用以下命令从请求中提取了action
:
action = req.get('queryResult').get('action')
在此示例中未使用操作,但可以将用于此目的。 现在,我们将研究如何从服务器获取响应以及如何根据响应来处理响应。
检查来自 Webhook 的响应
使用窗口右侧的控制台,可以调用意向,并可以检查响应。 在当前示例中,响应将如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QrS1wh3L-1681568818803)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_16_11.png)]
图 11:Dialogflow 响应验证
如果要调试聊天机器人并对其进行故障排除,可以单击诊断信息。 在这里,我们可以查看所有 DialogFlow 请求的详细信息以及从 Webhook 发送回的响应。 如果 Webhook 中存在错误,则诊断信息也可以用于调试。
本章的目的是介绍聊天机器人的基本知识。 在这一点上,我们的聊天机器人除了演示如何从服务器获取响应外,还没有做其他事情。 我们将其留给您,以探索如何进一步增强聊天机器人。 一些明显的增强功能包括:根据建立的菜单检查所请求的食物,以查看菜单上是否有可用项目; 根据当前库存检查请求的食物数量,以查看是否可以完成订单; 将订单存储在后端数据库中以进行记帐和跟踪; 并将聊天机器人连接到机器人或后端系统以实际完成订单。
总结
在本章中,我们首先了解了聊天机器人的潜在未来,以及聊天机器人变得越来越好将如何影响我们的社会。 然后,我们了解了当前聊天机器人技术的局限性以及当前局限性推荐的最佳实践。 我们了解了基本的聊天机器人概念以及最流行的聊天机器人平台。
最后,我们更深入地研究了由 Google 开发的名为 DialogFlow 的聊天机器人平台。 通过执行基本练习,然后学习如何使用 Webhooks 与其他后端服务集成,我们熟悉了该平台。 我们一步一步地了解了如何测试聊天机器人的功能并确保其设置正确。
在下一章中,我们将跳到另一个令人兴奋的主题,并学习如何训练序列数据并将其用于时间序列分析。
17 序列数据和时间序列分析
在本章中,我们将学习如何构建序列学习模型。 为此,我们将涵盖许多主题,以使我们更好地掌握如何构建和使用这些模型。 我们将学习如何处理 Pandas 中的时间序列数据。 我们将了解如何分割时间序列数据并对其执行各种操作,然后我们将讨论如何滚动地从时间序列数据中提取各种统计信息。 接下来,我们将学习隐马尔可夫模型(HMM),然后实现一个用于构建这些模型的系统。 我们将了解如何使用条件随机场来分析字母序列,最后,我们将讨论如何使用到目前为止学到的技术来分析股市数据。
在本章的最后,您将学到:
- 了解序列数据
- 使用 Pandas 处理时间序列数据
- 切片时间序列数据
- 操作时间序列数据
- 从时间序列数据中提取统计信息
- 使用隐马尔可夫模型生成数据
- 使用条件随机场识别字母序列
- 股市分析
让我们开始研究并理解序列数据。
了解序列数据
在机器学习的世界中,我们遇到了许多类型的数据,例如图像,文本,视频和传感器读数。 不同类型的数据需要不同类型的建模技术。 序列数据是指顺序很重要的数据。 序列数据可以在许多“野外”情况下找到。 这里有些例子:
基因组序列数据:这个也许是我们拥有的序列数据的最好和最重要的例子。 基因出现的顺序是创造和维持生命的最基本水平。 基因组学序列包含使我们存活的信息。
人类语言:沟通时顺序非常重要。 如果我们开始更改本书中单词的顺序,不久之后,本书将变得完全不可理解!
计算机语言:在大多数计算机语言中,正确的输入顺序对于任何功能正常运行至关重要。 例如,在许多计算机语言中,符号>=
的意思是“大于或等于”,而在其他语言中,=>
的意思可能是赋值或产生语法错误。
时间序列数据是序列数据的子分类。 时间序列数据的一些示例如下:
股票市场价格:时间序列数据的圣杯是股票价格。 许多数据科学家将在其职业生涯中的某个时刻尝试使用其数据科学技能来尝试预测股市。 他们中的许多人将意识到这是一项艰巨的努力,并转向其他主题和问题。 股票预测困难的几个原因是:
- 在经济周期的不同时间,股票对经济状况的反应不同。
- 影响股票价格的因素很多,这使它成为一个非常复杂的系统。
- 股票中一些最剧烈的变动发生在市场交易时间之外,这使得实时处理这些信息变得困难。
应用日志:根据定义,应用日志具有两个组成部分。 指示操作何时发生以及正在记录信息或错误的时间戳。
IoT 活动:IoT 设备中的活动以时间顺序方式发生,因此可以用作时间的数据。
时间序列数据是从任何数据源(例如传感器,麦克风,股票市场等)获得的时间戳值。 时间序列数据具有许多重要特征,需要对其进行建模才能进行有效分析。
时间序列数据中某些参数的测量以固定的时间间隔进行。 这些测量被安排并存储在时间线上,并且它们的出现顺序至关重要。 此顺序用于从数据中提取模式。
在本章中,我们将看到如何构建通常描述时间序列数据和序列数据的模型。 这些模型将用于了解时间序列变量的行为。 然后,我们将能够使用这些模型来预测和推断该模型以前未看到的值。
时间序列数据分析被广泛应用于金融,传感器数据分析,语音识别,经济学,天气预报,制造以及更多领域。 在本章中,我们将广泛使用一个名为 Pandas 的库来处理与时间序列相关的操作。
Pandas 是强大的和流行的 Python 包,用于数据处理和分析。 具体来说,它提供了用于操纵表结构的方法和操作。 这是一个可爱的名字,让人联想到毛茸茸的熊,但这里有一些没用的琐事。 Pandas 的名称来自面板数据一词,这是计量经济学术语,用于包含多个时间段内观测值的数据集。
我们还将使用其他几个有用的包,例如hmmlearn
和pystruct
。 确保继续安装它们。
可以通过运行以下命令来安装这些包:
代码语言:javascript复制$ pip3 install pandas
$ pip3 install hmmlearn
$ pip3 install pystruct
$ pip3 install cvxopt
$ pip3 install timeseries
如果在安装cvxopt
时出错,则可以在这个页面上找到进一步的说明。 假设您已经成功安装了包,让我们继续进行下一部分,在此我们将研究如何通过和 Pandas 处理时间序列数据。
使用 Pandas 处理时间序列数据
Pandas 可以说是 Python 中最重要的库。 学习良好地使用其方法至关重要,当您将 Python 用于其他任何项目时,它将为您提供良好的服务。 除了时间序列分析外,Pandas 还可以执行更多功能,包括:
- 使用集成索引的数据帧操作
- 从各种不同的文件格式读取数据并将数据写入内存数据结构的方法
- 数据分类
- 数据筛选
- 缺失值估计
- 重塑和旋转数据集
- 基于标签的切片,索引和子集创建
- 高效的列插入和删除
- 数据集上的按分组操作
- 合并和连接数据集
在本节中,我们将使用它来将数字序列转换为时间序列数据并将其可视化。 Pandas 提供了添加时间戳,组织数据然后对其进行有效操作的选项。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
定义一个函数以从输入文件中读取数据。 参数索引指示包含相关数据的列:
代码语言:javascript复制def read_data(input_file, index):
# Read the data from the input file
input_data = np.loadtxt(input_file, delimiter=',')
定义lambda
函数以将字符串转换为 Pandas 日期格式:
# Lambda function to convert strings to Pandas date format
to_date = lambda x, y: str(int(x)) '-' str(int(y))
使用此lambda
函数可从输入文件的第一行获取开始日期:
# Extract the start date
start = to_date(input_data[0, 0], input_data[0, 1])
在执行操作时,Pandas 库需要结束日期为独占日期,因此我们需要将最后一行的date
字段增加一个月:
# 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)
使用开始日期和结束日期以及每月一次的频率来创建带有日期的索引列表:
代码语言:javascript复制 # Create a date list with a monthly frequency
date_indices = pd.date_range(start, end, freq='M')
使用时间戳创建一个 Pandas 数据序列:
代码语言:javascript复制 # Add timestamps to the input data to create time series data
output = pd.Series(input_data[:, index], index=date_indices)
return output
定义和main
函数并指定输入文件:
if __name__=='__main__':
# Input filename
input_file = 'data_2D.txt'
指定包含数据的列:
代码语言:javascript复制 # Specify the columns that need to be converted
# into time series data
indices = [2, 3]
遍历各列并读取每列中的数据:
代码语言:javascript复制 # Iterate through the columns and plot the data
for index in indices:
# Convert the column to timeseries format
timeseries = read_data(input_file, index)
绘制时间序列数据:
代码语言:javascript复制 # Plot the data
plt.figure()
timeseries.plot()
plt.title('Dimension ' str(index - 1))
plt.show()
完整代码在文件timeseries.py
中给出。 如果运行代码,您将看到两个屏幕截图。
以下屏幕截图显示了第一维的数据:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-av2rBG1A-1681568818804)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_01.png)]
图 1:使用每日数据绘制的第一维数据
第二张屏幕截图以第二维表示数据:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fESxTYX3-1681568818804)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_02.png)]
图 2:使用每日数据绘制的第二维数据
在本节中,我们为如何使用 Pandas 从外部文件加载数据,如何将其转换为时间序列格式以及如何对其进行绘制和可视化奠定基础。 在下一节中,我们将学习如何进一步处理数据。
切片时间序列数据
现在我们已经加载了时间序列数据,让我们看看如何对其进行切片。 切片的过程是指将数据分为多个子间隔并提取相关信息。 当我们使用时间序列数据集时,这很有用。 我们将使用时间戳来切片数据,而不是使用索引。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from timeseries import read_data
从输入数据文件中加载第三列(零索引):
代码语言:javascript复制# Load input data
index = 2
data = read_data('data_2D.txt', index)
定义开始和结束年份,然后以年级粒度绘制数据:
代码语言:javascript复制# Plot data with year-level granularity
start = '2003'
end = '2011'
plt.figure()
data[start:end].plot()
plt.title('Input data from ' start ' to ' end)
定义开始和结束月,然后以月级粒度绘制数据:
代码语言:javascript复制# 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-iUHlPxTM-1681568818804)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_03.png)]
图 3:使用每月滴答作图的数据(2003 年至 2011 年)
第二张屏幕截图显示了 1998 年 2 月到 2006 年 7 月的数据:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bIQOeqP7-1681568818804)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_04.png)]
图 4:使用月度滴答作图的数据(1998 年至 2006 年)
正如我们在上一节创建的图表(“图 1”和“图 2”)中看到的那样,它们很难阅读。 数据被“捆绑”。 通过使用每月刻度对数据进行切片,可以更轻松地可视化数据的起伏。 在下一节中,我们将继续学习 Pandas 库中可用的不同功能,例如过滤和求和,以及该功能如何帮助更好地分析和处理数据集。
操作时间序列数据
Pandas 库可以有效地处理时间序列数据,并执行各种操作,例如过滤和加法。 可以设置条件,Pandas 会过滤数据集并根据条件返回正确的子集。 时间序列数据也可以加载和过滤。 让我们看另一个例子来说明这一点。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from timeseries import read_data
定义输入文件名:
代码语言:javascript复制# Input filename
input_file = 'data_2D.txt'
将第三和第四列加载到单独的变量中:
代码语言:javascript复制# Load data
x1 = read_data(input_file, 2)
x2 = read_data(input_file, 3)
通过命名两个维度来创建 Pandas DataFrame
对象:
# Create pandas dataframe for slicing
data = pd.DataFrame({'dim1': x1, 'dim2': x2})
通过指定开始和结束年份来绘制数据:
代码语言:javascript复制# Plot data
start = '1968'
end = '1975'
data[start:end].plot()
plt.title('Data overlapped on top of each other')
使用条件过滤数据,然后显示它。 在这种情况下,我们将获取dim1
中所有小于45
的数据点和dim2
中所有大于30
的值:
# 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')
我们也可以在 Pandas 中添加两个序列。 让我们在给定的开始日期和结束日期之间添加dim1
和dim2
:
# 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-1aRrYUO3-1681568818805)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_05.png)]
图 5:重叠数据(1968 年至 1975 年)
第二张屏幕截图显示了他过滤的数据:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-esLg8n8g-1681568818805)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_06.png)]
图 6:dim1 < 45
和dim2 > 30
的数据(1968 年-1975 年)
第三个屏幕截图显示了求和结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SVeBVigp-1681568818805)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_07.png)]
图 7:dim1
和dim2
的总和(1968 – 1975)
在本节中,我们继续学习 Pandas 库中可用的不同功能,包括过滤和求和。 在数据科学中,选择和训练模型以了解正在分析的数据集很重要。 Pandas 是完成此任务的有用工具。 在下一节中,我们将介绍另外两个有用的库。 这些库用于计算有关数据集的各种统计信息。
从时间序列数据中提取统计信息
为了从时间序列数据中提取有意义的见解,我们可以从中生成统计数据。 这些统计信息的示例包括平均值,方差,相关性,最大值等操作。 这些统计信息可以使用窗口滚动计算。 我们可以使用预定的窗口大小,并在该窗口内计算这些统计信息。 当我们可视化一段时间内的统计信息时,我们可能会看到有趣的模式。 让我们看一个如何从时间序列数据中提取这些统计信息的示例。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from timeseries import read_data
定义输入文件名:
代码语言:javascript复制# Input filename
input_file = 'data_2D.txt'
将第三和第四列加载到单独的变量中:
代码语言:javascript复制# Load input data in time series format
x1 = read_data(input_file, 2)
x2 = read_data(input_file, 3)
通过命名两个维度来创建 PandasDataFrame
:
# Create pandas dataframe for slicing
data = pd.DataFrame({'dim1': x1, 'dim2': x2})
沿每个维度提取最大值和最小值:
代码语言:javascript复制# Extract max and min values
print('nMaximum values for each dimension:')
print(data.max())
print('nMinimum values for each dimension:')
print(data.min())
提取前12
行的总体均值和行均值:
# Extract overall mean and row-wise mean values
print('nOverall mean:')
print(data.mean())
print('nRow-wise mean:')
print(data.mean(1)[:12])
使用24
窗口大小绘制滚动平均值:
# Plot the rolling mean using a window size of 24
data.rolling(center=False, window=24).mean().plot()
plt.title('Rolling mean')
打印相关系数:
代码语言:javascript复制# Extract correlation coefficients
print('nCorrelation coefficients:n', data.corr())
使用窗口大小60
绘制滚动相关性:
# 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-0ChoPgCp-1681568818805)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_08.png)]
图 8:滚动平均值
第二张屏幕截图显示了滚动相关性:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dBS328sZ-1681568818806)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_09.png)]
图 9:滚动相关
您也应该看到以下输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XTuCWvny-1681568818806)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_10.png)]
图 10:最大和最小大小以及总体平均值
如果向下滚动,则将看到按行平均值和打印出的相关系数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HgiTsHGg-1681568818806)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_11.png)]
图 11:逐行均值和相关系数
上图中的相关系数表示每个维度与所有其他维度的相关程度。 1.0
的相关性表示完全相关,而0.0
的相关性表示变量根本不相关。 dim1
与dim1
完美相关,dim2
与dim2
完美相关。 在任何混淆矩阵中,情况总是如此。 只是说一个变量与其自身完全相关。 另外,dim1
与dim2
具有低相关性。 这意味着dim1
将具有较低的功率来预测dim2
的值。 到目前为止,我们将不会很快凭借我们的模型及其预测股票价格的能力而成为百万富翁。 在下一节中,我们将学习一种有用的技术来分析时间序列数据,称为隐马尔科夫模型(HMM)。
使用隐马尔可夫模型生成数据
隐马尔可夫模型(HMM)是一种用于分析连续数据的强大分析技术。 假设要建模的系统是具有隐藏状态的马尔可夫过程。 这意味着基础系统可以是一组可能状态中的一个。
它经历一系列状态转换,从而产生一系列输出。 我们只能观察输出,而不能观察状态。 因此,这些状态对我们是隐藏的。 我们的目标是对数据建模,以便我们可以推断未知数据的状态转换。
为了理解 HMM,让我们考虑旅行商问题(TSP)的版本。 在此示例中,推销员必须在以下三个城市之间旅行才能工作:伦敦,巴塞罗那和纽约。 他的目标是最大程度地减少旅行时间,以使其成为最有效率的人。 考虑到他的工作承诺和时间表,我们有一组概率决定了从城市X
到城市Y
的机会。 在给出的以下信息中,P(X -> Y)
表示从城市X
到城市Y
的概率:
城市 | 概率 |
---|---|
P(London->London) | 0.10 |
P(London->Barcelona) | 0.70 |
P(London-> NY) | 0.20 |
P(Barcelona->Barcelona) | 0.15 |
P(Barcelona->London) | 0.75 |
P(Barcelona-> NY) | 0.10 |
P(NY->NY) | 0.05 |
P(NY->London) | 0.60 |
P(NY->Barcelona) | 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
因此,本质上:
代码语言:javascript复制X(i 3) = X(i).T ^ 3
我们将X(i)
设置为:
X(i) = [0.10 0.70 0.20]
下一步是计算矩阵的立方。 在线提供了许多工具来执行矩阵运算,例如这里。
如果您进行了所有矩阵计算,那么您将看到在星期四将获得以下概率:
代码语言:javascript复制P(London) = 0.31
P(Barcelona) = 0.53
P(NY) = 0.16
我们可以看到他在巴塞罗那的机会要比其他任何城市都高。 这也具有地理意义,因为巴塞罗那比纽约更靠近伦敦。 让我们看看如何在 Python 中为 HMM 建模。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import datetime
import numpy as np
import matplotlib.pyplot as plt
from hmmlearn.hmm import GaussianHMM
from timeseries import read_data
从输入文件加载数据:
代码语言:javascript复制# Load input data
data = np.loadtxt('data_1D.txt', delimiter=',')
提取第三列进行训练:
代码语言:javascript复制# Extract the data column (third column) for training
X = np.column_stack([data[:, 2]])
使用5
分量和对角协方差创建高斯 HMM:
# Create a Gaussian HMM
num_components = 5
hmm = GaussianHMM(n_components=num_components,
covariance_type='diag', n_iter=1000)
训练 HMM:
代码语言:javascript复制# Train the HMM
print('nTraining the Hidden Markov Model...')
hmm.fit(X)
打印 HMM 每个组成部分的均值和方差值:
代码语言:javascript复制# 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
样本并绘制它们:
# 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-zXi8JuAa-1681568818806)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_12.png)]
图 12:生成的数据
您还将看到以下输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r9fDqcX5-1681568818806)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_13.png)]
图 13:HMM 训练
在给定不同行程的情况下,我们解释图表的方式是遍历销售路线所需的时间不同。 第二个输出指示这些路径的均值和方差。
既然我们已经了解了 HMM,现在让我们了解与时间序列分析相关的另一个主题。 在下一部分中,我们将学习通常称为条件随机场的概率模型及其与 HMM 的区别。
使用条件随机场识别字母序列
条件随机场(CRF)是概率模型,经常用于分析结构化数据。 我们使用它们以各种形式标记和分割序列数据。 以下是一些应用 CRF 的最常见用例:
- 手写识别
- 字符识别
- 物体检测
- 命名实体识别
- 基因预测
- 图像分割
- 语音标记的一部分
- 降噪
关于 CRF 的注意事项之一是它们是判别模型。 将其与生成模型 HMM 对比。
我们可以在标记的测量序列上定义条件概率分布。 我们将使用它来构建 CRF 模型。 在 HMM 中,我们定义了观察序列和标签上的联合分布。
CRF 的主要优势之一是它们本质上是有条件的。 HMM 并非如此。 CRF 不假定输出观测值之间有任何独立性。 HMM 假定任何给定时间的输出在统计上均独立于先前的数据点。 HMM 必须做出此假设,以确保推理过程以可靠的方式工作。 但是这个假设并不总是正确的。 现实世界的数据充满了时间依赖性。
在自然语言处理,语音识别,生物技术等各种应用中,CRF 往往优于 HMM。 在本节中,我们将讨论如何使用 CRF 分析和识别单词。
这是一个很好的用例,将突出显示识别数据中依存关系的能力。 英语单词中字母的顺序决不是随机的。 例如,考虑单词random
。 在第一个字母之后的下一个字母将成为元音的可能性高于其在辅音中的可能性。 单词中的第二个字母变为字母x
的可能性不为零。 我们可以想到几个符合此条件的单词-豁免,准确,展示等。 但是,考虑到第一个字母是r
,单词中第二个字母是x
的概率是多少? 我们想不出一个符合该标准的单词。 即使它们存在,也没有那么多,因此可能性较低。 CRF 利用这一事实。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制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
选择较低的值,则可以使模型更好地推广。 但这也意味着我们将对训练数据点的错误分类处以较低的罚款。
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 模型:
# 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)
定义一个函数来加载训练数据:
代码语言:javascript复制 # 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 模型:
代码语言:javascript复制 # Train the CRF
def train(self, X_train, y_train):
self.clf.fit(X_train, y_train)
定义一个函数来评估 CRF 模型的准确率:
代码语言:javascript复制 # Evaluate the accuracy of the CRF
def evaluate(self, X_test, y_test):
return self.clf.score(X_test, y_test)
定义一个函数以在未知数据点上运行经过训练的 CRF 模型:
代码语言:javascript复制 # Run the CRF on unknown data
def classify(self, input_data):
return self.clf.predict(input_data)[0]
定义一个函数以根据索引列表从字母中提取子字符串:
代码语言:javascript复制# Convert indices to alphabets
def convert_to_letters(indices):
# Create a numpy array of all alphabets
alphabets = np.array(list(string.ascii_lowercase))
提取字母:
代码语言:javascript复制 # Extract the letters based on input indices
output = np.take(alphabets, indices)
output = ''.join(output)
return output
定义主函数并解析输入参数:
代码语言:javascript复制if __name__=='__main__':
args = build_arg_parser().parse_args()
c_val = args.c_val
创建 CRF 模型对象:
代码语言:javascript复制 # Create the CRF model
crf = CRFModel(c_val)
加载输入数据并将其分为训练集和测试集:
代码语言:javascript复制 # 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 模型:
代码语言:javascript复制 # Train the CRF model
print('nTraining the CRF model...')
crf.train(X_train, y_train)
评估 CRF 模型的准确率并打印:
代码语言:javascript复制 # Evaluate the accuracy
score = crf.evaluate(X_test, y_test)
print('nAccuracy score =', str(round(score*100, 2)) '%')
在一些测试数据点上运行它并打印输出:
代码语言:javascript复制 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-0yBNGUwt-1681568818807)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_14.png)]
图 14:CRF 模型训练
如果滚动到末尾,还应该看到以下输出: :
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0gAq0SJn-1681568818807)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_15.png)]
图 15:原始输出与预测输出
如我们所见,可以正确预测中的大多数单词。 希望我们能够说明 CRF 的功能以及它们与 HMM 的区别。 在下一节中,我们将重新介绍 HMM,并在示例中应用它们来分析股票市场数据。
股市分析
我们将在本节中使用 HMM 分析股票市场数据。 这是一个已经组织好数据并加上时间戳的示例。 我们将使用matplotlib
包中的可用数据集。 数据集包含这些年来不同公司的股票价值。 HMM 是生成模型,可以分析此类时间序列数据并提取底层结构。 我们将使用此模型来分析股票价格变化并生成输出。
请不要指望此模型产生的结果将接近生产质量,并且您将能够使用此模型执行实时交易并从中获利。 它将为开始思考如何实现这一目标提供基础。 如果您愿意,我们建议您继续增强模型,并针对不同的数据集强调该模型,并可能将其与当前市场数据一起使用。 我们不对模型的盈利或不盈利做任何表述。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制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 日的历史股票市场报价。您可以自由选择任何日期范围:
代码语言:javascript复制# 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)
每天计算收盘报价的百分比差异:
代码语言:javascript复制# Take the percentage difference of closing stock prices
diff_percentages = 100.0 * np.diff(intc.Close) / intc.Close[:-1]
堆叠两个数据列以创建训练数据集:
代码语言:javascript复制# Stack the differences and volume values
# column-wise for training
training_data = np.column_stack([diff_percentages, intc.Volume[:-1]])
使用7
分量和对角协方差创建并训练高斯 HMM:
# 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
样本。 您可以选择生成任意数量的样本。
# Generate data using the HMM model
num_samples = 300
samples, _ = hmm.sample(num_samples)
绘制生成的差异百分比值:
代码语言:javascript复制# Plot the difference percentages
plt.figure()
plt.title('Difference percentages')
plt.plot(np.arange(num_samples), samples[:, 0], c='black')
绘制交易股票数量的生成值:
代码语言:javascript复制# 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-hCg7vGfQ-1681568818807)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_16.png)]
图 16:差异百分比
第二张屏幕截图显示了 HMM 为交易的股票数量生成的值:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7NxNzjS2-1681568818807)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_17_17.png)]
图 17:股票数量
我们将其留给阅读器,以针对数据集中的实际数据点计算 HMM 模型预测的值的准确率。 然后将需要一些工作来使用它来产生交易信号。 正如我们在本节开头提到的,我们不建议您使用此代码来使用真实货币进行实际交易。
总结
在本章中,我们学习了如何构建序列学习模型。 我们了解了如何处理 Pandas 中的时间序列数据。 我们讨论了如何分割时间序列数据并对其执行各种操作。 我们学习了如何以滚动方式从时间序列数据中提取各种统计信息。 我们了解了隐马尔可夫模型,然后实现了构建该模型的系统。
我们讨论了如何使用条件随机场来分析字母序列。 我们学习了如何使用各种技术来分析股票市场数据。 在下一章中,我们将继续学习如何在图像识别领域内实现 AI。
18 图像识别
在本章中,我们将学习有关对象检测和跟踪的知识。 首先,我们将花费一些时间来理解为什么图像识别对于机器学习非常重要。 然后,我们将学习称为 OpenCV 的图像识别包,该包是计算机视觉的流行库。 我们还将学习如何安装 OpenCV 并讨论帧差异,以了解如何检测视频中的运动部分。 我们将学习如何使用色彩空间跟踪对象,以及如何使用背景减法来跟踪对象。 之后,我们将使用 CAMShift 算法构建交互式对象跟踪器,并学习如何构建基于光流的跟踪器。 我们将讨论人脸检测和相关概念,例如 Haar 级联和积分图像。 然后,我们将使用此技术来构建眼睛检测器和跟踪器。
在本章结束时,您将了解:
- 安装 OpenCV
- 帧差分
- 使用色彩空间跟踪对象
- 使用背景减法跟踪对象
- 使用 CAMShift 算法构建交互式对象跟踪器
- 基于光流的跟踪
- 人脸检测和追踪
- 将 Haar 级联用于对象检测
- 将积分映射用于特征提取
- 眼睛检测和跟踪
我们将从介绍 OpenCV 开始,然后逐步介绍如何安装它。
图像识别的重要性
作为,希望从本书的主题中变得清晰起来,一般的人工智能,尤其是机器学习,是推动当今社会发生数字化转型的一些技术。 能够“看到”是人类学习过程的关键组成部分。 类似地,即使他们使用不同的方法“查看”,捕获图像并识别这些图像中包含的内容对于计算机来说也是最重要的,以便创建数据集以馈入机器学习管道并从该数据中获取洞察力。
无人驾驶技术就是一个明显的例子。 在这种情况下,计算机就像人类的同类计算机一样,需要能够在任何给定的每秒内提取千兆字节的数据,分析这些数据,并实时做出改变人生的决定。 当这项技术被广泛使用的那天,我感到非常兴奋。 我的估计是,这将尽早发生。 根据世界卫生组织的数据,2013 年道路交通事故死亡人数为 125 万人。部署自动驾驶汽车时,可以避免其中的很大比例。
无人驾驶技术只是图像识别的一种应用,其应用几乎是无限的,仅受我们的想象力限制。 其他一些流行的用途是:
自动图像分类:我们可以在 Google 相册中以及在将图像上传到 Facebook 以及查看 Facebook 如何向我们提供有关图像中人物的建议时看到的第一手示例。
反向图像搜索:Google 除其他功能外,还提供功能,您可以将图像用作输入,而不是使用关键字作为输入并获取图像,而 Google 可以猜测图片内容。 您可以在这里尝试。
光学字符识别:将图像转换为文本非常依赖于图像识别。
MRI 和超声解释:在识别癌症和其他疾病方面,某些工具的表现优于人类。
考虑了图像识别的一些实际应用后,让我们进入将要使用的包以亲自了解它。
OpenCV
在本章中,我们将使用名为 OpenCV 的包。 其名称暗示了 OpenCV(开源计算机视觉)是一个开源跨平台 Python 包,可用于启用实时计算机视觉。 该工具起源于英特尔实验室。
OpenCV 可以与 TensorFlow,PyTorch 和 Caffe 结合使用。
安装
在本章中,我们将使用名为 OpenCV 的包。 您可以在此处了解更多信息。 在继续操作之前,请确保已安装。 以下是在各种操作系统上使用 Python3 安装 OpenCV 3 的链接:
- Windows
- Ubuntu
- Mac
现在您已经安装了它,让我们转到下一部分,我们将讨论帧差异分段。
帧差分
帧差分是可用于识别视频中运动部分的最简单技术之一。 直观地,在大多数应用中,这是有趣的部分所在。 如果我们有一个跑步者的视频,我们可能想分析跑步者的跑步情况,而不是背景图像。 当我们看电影时,我们主要关注最前沿的人物在说话和做事时。 我们不会倾向于关注背景中无聊的相框。
有时候,您会发现一个一次性的极客,会在隐藏在这种背景下的电影中发现问题,正如我们在《权力的游戏》的最新剧情中几次看到,有人在背景中发现一杯星巴克,但这是例外而不是规则。
当我们观看实时视频流时,从该流捕获的连续帧之间的差异为我们提供了很多信息。 让我们看看如何获取连续帧之间的差异并显示差异。 本节中的代码需要连接的摄像头,因此请确保您的计算机上装有摄像头。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import cv2
定义一个函数来计算帧差。 首先计算当前帧和下一帧之间的差异:
代码语言:javascript复制# 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)
计算当前帧和上一帧之间的差异:
代码语言:javascript复制 # Difference between the current frame and the previous frame
diff_frames_2 = cv2.absdiff(cur_frame, prev_frame)
计算两个差异帧之间的按位与并返回:
代码语言:javascript复制 return cv2.bitwise_and(diff_frames_1, diff_frames_2)
定义一个函数以从网络摄像头抓取当前帧。 首先从视频捕获对象中读取它:
代码语言:javascript复制# 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()
根据缩放比例调整框架大小并返回:
代码语言:javascript复制 # Resize the image
frame = cv2.resize(frame, None, fx=scaling_factor,
fy=scaling_factor, interpolation=cv2.INTER_AREA)
将图像转换为灰度并返回:
代码语言:javascript复制 # Convert to grayscale
gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
return gray
定义main
函数并初始化视频捕获对象:
if __name__=='__main__':
# Define the video capture object
cap = cv2.VideoCapture(0)
定义比例因子以调整图像大小:
代码语言:javascript复制 # Define the scaling factor for the images
scaling_factor = 0.5
抓取当前的帧,下一帧以及之后的帧:
代码语言:javascript复制 # 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
键。 首先计算帧差异:
# 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
变量:
# Update the variables
prev_frame = cur_frame
cur_frame = next_frame
从网络摄像头抓取下一帧:
代码语言:javascript复制 # Grab the next frame
next_frame = get_frame(cap, scaling_factor)
检查用户是否按下了Esc
键。 如果是这样,请退出循环:
# Check if the user hit the 'Esc' key
key = cv2.waitKey(10)
if key == 27:
break
退出循环后,请确保所有窗口均已正确关闭:
代码语言:javascript复制 # Close all the windows
cv2.destroyAllWindows()
完整代码在提供给您的文件frame_diff.py
中给出。 如果运行代码,您将看到一个显示实时输出的输出窗口。 如果四处走动,您会在这里看到自己的轮廓在这里展示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bEd2rIlr-1681568818808)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_01.png)]
图 1:轮廓图像
前面的屏幕截图中的白线表示轮廓。 我们取得了什么成就? 为什么这很重要? 根据应用的不同,我们可能不需要原始图像提供的所有信息。 原始图像具有更多细节,更多对比度和更多颜色。 让我们以自动驾驶汽车为例。 我们可能不在乎我们前面的车是红色还是绿色。 我们更担心要知道汽车是否正在驶向我们并即将撞向我们。 通过过滤所有额外的不相关信息,它允许系统中的其他算法更有效地处理图像中的相关信息,因此可以更快地对任何潜在的危险做出反应。
这种过滤可能有用的另一个实例是,加载视频的空间有限或成本高昂,我们需要压缩图像并提高空间利用率。 我们将由读者提出其他可能有用的方案,但希望我们能给您足够的灵感,激发您的想象力和创造力。
通过帧差分获得的信息很有用,但是我们将无法使用它构建健壮的跟踪器。 它对噪声敏感,并且不能真正完全跟踪物体。 要构建健壮的对象跟踪器,我们需要知道可以使用对象的哪些特征来精确跟踪它。 这是色彩空间变得重要的地方,我们将在扩展部分中讨论。
使用颜色空间跟踪对象
可以使用各种色彩空间来表示图像。 RGB 颜色空间可能是最流行的颜色空间,但不适用于对象跟踪之类的应用。 因此,我们将改用 HSV 颜色空间。 这是一种直观的色彩空间模型,更接近于人类对色彩的感知方式。 您可以在此处了解更多信息。
我们可以将捕获的帧从 RGB 转换为 HSV 颜色空间,然后使用颜色阈值跟踪任何给定的对象。 我们应该注意,我们需要知道对象的颜色分布,以便为阈值选择合适的范围。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import cv2
import numpy as np
定义一个函数以从网络摄像头抓取当前帧。 首先从视频捕获对象中读取它:
代码语言:javascript复制# 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()
根据缩放比例调整框架大小并返回:
代码语言:javascript复制 # Resize the image
frame = cv2.resize(frame, None, fx=scaling_factor,
fy=scaling_factor, interpolation=cv2.INTER_AREA)
return frame
定义main
函数。 首先初始化视频捕获对象:
if __name__=='__main__':
# Define the video capture object
cap = cv2.VideoCapture(0)
定义用于调整捕获帧大小的比例因子:
代码语言:javascript复制 # Define the scaling factor for the images
scaling_factor = 0.5
无限期地进行迭代,直到用户按下Esc
键。 抓取当前帧开始:
# 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 颜色空间:
代码语言:javascript复制 # Convert the image to HSV colorspace
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
为人类皮肤的颜色定义近似 HSV 颜色范围:
代码语言:javascript复制 # Define range of skin color in HSV
lower = np.array([0, 70, 60])
upper = np.array([50, 150, 255])
阈值 HSV 图像以创建掩码:
代码语言:javascript复制 # Threshold the HSV image to get only skin color
mask = cv2.inRange(hsv, lower, upper)
计算遮罩和原始图像之间的按位与:
代码语言:javascript复制 # Bitwise-AND between the mask and original image
img_bitwise_and = cv2.bitwise_and(frame, frame, mask=mask)
运行中值模糊以使图像平滑:
代码语言:javascript复制 # Run median blurring
img_median_blurred = cv2.medianBlur(img_bitwise_and, 5)
显示输入和输出帧:
代码语言:javascript复制 # Display the input and output
cv2.imshow('Input', frame)
cv2.imshow('Output', img_median_blurred)
检查用户是否按下了Esc
键。 如果是这样,则退出循环:
# Check if the user hit the 'Esc' key
c = cv2.waitKey(5)
if c == 27:
break
退出循环后,请确保所有窗口均已正确关闭:
代码语言:javascript复制 # Close all the windows
cv2.destroyAllWindows()
提供给您的文件colorspaces.py
中提供了完整的代码。 如果运行代码,您将获得两个屏幕截图。 标题为输入的窗口是捕获的帧:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hm0VzIN1-1681568818808)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_02.png)]
图 2:捕获的帧
标题为输出的第二个窗口显示皮肤遮罩:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3DnrGKLo-1681568818808)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_03.png)]
图 3:输出框架
如您在中看到的输出帧,我们现在只在图像中看到一种颜色,它对应于任何皮肤。 其他一切都是黑色的。 与上一节中看到的类似,我们对图像进行了过滤,以仅包含我们感兴趣的信息。在这种情况下,过滤是不同的,但结果是事实是我们现在仅具有进一步处理图像所需的信息。 我想到了一些应用:
- 检测异常皮肤状况或变色。
- 仅在看到人的肤色时才会打开的安全系统。 这可用于人类可能藏在容器中的港口。
您还能想到其他一些应用吗? 在下一节中,我们将学习另一种称为背景减法的图像转换技术。
使用背景减法的对象跟踪
背景减法是一种技术,它对给定视频中的背景进行建模,然后使用该模型来检测运动对象。 该技术在视频压缩和视频监视中大量使用。 在必须检测静态场景中的移动物体的情况下,它的表现很好。 该算法的基本原理是检测背景,为其建立模型,然后从当前帧中减去背景以获得前景。 该前景对应于移动的对象。
这里的主要步骤之一是建立背景的模型。 它与帧差分不同,因为我们不差分连续帧。 我们正在对背景进行建模并实时更新,这使其成为一种自适应算法,可以适应不断变化的基线。 因此,它的表现要比帧差分好得多。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import cv2
import numpy as np
定义一个函数以获取当前帧:
代码语言:javascript复制# 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()
调整框架大小并返回:
代码语言:javascript复制 # Resize the image
frame = cv2.resize(frame, None, fx=scaling_factor,
fy=scaling_factor, interpolation=cv2.INTER_AREA)
return frame
定义main
函数并初始化视频捕获对象:
if __name__=='__main__':
# Define the video capture object
cap = cv2.VideoCapture(0)
定义背景减法器对象:
代码语言:javascript复制 # Define the background subtractor object
bg_subtractor = cv2.createBackgroundSubtractorMOG2()
定义历史记录和学习率。 关于history
的全部内容,以下注释不言自明:
# 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
键。 首先抓取当前帧:
# 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
:
# Compute the mask
mask = bg_subtractor.apply(frame, learningRate=learning_rate)
将mask
从灰度转换为 RGB:
# Convert grayscale image to RGB color image
mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
显示输入和输出图像:
代码语言:javascript复制 # Display the images
cv2.imshow('Input', frame)
cv2.imshow('Output', mask & frame)
检查用户是否按下了Esc
键。 如果是这样,请退出循环:
# Check if the user hit the 'Esc' key
c = cv2.waitKey(10)
if c == 27:
break
退出循环后,请确保释放视频捕获对象并正确关闭所有窗口:
代码语言:javascript复制 # Release the video capture object
cap.release()
# Close all the windows
cv2.destroyAllWindows()
完整代码在提供给您的文件background_subtraction.py
中给出。 如果运行代码,您将看到一个显示实时输出的窗口。 如果四处走动,您将部分看到自己,如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BxjcI5zi-1681568818808)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_04.png)]
图 4:背景减影
一旦停止移动,它就会开始褪色,因为您现在是背景的一部分。 该算法会将您视为背景的一部分,并开始相应地更新模型:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-neH7E4tG-1681568818809)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_05.png)]
图 5:背景减影
当您保持静止时,它将继续消失,如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yJM8lDxp-1681568818809)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_06.png)]
图 6:背景减影
淡入淡出的过程表示当前场景是,而背景模型是。
您已经可以想象到,仅在移动时才生成图像将节省大量存储空间。 一个简单的例子是使用安全摄像机。 看着一个小时或几个小时的镜头对准空旷的停车场,可能比看着油漆枯燥更无聊,但是如果安全系统足够智能,可以在车架发生运动时进行记录,我们将能够辨别出“有趣的东西”。 视频中的“”部分。
基于颜色空间的跟踪允许我们跟踪有色对象,但是我们必须首先定义颜色。 这似乎是限制性的! 让我们看看如何在实时视频中选择一个对象,然后使用一个可以跟踪它的跟踪器。 这是,在其中 CAMShift 算法(代表连续自适应均值漂移)变得很重要。 这基本上是 MeanShift 算法的自适应版本。 我们将在下一节讨论 CAMShift。
使用 CAMShift 算法构建交互式对象跟踪器
为了理解 CAMShift,首先让我们了解均值漂移的工作原理。 考虑给定帧中的感兴趣区域。 我们选择该区域是因为它包含感兴趣的对象。 我们要跟踪该对象,因此在其周围绘制了一个粗糙的边界,这就是感兴趣的区域所指的区域。 我们希望我们的对象跟踪器在视频中四处移动时跟踪该对象。
为此,我们基于该区域的颜色直方图选择一组点,然后计算质心。 如果此质心的位置在该区域的几何中心,则我们知道该对象没有移动。 但是,如果质心的位置不在此区域的几何中心,则我们知道对象已移动。 这意味着我们还需要移动封闭边界。 质心的运动直接指示物体的运动方向。 我们需要移动边界框,以使新质心成为此边界框的几何中心。 我们对每一帧都保持这种状态,并实时跟踪对象。 因此,此算法称为均值平移,因为均值(即质心)一直在移动,我们使用此跟踪对象。
让我们看看这与 CAMShift 有何关系。 均值平移的问题之一是不允许对象的大小随时间变化。 绘制边界框后,无论物体离相机有多近,它都将保持不变。 因此,我们需要使用 CAMShift,因为它可以使边界框的大小适应于对象的大小。 如果您想进一步探索,可以查看以下链接。
让我们看看如何构建跟踪器。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import cv2
import numpy as np
定义一个类来处理与对象跟踪有关的所有功能:
代码语言:javascript复制# 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)
捕获当前帧:
代码语言:javascript复制 # Capture the frame from the webcam
_, self.frame = self.cap.read()
设置比例因子:
代码语言:javascript复制 # Scaling factor for the captured frame
self.scaling_factor = scaling_factor
调整框架大小:
代码语言:javascript复制 # Resize the frame
self.frame = cv2.resize(self.frame, None,
fx=self.scaling_factor, fy=self.scaling_factor,
interpolation=cv2.INTER_AREA)
创建一个窗口以显示输出:
代码语言:javascript复制 # Create a window to display the frame
cv2.namedWindow('Object Tracker')
设置鼠标回调函数以从鼠标获取输入:
代码语言:javascript复制 # Set the mouse callback function to track the mouse
cv2.setMouseCallback('Object Tracker', self.mouse_event)
初始化变量以跟踪矩形选择:
代码语言:javascript复制 # 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
定义一个函数来跟踪鼠标事件:
代码语言:javascript复制 # 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])
当鼠标左键按下时,表明用户已经开始绘制矩形:
代码语言:javascript复制 # Check if a mouse button down event has occurred
if event == cv2.EVENT_LBUTTONDOWN:
self.drag_start = (x, y)
self.tracking_state = 0
如果用户当前正在拖动鼠标以设置矩形选区的大小,请跟踪宽度和高度:
代码语言:javascript复制 # 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
坐标的:
# Get the initial position
xi, yi = self.drag_start
获取坐标的最大值和最小值,以使其与拖动鼠标以绘制矩形的方向无关:
代码语言:javascript复制 # 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]))
重置选择变量:
代码语言:javascript复制 # Reset the selection variable
self.selection = None
完成矩形选择:
代码语言:javascript复制 # Finalize the rectangular selection
if x1-x0 > 0 and y1-y0 > 0:
self.selection = (x0, y0, x1, y1)
如果选择完成,则设置标志,指示我们应该开始跟踪矩形区域内的对象:
代码语言:javascript复制 else:
# If the selection is done, start tracking
self.drag_start = None
if self.selection is not None:
self.tracking_state = 1
定义跟踪对象的方法:
代码语言:javascript复制 # 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()
调整框架大小:
代码语言:javascript复制 # Resize the input frame
self.frame = cv2.resize(self.frame, None,
fx=self.scaling_factor, fy=self.scaling_factor,
interpolation=cv2.INTER_AREA)
创建框架的副本。 我们稍后将需要它:
代码语言:javascript复制 # Create a copy of the frame
vis = self.frame.copy()
将帧的颜色空间从 RGB 转换为 HSV:
代码语言:javascript复制 # Convert the frame to HSV colorspace
hsv = cv2.cvtColor(self.frame, cv2.COLOR_BGR2HSV)
根据预定义的阈值创建掩码:
代码语言:javascript复制 # Create the mask based on predefined thresholds
mask = cv2.inRange(hsv, np.array((0., 60., 32.)),
np.array((180., 255., 255.)))
检查用户是否选择了区域:
代码语言:javascript复制 # 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 图像以及遮罩中提取感兴趣的区域。 根据以下这些计算感兴趣区域的直方图:
代码语言:javascript复制 # 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] )
标准化直方图:
代码语言:javascript复制 # Normalize and reshape the histogram
cv2.normalize(hist, hist, 0, 255, cv2.NORM_MINMAX)
self.hist = hist.reshape(-1)
从原始帧中提取感兴趣的区域:
代码语言:javascript复制 # Extract the region of interest from the frame
vis_roi = vis[y0:y1, x0:x1]
计算感兴趣区域的按位非。 这仅用于显示目的:
代码语言:javascript复制 # Compute the image negative (for display only)
cv2.bitwise_not(vis_roi, vis_roi)
vis[mask == 0] = 0
检查系统是否处于跟踪模式:
代码语言:javascript复制 # Check if the system in the "tracking" mode
if self.tracking_state == 1:
# Reset the selection variable
self.selection = None
计算直方图的反向投影:
代码语言:javascript复制 # Compute the histogram back projection
hsv_backproj = cv2.calcBackProject([hsv], [0],
self.hist, [0, 180], 1)
计算直方图和掩码之间的按位与:
代码语言:javascript复制 # Compute bitwise AND between histogram
# backprojection and the mask
hsv_backproj &= mask
定义跟踪器的终止条件:
代码语言:javascript复制 # Define termination criteria for the tracker
term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)
将 CAMShift 算法应用于反投影直方图:
代码语言:javascript复制 # Apply CAMShift on 'hsv_backproj'
track_box, self.track_window = cv2.CamShift(hsv_backproj, self.track_window, term_crit)
在对象周围绘制一个椭圆并显示它:
代码语言:javascript复制 # 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
,则退出循环:
# Stop if the user hits the 'Esc' key
c = cv2.waitKey(5)
if c == 27:
break
退出循环后,请确保正确关闭所有窗口:
代码语言:javascript复制 # Close all the windows
cv2.destroyAllWindows()
定义main
函数并开始跟踪:
if __name__ == '__main__':
# Start the tracker
ObjectTracker().start_tracking()
完整代码在提供给您的文件camshift.py
中给出。 如果运行代码,您将看到一个窗口,显示来自网络摄像头的实时视频。
拿一个对象,将其握在手中,然后在其周围绘制一个矩形。 绘制矩形后,请确保将鼠标指针从最终位置移开。 该图像将如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yd1Yne08-1681568818809)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_07.png)]
图 7:物体检测图像
完成选择后,将鼠标指针移动到另一个位置以锁定矩形。 该事件将开始跟踪过程,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-365Ldw7J-1681568818809)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_08.png)]
图 8:物体检测图像 2
让我们将对象移动到并查看是否仍在跟踪中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ylcmADbV-1681568818809)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_09.png)]
图 9:物体检测图 3
看起来运作良好。 您可以移动物体,以查看如何实时对其进行跟踪。
希望到现在为止,您已经看到了图像识别的许多应用的可能性,并且可能已经提出了关于如何应用到目前为止所学知识的自己的想法。 他们使用的技术可能比我们在本章中使用的技术要复杂一些,但是概念并没有什么不同。 NFL 实际上使用这些技术在电视上设置 10 码标记,而美国职业棒球大联盟使用与我们在本节中学到的技术类似的技术来绘制打击区。 与我们在这里看到的最接近的示例是温网锦标赛用来确定网球着陆的位置并确定其进出的例子。 在下一节中,我们将学习基于光流的跟踪。 这是用于图像识别的有用技术。
基于光流的跟踪
2020 年 1 月,宣布由马丁·斯科塞斯(Martin Scorsese)执导的电影《爱尔兰人》被提名奥斯卡奖。 这部电影详细介绍了卡车司机,徒和队友弗兰克·希兰(Frank Sheeran)的生活(由罗伯特·德尼罗(Robert DeNiro)饰演)。 在电影中,我们看到希兰生活中的不同时期。 从他 20 多岁到 80 多岁。 整个过程中,我们在屏幕上看到 DeNiro 的时候很明显是他,你真的可以看出他是 20 岁还是 80 岁。
在以前的电影中,这可能是通过化妆实现的。 对于这部电影,没有为此目的使用化妆。 相反,他们使用特殊效果,并使用数字化妆修饰了 DeNiro 的脸。 太好了吧?
多年来,用计算机制作逼真的面孔非常困难,但好莱坞及其特效艺术家终于破解了密码。 显然,他们正在使用比本章将介绍的技术更复杂的技术,但是光流技术是开始实现此功能的基础技术。 您必须能够在视频移动的任何时刻跟踪该人的脸,然后才能在视频中更改该人的脸。 这是光流可以解决的问题之一。
光流是计算机视觉中使用的一种流行技术。 它使用图像特征点来跟踪对象。 在实时视频的连续帧中跟踪各个特征点。 当我们在给定帧中检测到一组特征点时,我们将计算位移向量以对其进行跟踪。 我们显示了连续帧之间这些特征点的运动。 这些向量被称为运动向量。 有许多方法可以执行光流,但是 Lucas-Kanade 方法可能是最受欢迎的方法。 这是描述此技术的原始论文。
第一步是从当前帧中提取特征点。 对于提取的每个特征点,将以特征点为中心创建一个3×3
的像素块。 我们假设每个面片中的所有点都具有相似的运动。 该窗口的大小可以根据情况进行调整。
对于每个补丁,我们在前一帧中在其附近寻找匹配项。 我们根据错误指标选择最佳匹配。 搜索区域大于3×3
,因为我们寻找一堆不同的3×3
色块以获得与当前色块最接近的那个。 一旦获得该信息,从当前补丁的中心点到前一帧中匹配的补丁的路径将成为运动向量。 类似地,我们为所有其他补丁计算运动向量。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import cv2
import numpy as np
定义一个函数以使用光流开始跟踪。 首先初始化视频捕获对象和缩放因子:
代码语言:javascript复制# 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
定义要跟踪的帧数和要跳过的帧数:
代码语言:javascript复制 # Number of frames to track
num_frames_to_track = 5
# Skipping factor
num_frames_jump = 2
初始化与跟踪路径和帧索引相关的变量:
代码语言:javascript复制 # Initialize variables
tracking_paths = []
frame_index = 0
定义跟踪参数,例如窗口大小,最大级别和终止条件:
代码语言:javascript复制 # Define tracking parameters
tracking_params = dict(winSize = (11, 11), maxLevel = 2,
criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,
10, 0.03))
无限期迭代,直到用户按下Esc
键。 首先捕获当前帧并调整其大小:
# 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 转换为灰度:
代码语言:javascript复制 # Convert to grayscale
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
创建框架的副本:
代码语言:javascript复制 # Create a copy of the frame
output_img = frame.copy()
检查跟踪路径的长度是否大于零:
代码语言:javascript复制 if len(tracking_paths) > 0:
# Get images
prev_img, current_img = prev_gray, frame_gray
组织特征点:
代码语言:javascript复制 # Organize the feature points
feature_points_0 = np.float32([tp[-1] for tp in
tracking_paths]).reshape(-1, 1, 2)
使用特征点和跟踪参数,根据先前图像和当前图像计算光流:
代码语言:javascript复制 # 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)
提取好的特征点:
代码语言:javascript复制 # Extract the good points
good_points = diff_feature_points < 1
初始化新跟踪路径的变量:
代码语言:javascript复制 # Initialize variable
new_tracking_paths = []
遍历所有好的特征点并在它们周围绘制圆圈:
代码语言:javascript复制 # 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
坐标,并且不要超过我们应该跟踪的帧数:
# 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)
围绕该点画一个圆。 更新跟踪路径并使用新的跟踪路径绘制线条以显示运动:
代码语言:javascript复制 # 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
条件:
# 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)
使用内置函数以及遮罩,最大拐角,质量级别,最小距离和块大小等参数,计算要跟踪的良好特征:
代码语言:javascript复制 # Compute good features to track
feature_points = cv2.goodFeaturesToTrack(frame_gray,
mask = mask, maxCorners = 500, qualityLevel = 0.3,
minDistance = 7, blockSize = 7)
如果特征点存在,请将其附加到跟踪路径:
代码语言:javascript复制 # 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)])
更新与帧索引和以前的灰度图像有关的变量:
代码语言:javascript复制 # Update variables
frame_index = 1
prev_gray = frame_gray
显示输出:
代码语言:javascript复制 # Display output
cv2.imshow('Optical Flow', output_img)
检查用户是否按下了Esc
键。 如果是这样,请退出循环:
# Check if the user hit the 'Esc' key
c = cv2.waitKey(1)
if c == 27:
break
定义main
函数并开始跟踪。 停止跟踪器后,请确保所有窗口均已正确关闭:
if __name__ == '__main__':
# Start the tracker
start_tracking()
# Close all the windows
cv2.destroyAllWindows()
完整代码在提供给您的文件optical_flow.py
中给出。 如果运行代码,您将看到一个显示实时视频的窗口。 您将看到特征点,如以下屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qn4xG1HT-1681568818810)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_10.png)]
图 10:对象跟踪图像
如果四处走动,将看到显示这些特征点移动的线:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x8BPQM4b-1681568818810)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_11.png)]
图 11:对象跟踪图像
如果您随后沿相反方向移动,则线也会相应地更改其方向:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tDseRjrl-1681568818810)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_12.png)]
图 12:对象跟踪图像
看到了的输出后,我们继续下一部分:“人脸检测和跟踪”。
人脸检测和追踪
人脸检测是指在给定图像中检测人脸的位置。 这通常与人脸识别相混淆,人脸识别是识别人物身份的过程。 典型的生物特征识别系统利用人脸检测和人脸识别两者来执行任务。 它使用人脸检测来定位人脸,然后使用人脸识别来识别人。 在本节中,我们将看到如何自动来自动检测实时视频中人脸的位置并对其进行跟踪。
使用 Haar 级联进行对象检测
我们将使用 Haar 级联来检测示例视频中的人脸。 在这种情况下,Haar 级联是指基于 Haar 特征的级联分类器。 Paul Viola 和 Michael Jones 于 2001 年在他们的标志性研究论文中首次提出了这种对象检测方法。您可以在这里查看。
他们在论文中描述了一种有效的机器学习技术,可用于检测任何物体。
他们使用简单分类器的增强级联。 该级联用于构建以高精度执行的整体分类器。 之所以相关,是因为它帮助我们规避了构建具有较高准确率的单步分类器的过程。 建立这样一个健壮的单步分类器是一个计算量大的过程。
考虑一个示例,在该示例中我们必须检测一个物体,例如网球。 为了构建检测器,我们需要一个可以了解网球外观的系统。 它应该能够推断出给定的图像是否包含网球。 我们需要使用很多网球图像来训练该系统。 我们还需要很多不包含网球的图像。 这有助于系统学习如何区分对象。
如果我们建立一个准确的模型,它将是复杂的。 因此,我们将无法实时运行它。 如果太简单,则可能不准确。 在机器学习领域中,经常会在速度和准确率之间进行权衡。 Viola-Jones 方法通过构建一组简单的分类器克服了这个问题。 然后将这些分类器级联到或健壮且准确的统一分类器中。
让我们看看如何使用它来执行人脸检测。 为了构建用于检测人脸的机器学习系统,我们首先需要构建特征提取器。 机器学习算法将使用这些特征来了解人脸。 这就是 Haar 特征变得相关的地方。
它们只是图像上补丁的简单总结和差异。 Haar 特征易于计算。 为了使其具有强大的缩放能力,我们在多种图像大小下执行此操作。 如果您想以教程格式了解更多信息,可以查看以下链接。
提取特征后,我们将其传递给简单分类器的增强级联。 我们检查图像中的各个矩形子区域,并继续丢弃不包含人脸的区域。 我们很高兴迅速得出最终答案。 为了高效地计算这些特征,他们使用了称为积分图像的概念。
使用积分图像进行特征提取
为了计算 Haar 特征,我们必须计算图像中许多子区域的总和和差。 我们需要在多个尺度上计算这些求和和差异,这使其成为计算密集型过程。 为了构建实时系统,我们使用积分图像。 考虑下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RPF2JXZX-1681568818810)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_13.png)]
图 13:ABCD 区域
如果要计算此图像中矩形ABCD
的总和,则无需遍历该矩形区域中的每个像素。 假设OC
表示由矩形的左上角O
和矩形的对角相对角点C
形成的矩形区域。 要计算矩形ABCD
的面积,我们可以使用以下公式:
矩形 ABCD 的面积 = OC – (OB OD – OA)
这个公式有何特别之处? 如果您注意到了,我们不必进行任何迭代或重新计算任何矩形区域。 等式右侧的所有值均已可用,因为它们是在较早的循环中计算的。 我们直接使用它们来计算此矩形的面积。 我们有效地做的是考虑一个较大的矩形,其中O
和C
代表相对的对角线,然后我们“切出”白色部分以仅留下蓝色区域。 考虑到这一点,让我们看看如何构建一个人脸检测器。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import cv2
import numpy as np
加载与人脸检测相对应的 Haar 级联文件:
代码语言:javascript复制# 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')
初始化视频捕获对象并定义缩放比例:
代码语言:javascript复制# Initialize the video capture object
cap = cv2.VideoCapture(0)
# Define the scaling factor
scaling_factor = 0.5
无限期地迭代,直到用户按下Esc
键。 捕获当前帧:
# Iterate until the user hits the 'Esc' key
while True:
# Capture the current frame
_, frame = cap.read()
调整框架大小:
代码语言:javascript复制 # Resize the frame
frame = cv2.resize(frame, None,
fx=scaling_factor, fy=scaling_factor,
interpolation=cv2.INTER_AREA)
将图像转换为灰度:
代码语言:javascript复制 # Convert to grayscale
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
在灰度图像上运行人脸检测器:
代码语言:javascript复制 # Run the face detector on the grayscale image
face_rects = face_cascade.detectMultiScale(gray, 1.3, 5)
遍历检测到的人脸并在其周围绘制矩形:
代码语言:javascript复制 # 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)
显示输出:
代码语言:javascript复制 # Display the output
cv2.imshow('Face Detector', frame)
检查用户是否按下了Esc
键。 如果是这样,请退出循环:
# Check if the user hit the 'Esc' key
c = cv2.waitKey(1)
if c == 27:
break
退出循环后,请确保释放视频捕获对象并正确关闭所有窗口:
代码语言:javascript复制# Release the video capture object
cap.release()
# Close all the windows
cv2.destroyAllWindows()
完整代码为提供给您的文件face_detector.py
中提供的。 如果运行代码,您将看到以下内容:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3cg6476r-1681568818811)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_14.png)]
图 14:人脸检测图像
从人脸检测,我们将在下一部分中转到类似的概念:“眼睛检测和跟踪”。
眼睛检测和跟踪
眼睛检测与人脸检测相似。 我们将使用眼睛级联文件,而不是使用人脸级联文件。 创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import cv2
import numpy as np
加载与人脸和眼睛检测相对应的 Haar 级联文件:
代码语言:javascript复制# 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')
初始化视频捕获对象并定义缩放比例:
代码语言:javascript复制# Initialize the video capture object
cap = cv2.VideoCapture(0)
# Define the scaling factor
ds_factor = 0.5
无限期重复,直到用户按下Esc
键:
# Iterate until the user hits the 'Esc' key
while True:
# Capture the current frame
_, frame = cap.read()
调整框架大小:
代码语言:javascript复制 # Resize the frame
frame = cv2.resize(frame, None, fx=ds_factor, fy=ds_factor, interpolation=cv2.INTER_AREA)
将帧从 RGB 转换为灰度:
代码语言:javascript复制 # Convert to grayscale
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
运行人脸检测器:
代码语言:javascript复制 # Run the face detector on the grayscale image
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
对于检测到的每个人脸,在该区域内运行眼睛检测器:
代码语言:javascript复制 # 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]
提取感兴趣的区域并运行眼睛检测器:
代码语言:javascript复制 # 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)
在眼睛周围绘制圆圈并显示输出:
代码语言:javascript复制 # 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
键,则退出循环:
# Check if the user hit the 'Esc' key
c = cv2.waitKey(1)
if c == 27:
break
退出循环后,请确保释放视频捕获对象并关闭所有窗口:
代码语言:javascript复制# Release the video capture object
cap.release()
# Close all the windows
cv2.destroyAllWindows()
完整代码在提供给您的文件eye_detector.py
中给出。 如果运行代码,您将看到类似以下内容:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-99rttC4N-1681568818811)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_18_15.png)]
图 15:眼睛检测图像
窃听上一节中的想法,我们可以使用在本节中学到的技术为电影(或胡须或胡须等)中的屏幕角色添加眼镜。
我想到的另一个应用是跟踪卡车驾驶员的眼睛,并确定他们眨眼或闭上眼睛的速度,以查看他们是否感到疲倦,并要求他们(可能是强迫他们)翻身。
本章旨在演示如何在各种应用中使用图像识别。 我们期待您的来信,并结合您自己的想法,学习如何应用本章中学到的技术。
总结
在本章中,我们学习了对象检测和跟踪。 我们了解了如何在各种操作系统上安装具有 Python 支持的 OpenCV。 我们了解了帧差异,并用它来检测视频中的运动部分。 我们讨论了如何使用色彩空间跟踪人类皮肤。 我们讨论了背景减法以及如何将其用于跟踪静态场景中的对象。 我们使用 CAMShift 算法构建了一个交互式对象跟踪器。
我们学习了如何构建基于光流的跟踪器。 我们讨论了人脸检测技术,并了解了 Haar 级联和积分图像的概念。 我们使用这种技术来构建眼睛检测器和跟踪器。
在下一章中,我们将讨论人工神经网络,并使用这些技术来构建光学字符识别引擎。
19 神经网络
在本章中,我们将学习神经网络。 我们将从神经网络的介绍和相关库的安装开始。 然后,我们将讨论感知器以及如何基于它们构建分类器。 之后,我们将更深入地学习单层神经网络和多层神经网络。
稍后,我们将看到如何使用神经网络构建向量量化器。 我们将使用循环神经网络分析序列数据,最后将使用神经网络构建光学字符识别引擎。 在本章的最后,我们将介绍:
- 神经网络简介
- 构建基于感知器的分类器
- 构建单层神经网络
- 构建多层神经网络
- 构建向量量化器
- 使用循环神经网络分析序列数据
- 在光学字符识别(OCR)数据库中可视化字符
- 构建光学字符识别(OCR)引擎
让我们开始介绍神经网络。
神经网络介绍
人工智能的基本前提之一是构建可以执行通常需要人类智能的任务的系统。 人脑在学习新概念方面非常了不起。 为什么不使用人脑模型来构建系统? 神经网络是一种旨在宽松地模拟人脑学习过程的模型。
神经网络的设计使其可以识别数据中的基本模式并从中学习。 它们可用于各种任务,例如分类,回归和细分。 神经网络的一个缺点是,在将给定数据输入神经网络之前,我们需要将其转换为数字格式。 例如,我们处理许多不同类型的数据,包括视觉,文本和时间序列。 我们需要弄清楚如何以神经网络可以理解的方式表示问题。 为了理解这一过程,让我们首先考虑如何构建神经网络,然后如何训练神经网络。
建立神经网络
人类学习过程的某些组成部分是分层的。 我们大脑神经网络中有多个部分,每个阶段对应不同的粒度。 有些部分学习简单的东西,有些部分学习更复杂的东西。 让我们考虑视觉上识别对象的示例。
当我们看着一个盒子时,大脑的第一部分可能会识别出简单的事物,例如角落和边缘。 下一部分将标识通用形状,其后的部分将标识其是哪种对象。 对于不同的大脑功能,此过程可能有所不同,但是您可以理解。 使用这种层次结构,人脑可以分离任务并识别给定的对象。
为了模拟人脑的学习过程,使用神经元层构建了神经网络。 这些神经元受到我们在上一段中讨论的生物神经元的启发。 神经网络中的每一层都是一组独立的输入神经元。 一层中的每个神经元都与相邻层中的神经元相连。
训练神经网络
如果我们要用N
维输入数据处理,则输入层将由N
个神经元组成。 如果我们在训练数据中具有M
个不同的类,则输出层将包含M
个神经元。 输入和输出层之间的层称为隐藏层。 一个简单的神经网络将由两层组成,而一个深度神经网络将由许多层组成。
那么如何使用神经网络对数据进行分类呢? 第一步是收集适当的训练数据并贴上标签。 每个神经元都充当简单函数,并且神经网络会自我训练,直到误差降至某个阈值以下。
误差是预测输出与实际输出之间的差。 基于误差有多大,神经网络会自行调整并重新训练,直到其更接近可解度。
足够抽象地思考神经网络。 就像我们在本书中一直在做的那样,是时候动手动手,边做边学。 在本章中,我们将使用名为 NeuroLab 的库。 NeuroLab 是库,它实现了基本的神经网络算法。 它具有各种参数,可以对其进行配置。 其界面类似于 MATLAB 中的神经网络工具箱(NNT)包。 该库是基于 NumPy 包的。 您可以在以下位置找到有关它的更多信息。
您可以通过在终端上运行以下命令来安装它:
代码语言:javascript复制$ pip3 install neurolab
安装后,您可以继续进行下一部分,在此我们将构建基于 Perceptron 的分类器。
构建基于感知器的分类器
神经元,树突和轴突构成了大脑的组成部分。 同样,感知器是神经网络中最基本的结构。
神经网络的发展经历了许多变化。 他们的发展基于圣地亚哥·拉蒙·卡哈尔和查尔斯·斯科特·谢灵顿爵士所做的神经系统工作。 拉蒙·卡哈尔(Ramon y Cajal)是探索神经组织结构的先驱,并证明:
- 神经元可以互相交流
- 神经元与其他神经元在物理上是分开的
利用 Ramony Cajal 和 Sherrington,Warren McCulloch 和 Walter Pitts 在 1943 年的论文中进行的研究,《神经活动固有的逻辑思想》描述了一种结构,该结构借鉴了神经元的结构,具有二进制阈值激活函数,类似于一阶逻辑语句。
以下是 McCulloch 和 Pitts 神经元的基本表示,也称为感知器:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ek6dKsnT-1681568818811)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_01.png)]
图 1:基本的感知器功能
因此,感知器是许多神经网络的基本构建块。 它接受输入,对其进行计算,然后产生输出。 它使用简单的线性函数进行决策。 假设我们正在处理N
维输入数据点。 感知器计算这些N
个数字的加权总和,然后添加一个常数以产生输出。 该常数称为神经元的偏差。 值得注意的是,这些简单的感知器可用于设计复杂的深度神经网络。
在本章中,我们将看到如何使用这种基本结构进行机器学习。 在后面的章节中,我们将看到更复杂的示例以及神经网络的一些有趣应用。 许多神经网络的核心,无论它们多么复杂,都利用了感知器的简单概念。 这就是为什么对这个主题有一个透彻的了解很重要的原因。 让我们看看如何使用 NeuroLab 构建基于感知器的分类器。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import numpy as np
import matplotlib.pyplot as plt
import neurolab as nl
从提供给您的文本文件data_perceptron.txt
中加载输入数据。 每行包含用空格分隔的数字,其中前两个数字是特征,最后一个数字是标签:
# Load input data
text = np.loadtxt('data_perceptron.txt')
将文本分为数据点和标签:
代码语言:javascript复制# Separate datapoints and labels
data = text[:, :2]
labels = text[:, 2].reshape((text.shape[0], 1))
绘制数据点:
代码语言:javascript复制# Plot input data
plt.figure()
plt.scatter(data[:,0], data[:,1])
plt.xlabel('Dimension 1')
plt.ylabel('Dimension 2')
plt.title('Input data')
定义每个大小可以采用的最大值和最小值:
代码语言:javascript复制# Define minimum and maximum values for each dimension
dim1_min, dim1_max, dim2_min, dim2_max = 0, 1, 0, 1
由于数据分为两类,我们只需要一位即可代表输出。 因此,输出层将包含单个神经元。
代码语言:javascript复制# Number of neurons in the output layer
num_output = labels.shape[1]
我们有一个数据集,其中的数据点是二维的。 让我们定义一个具有两个输入神经元的感知器,在其中为每个维度分配一个神经元。
代码语言:javascript复制# 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)
用训练数据训练感知器:
代码语言:javascript复制# Train the perceptron using the data
error_progress = perceptron.train(data, labels, epochs=100, show=20, lr=0.03)
使用误差度量绘制训练进度:
代码语言:javascript复制# 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-Qt7EV2B2-1681568818811)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_02.png)]
图 2:训练进度图
第二张屏幕截图使用误差指标表示训练进度:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZiIem3e7-1681568818812)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_03.png)]
图 3:训练误差图
我们可以从前面的屏幕截图中观察到,在第四个周期结束时,误差降至0
,这正是我们想要发生的情况。 如果误差为0
,则无法进一步改善。 在下一部分中,我们将增强模型并创建单层神经网络。
构建单层神经网络
建立一个带有几个感知器的模型是一个好的开始,它使我们对这个令人兴奋的概念有了基本的了解,但是要真正解决问题,这种简单的模型是不够的。 人脑大约有 850 亿个神经元。 我们不会建立具有这么多节点的神经网络,但是这个数字使您了解解决复杂问题所需的方法。 在建立具有数十亿个节点的模型之前,让我们进行下一步以建立具有单层的网络。 这个单层神经网络由作用于输入数据以产生输出的独立神经元组成。 让我们开始吧。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import numpy as np
import matplotlib.pyplot as plt
import neurolab as nl
我们将使用提供给您的文件data_simple_nn.txt
中的输入数据。 该文件中的每一行都包含四个数字。 前两个数字构成数据点,后两个数字是标签。 为什么我们需要为标签分配两个数字? 因为我们在数据集中有四个不同的类,所以我们需要两位来表示它们。 让我们继续加载数据:
# Load input data
text = np.loadtxt('data_simple_nn.txt')
将数据分为数据点和标签:
代码语言:javascript复制# Separate it into datapoints and labels
data = text[:, 0:2]
labels = text[:, 2:]
绘制输入数据:
代码语言:javascript复制# Plot input data
plt.figure()
plt.scatter(data[:,0], data[:,1])
plt.xlabel('Dimension 1')
plt.ylabel('Dimension 2')
plt.title('Input data')
提取每个维度的最小值和最大值(我们不需要像上一节中那样对其进行硬编码):
代码语言:javascript复制# 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()
定义输出层中的神经元数量:
代码语言:javascript复制# Define the number of neurons in the output layer
num_output = labels.shape[1]
使用以上参数定义单层神经网络:
代码语言:javascript复制# Define a single-layer neural network
dim1 = [dim1_min, dim1_max]
dim2 = [dim2_min, dim2_max]
nn = nl.net.newp([dim1, dim2], num_output)
使用训练数据训练神经网络:
代码语言:javascript复制# Train the neural network
error_progress = nn.train(data, labels, epochs=100, show=20, lr=0.03)
绘制训练进度:
代码语言:javascript复制# 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()
定义一些样本测试数据点,并在这些点上运行网络:
代码语言:javascript复制# 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-g1dKixmh-1681568818812)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_04.png)]
图 4:数据点图
第二张屏幕截图显示了训练进度:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mvzf7L6o-1681568818812)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_05.png)]
图 5:训练进度图
关闭图形后,您将看到以下输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rosYawos-1681568818812)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_06.png)]
图 6:训练周期
正如我们在“图 5”中看到的那样,误差迅速开始减少,这表明我们的训练有效地创造了越来越好的预测。 在这种情况下,误差不会降为零。 但是如果我们让模型再运行几个周期,我们预计误差将继续减少。 如果将这些测试数据点定位在 2D 图形上,则可以直观地验证预测输出的正确性。
构建多层神经网络
因此,我们将模型从几个节点增强到了一个单层,但距离 850 亿个节点还差得很远。 我们也不会在本节中谈到这一点,但让我们朝着正确的方向迈出又一步。 人脑不使用单层模型。 一些神经元的输出变成其他神经元的输入,依此类推。 具有这种特征的模型被称为多层神经网络。 这种类型的架构可产生更高的精度,并且使我们能够解决更复杂,更多样化的问题。 让我们看看如何使用 NeuroLab 构建多层神经网络。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import numpy as np
import matplotlib.pyplot as plt
import neurolab as nl
在前两节中,我们看到了如何使用神经网络作为分类器。 在本节中,我们将看到如何使用多层神经网络作为回归器。 根据公式y = 3x ^ 2 5
生成一些样本数据点,然后将这些点归一化:
# 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)
重塑前面的变量以创建训练数据集:
代码语言:javascript复制# Create data and labels
data = x.reshape(num_points, 1)
labels = y.reshape(num_points, 1)
绘制输入数据:
代码语言:javascript复制# Plot input data
plt.figure()
plt.scatter(data, labels)
plt.xlabel('Dimension 1')
plt.ylabel('Dimension 2')
plt.title('Input data')
定义具有两个隐藏层的多层神经网络。 您可以根据需要自由设计神经网络。 对于这种情况,我们在第一层使用10
神经元,在第二层使用6
神经元。 我们的任务是预测值,因此输出层将包含单个神经元:
# 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])
将训练算法设置为梯度下降:
代码语言:javascript复制# Set the training algorithm to gradient descent
nn.trainf = nl.train.train_gd
使用生成的训练数据训练神经网络:
代码语言:javascript复制# Train the neural network
error_progress = nn.train(data, labels, epochs=2000, show=100, goal=0.01)
在训练数据点上运行神经网络:
代码语言:javascript复制# Run the neural network on training datapoints
output = nn.sim(data)
y_pred = output.reshape(num_points)
绘制训练进度:
代码语言:javascript复制# Plot training error
plt.figure()
plt.plot(error_progress)
plt.xlabel('Number of epochs')
plt.ylabel('Error')
plt.title('Training error progress')
绘制预测输出:
代码语言:javascript复制# 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-xcTRG9ec-1681568818812)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_07.png)]
图 7:输入数据图
第二张屏幕截图显示了训练进度:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y67V1cRM-1681568818813)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_08.png)]
图 8:训练进度图
第三个屏幕截图显示了覆盖在输入数据之上的预测输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wW2c1Ipm-1681568818813)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_09.png)]
图 9:覆盖输入数据的输出图
预测的输出似乎在一定程度上接近实际输入。 如果继续训练网络并减少误差,您将看到预测输出将与输入曲线相匹配,即使重新仍很准确。
您还应该看到打印出以下内容:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bWb1vqUc-1681568818813)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_10.png)]
图 10:训练周期
在前面的部分中,我们学习了如何构建基本的神经网络以及对基础知识的牢固掌握和理解。 在下一节中,我们将继续学习如何构建神经网络。 现在,我们将学习如何使用向量量化器构建神经网络。
建立向量量化器
向量量化是一种量化技术,其中输入数据由固定数量的代表点表示。 它是N
维的四舍五入数字。 此技术通常用于多个领域,例如语音/图像识别,语义分析和图像/语音压缩。 最佳向量量化理论的历史可以追溯到 1950 年代的贝尔实验室,在那里进行了研究以使用离散化程序优化信号传输。 向量量化器神经网络的一个优点是它们具有很高的解释性。 让我们看看如何构建向量。
由于当前版本的 NeuroLab(v0.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 文件并导入以下包:
代码语言:javascript复制import numpy as np
import matplotlib.pyplot as plt
import neurolab as nl
从文件data_vector_quantization.txt
加载输入数据。 该文件中的每一行都包含六个数字。 前两个数字形成数据点,后四个数字形成单热编码标签。 总体上有四个类别。
# Load input data
text = np.loadtxt('data_vector_quantization.txt')
将文本分为数据和标签:
代码语言:javascript复制# Separate it into data and labels
data = text[:, 0:2]
labels = text[:, 2:]
定义一个具有两层的神经网络,其中输入层有10
神经元,输出层有4
神经元:
# 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)
使用训练数据训练神经网络:
代码语言:javascript复制# Train the neural network
_ = nn.train(data, labels, epochs=500, goal=-1)
为了可视化输出集群,让我们创建一个点网格:
代码语言:javascript复制# 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)
使用神经网络求解点的网格:
代码语言:javascript复制# Evaluate the input grid of points
grid_eval = nn.sim(grid_xy)
提取四个类:
代码语言:javascript复制# 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]
提取与这四个类相对应的网格:
代码语言:javascript复制# 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]
绘制输出:
代码语言:javascript复制# 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-GXUGrmM6-1681568818813)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_11.png)]
图 11:输入数据点和集群之间边界的图
您还应该看到以下输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vUVO3Vg0-1681568818814)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_12.png)]
图 12:训练周期
在上一节中,我们学习了如何使用向量量化器构建神经网络。 在下一节中,我们将继续学习神经网络。 接下来,我们将学习如何使用循环神经网络(RNN)分析序列数据。
使用循环神经网络分析序列数据
到目前为止,在我们所有的神经网络示例中,一直在使用静态数据。 神经网络也可以有效地用于构建处理序列数据的模型。 循环神经网络(RNN)在建模序列数据方面非常出色。 您可以在以下位置了解有关循环神经网络的更多信息。
当我们使用时间序列数据时,我们通常不能使用通用学习模型。 我们需要捕获数据中的时间依赖性,以便可以构建健壮的模型。 让我们看看如何构建它。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import numpy as np
import matplotlib.pyplot as plt
import neurolab as nl
定义一个函数来生成波形。 首先定义四个正弦波:
代码语言:javascript复制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))
为整个波形创建变化的幅度:
代码语言:javascript复制 # 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)
创建整体波形:
代码语言:javascript复制 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
定义一个函数以可视化神经网络的输出:
代码语言:javascript复制# 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
函数并创建一个波形:
if __name__=='__main__':
# Create some sample data
num_points = 40
wave, amp = get_data(num_points)
创建一个具有两层的循环神经网络:
代码语言:javascript复制 # Create a recurrent neural network with 2 layers
nn = nl.net.newelm([[-2, 2]], [10, 1], [nl.trans.TanSig(), nl.trans.PureLin()])
为每层设置初始化函数:
代码语言:javascript复制 # 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()
训练神经网络:
代码语言:javascript复制 # Train the recurrent neural network
error_progress = nn.train(wave, amp, epochs=1200, show=100, goal=0.01)
通过网络运行数据:
代码语言:javascript复制 # Run the training data through the network
output = nn.sim(wave)
绘制输出:
代码语言:javascript复制 # 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'])
在未知的测试数据上测试神经网络的表现:
代码语言:javascript复制 # 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-NyQEB8DP-1681568818814)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_13.png)]
图 13:输出波形叠加在输入波形上方
以下屏幕截图的上半部分显示了即使我们增加了波形的长度,神经网络也如何模拟波形。 屏幕截图的下半部分显示与减少长度相同:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PwqTa7KJ-1681568818814)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_14.png)]
图 14:波形仿真图
您还应该看到以下输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CeuUXZZA-1681568818814)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_15.png)]
图 15:训练周期
如您所见,误差持续减小,直到达到训练次数的最大值。 到此结束了本节,我们展示了如何使用 RNN 来分析时间序列数据。 在下一部分中,我们将通过研究光学字符识别来演示神经网络的实际应用。
可视化光学字符识别数据库中的字符
神经网络可以将用于光学字符识别。 它可能是其最常见的用例之一。 将手写体转换为计算机字符一直是许多计算机科学家试图解决的基本问题,但仍然难以捉摸。 我们已经取得了长足的进步,但是,由于显而易见的原因,100% 的准确率仍然遥不可及。 为什么?
考虑这种情况。 您曾经写下任何东西吗?五分钟后,您无法阅读自己的笔迹? 计算机也总是会出现此问题。 写下数字6
的方法有无数种,其中有些看起来比6
更像0
或5
。 我可能是错的,但是我认为我们将找到一种治愈癌症的方法,然后才能找到一种可靠的方法来使计算机识别医生的笔迹。 我们已经可以达到很高的准确率,并且笔迹越漂亮,阅读起来就越容易。 我们继续尝试解决此问题的原因是,这是一个有价值的目标,具有许多应用。 举一个简短的例子,医生的时间受到高度重视。 随着系统能够更好地识别他们的笔记,他们将获得更多的精力来专注于实际治疗和帮助患者的精力,而不再关注文书工作。
光学字符识别(OCR)是识别图像中手写字符的过程。 在构建模型之前,让我们使熟悉数据集。 我们将使用以下位置提供的数据集。
您将下载一个名为letter.data
的文件。 为了方便起见,此文件已在代码包中提供给您。 让我们看看如何加载数据并形象化角色。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import os
import sys
import cv2
import numpy as np
定义包含 OCR 数据的输入文件:
代码语言:javascript复制# Define the input file
input_file = 'letter.data'
定义从该文件加载数据所需的可视化效果和其他参数:
代码语言:javascript复制# Define the visualization parameters
img_resize_factor = 12
start = 6
end = -1
height, width = 16, 8
遍历该文件的各行,直到用户按下Esc
键。 该文件中的每一行都用制表符分隔。 阅读每一行并将其放大到255
:
# 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]])
将一维数组重塑为二维图像:
代码语言:javascript复制 # Reshape the data into a 2D image
img = np.reshape(data, (height, width))
缩放图像以进行可视化:
代码语言:javascript复制 # Scale the image
img_scaled = cv2.resize(img, None, fx=img_resize_factor, fy=img_resize_factor)
显示图像:
代码语言:javascript复制 # Display the image
cv2.imshow('Image', img_scaled)
检查用户是否按下了Esc
键。 如果是,请退出循环:
# Check if the user pressed the Esc key
c = cv2.waitKey()
if c == 27:
break
完整代码在文件character_visualizer.py
中给出。 如果运行代码,您将获得显示字符的输出屏幕截图。 您可以按住空格键查看更多字符。 例如,o
可能看起来像这样:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wbldPbza-1681568818814)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_16.png)]
图 16:字母 O 的图
和i
可能看起来像这样:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uvvTucdx-1681568818815)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_17.png)]
图 17:字母 I 的图
到目前为止,我们还没有识别出任何字符。 我们刚刚想出了一种可视化数据集并验证我们的模型正在做出准确预测的方法。 我们将在下一部分中进行构建。
构建光学字符识别引擎
现在我们已经学习了如何处理这些数据,让我们使用神经网络构建光学字符识别系统。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import numpy as np
import neurolab as nl
定义输入文件:
代码语言:javascript复制# Define the input file
input_file = 'letter.data'
定义将要加载的数据点的数量:
代码语言:javascript复制# Define the number of datapoints to
# be loaded from the input file
num_datapoints = 50
定义包含所有不同字符的字符串:
代码语言:javascript复制# String containing all the distinct characters
orig_labels = 'omandig'
提取不同类的数量:
代码语言:javascript复制# Compute the number of distinct characters
num_orig_labels = len(orig_labels)
定义训练和测试拆分。 我们将使用 90% 的训练和 10% 的测试:
代码语言:javascript复制# Define the training and testing parameters
num_train = int(0.9 * num_datapoints)
num_test = num_datapoints - num_train
定义数据集提取参数:
代码语言:javascript复制# Define the dataset extraction parameters
start = 6
end = -1
创建数据集:
代码语言:javascript复制# 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')
如果标签不是我们列表中的,则应跳过该标签:
代码语言:javascript复制 # 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
提取当前标签并将其附加到主列表中:
代码语言:javascript复制 # 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)
提取字符向量并将其附加到主列表中:
代码语言:javascript复制 # 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)
创建数据集后,退出循环:
代码语言:javascript复制 # Exit the loop once the required dataset has been created
if len(data) >= num_datapoints:
break
将列表转换为 NumPy 数组:
代码语言:javascript复制# Convert the data and labels to numpy arrays
data = np.asfarray(data)
labels = np.array(labels).reshape(num_datapoints, num_orig_labels)
提取维数:
代码语言:javascript复制# Extract the number of dimensions
num_dims = len(data[0])
创建一个前馈神经网络并将训练算法设置为梯度下降:
代码语言:javascript复制# 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
训练神经网络:
代码语言:javascript复制# Train the network
error_progress = nn.train(data[:num_train,:], labels[:num_train,:],
epochs=10000, show=100, goal=0.01)
预测测试数据的输出:
代码语言:javascript复制# 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-TxoV75aa-1681568818815)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_18.png)]
图 18:训练周期
它将持续进行直到 10,000 个周期。 完成后,您应该看到以下输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7KdkFZM7-1681568818815)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_19_19.png)]
图 19:训练周期
正如我们在前面的屏幕截图中看到的,我们的模型正确地选择了其中的三个。 如果使用更大的数据集并训练更长的时间,则应该获得更高的准确率。 我们让您看看它们是否可以通过更长的网络训练和调整模型的配置来获得更高的准确率和更好的结果。
希望本章使您对 OCR 特别是神经网络感到兴奋。 在随后的章节中,我们将回顾该技术的许多其他用例,这些用例处于当前机器学习革命的最前沿。
总结
在本章中,我们学习了神经网络。 我们讨论了如何构建和训练神经网络。 我们讨论了感知器,并在此基础上构建了分类器。 我们了解了单层神经网络以及多层神经网络。 我们讨论了如何将神经网络用于构建向量量化器。 我们使用循环神经网络分析了序列数据。 然后,我们使用神经网络构建了光学字符识别引擎。 在下一章中,我们将学习强化学习,并了解如何构建智能学习智能体。
20 将卷积神经网络用于深度学习
在本章中,我们将学习深度学习和卷积神经网络(CNN)。 在过去的几年中,CNN 取得了很大的发展势头,尤其是在图像识别领域。 我们将讨论 CNN 的架构以及内部使用的层的类型。 我们将看到如何使用一个名为 TensorFlow 的包。 我们将构建一个基于感知器的线性回归器。 我们将学习如何使用单层神经网络构建图像分类器。
然后,我们将使用 CNN 构建图像分类器。 图像分类器有许多应用。 这是一个奇特的名字,但这只是计算机辨别对象是什么的能力。 例如,您可以构建一个分类器来确定某物是热狗还是非热狗。 这是一个轻松的示例,但是图像分类器也可以使用生死攸关的应用。 为嵌入了图像分类软件的无人机拍照,它可以区分平民和敌方战斗人员。 在这种情况下不能犯任何错误。
本章涵盖以下主题:
- CNN 的基础
- CNN 的架构
- CNN 中的层类型
- 构建基于感知器的线性回归器
- 使用单层神经网络构建图像分类器
- 使用 CNN 构建图像分类器
让我们开始学习基础知识。
卷积神经网络的基础
总体而言,CNN,尤其是生成对抗网络(尤其是 GAN),已经成为新闻。 GAN 是 Ian Goodfellow 及其同事于 2014 年最初开发的一类 CNN。在 GAN 中,两个神经网络在游戏中相互竞争(从博弈论的角度)。 给定一个数据集,GAN 学习创建类似于训练集的新数据示例。 例如,速度可能会有些慢,但是有一个网站会产生不存在的人的面孔。
我们将让您的想象力疯狂起来,但是使用其中一些生成的“人类”在电影中出演肯定可以制作一部电影。 还有其他研究试图解决这一问题。 给定一个图像,我们可以确定它是 GAN 生成的图像还是真实的人? 您可以在此处浏览该网站。
要使用它,只需继续刷新页面,它将每次生成一个新图像。 GAN 最初是作为无监督学习的生成模型而创建的。 GAN 还被证明可用于半监督学习,监督学习和强化学习。 AI 的巨头之一 Yann LeCun 称 GAN 是 ML 中最近十年中最有趣的想法[1]。 让我们考虑 GAN 的其他一些用例和应用。
使用 GAN 生成更多示例数据:数据是 ML 中的组成部分。 在某些情况下,不可能获得足够的数据来馈送到模型。 使用 GAN 生成更多输入数据是生成附加质量数据以馈入模型的好方法。
安全性:ML 为许多行业提供了提升。 无论市场部门如何,网络安全始终是企业高管的“首要任务”。 某些安全供应商使用 GAN 来处理网络攻击。 简而言之,GAN 会创建伪造的入侵,然后使用这些入侵来训练模型以识别这些威胁,从而使我们能够阻止这些攻击的真实版本。
数据操作:GAN 可用于“伪样式迁移”,即,在不完全修改示例的情况下修改示例的某些大小。
GAN 可用于语音应用。 给定语音,可以训练 GAN 来重现著名的声音。
在一些著名的例子中,使用 GAN 修改了 Trump,Obama 或 Mona Lisa 的视频,并且开发人员使这些数字说出了他们从未说过的短语。 他们可能是很现实的。 或者可以将视频或图像更改为看起来像不同的人。 这是在麻省理工学院创建的唐纳德·特朗普总统形象之上插入尼古拉斯·凯奇的脸的例子:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RT43HQu0-1681568818815)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_20_01.png)]
图 1:尼古拉斯·凯奇(Nicolas Cage)担任唐纳德·特朗普(Donald Trump)总统
可以将这些技术移植到其他领域,例如自然语言处理,语音处理等。 例如,GAN 可能会略微调整句子,从而改变句子的含义。
隐私:作为其安全策略的的一部分,许多公司希望将某些数据保密和保密。 显而易见的例子是国防和军事应用。 可以在加密数据时使用 GAN。 例如,生成一次性的密钥。
为了更好地利用 GAN,谷歌于 2016 年开始进行研究。 基本思想是让一个网络创建一个密钥,另一个网络试图破解它。
在前两章中,我们看到了神经网络是如何工作的。 神经网络由具有权重和偏置的神经元组成。 在训练过程中会调整这些权重和偏置,以得出一个好的学习模型。 每个神经元接收一组输入,以某种方式对其进行处理,然后输出值。
如果我们构建具有多层的神经网络,则称为深度神经网络。 AI 处理这些深度神经网络的分支称为深度学习。
普通神经网络的主要缺点之一是它们忽略了输入数据的结构。 在将所有数据馈入网络之前,所有数据都将转换为一维数组。 这可能适用于数字数据,但是当我们处理图像时会变得困难。
让我们考虑灰度图像。 这些图像是 2D 结构,我们知道像素的空间排列具有很多隐藏信息。 如果我们忽略这些信息,我们将失去很多潜在的模式。 这是 CNN 出现的地方。 CNN 处理图像时会考虑图像的 2D 结构。
CNN 也由神经元组成,这些神经元由权重和偏差组成。 这些神经元接受输入数据,对其进行处理,然后输出某些数据。 网络的目标是从输入层中的原始图像数据转到输出层中的正确类别。 普通神经网络和 CNN 之间的区别在于我们使用的层类型以及我们如何处理输入数据。 CNN 假定输入是图像,这使它们可以提取特定于图像的属性。 这使 CNN 可以更有效地处理图像。 现在我们已经了解了 CNN 的基础知识,下面让我们看一下它们是如何成立的。
CNN 的架构
当我们在普通神经网络中使用时,我们需要将输入数据转换为单个向量。 此向量充当神经网络的输入,然后通过神经网络的各层。 在这些层中,每个神经元都连接到上一层中的所有神经元。 还值得注意的是,每一层中的神经元彼此之间没有连接。 它们仅连接到相邻层中的神经元。 网络中的最后一层是输出层,它代表最终输出。
如果我们将这种结构用于图像,它将很快变得难以管理。 例如,让我们考虑由256×256
RGB 图像组成的图像数据集。 由于这些是 3 通道图像,因此权重为256 * 256 * 3 = 196,608
。 请注意,这仅适用于单个神经元! 每层将有多个神经元,因此权重数趋于迅速增加。 这意味着模型现在将具有在训练过程中要调整的大量参数。 因此,它很快变得非常复杂且耗时。 将每个神经元连接到上一层中的每个神经元(称为完全连通性)显然不会起作用。
CNN 在处理数据时会明确考虑图像的结构。 CNN 中的神经元按 3 个维度排列-宽度,高度和深度。 当前层中的每个神经元都连接到前一层输出的一小块。 就像在输入图像上叠加NxN
过滤器一样。 这与完全连接层形成对比,在完全连接层中,每个神经元都连接到上一层的所有神经元。
由于单个过滤器无法捕获图像的所有细微差别,因此我们多次执行M
,以确保捕获所有细节。 这些M
过滤器充当特征提取器。 如果查看这些过滤器的输出,我们可以看到它们提取了诸如边,角等特征。 对于 CNN 中的初始层来说,这是正确的。 随着我们在网络的层中的进展,我们将看到后面的层提取更高级别的特征。
CNN 是深度学习网络。 它通常用于识别图像。 了解它如何识别图像将有助于您了解它们的工作原理。 像任何其他神经网络一样,它为图像中的元素分配权重和偏差,并能够将这些元素彼此区分开。 与其他分类模型相比,CNN 中使用的预处理较少。 当使用经过足够训练的原始方法过滤器时,可以训练 CNN 来区分这些过滤器和特征。
CNN 架构的基本形式可以与人脑中的神经元和树突进行比较,并从视觉皮层中汲取灵感。 单个神经元对视野受限区域的刺激作出反应。 该区域称为接受场。 这些视场的一组彼此重叠,因此覆盖了整个视场。
CNN 与感知器神经网络
图像是像素值矩阵。 为什么我们不能仅将输入图像展开? 例如,可以将7x7
图像展开为49x1
向量。 然后,我们可以将这个扁平化的图像用作基于感知器的神经网络的输入。
当使用基本的二进制(黑白)输入时,此方法在执行类的预测时可能会显示平均精度得分,但对于涉及像素始终的复杂图像,该方法几乎没有准确率。
让我们对此进行分析,以通过思考人类如何处理图像来获得一些理解。 考虑一个包含菱形的图像:♦。 我们的大脑可以立即处理图像,并意识到它是菱形:♦。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bshS469L-1681568818816)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_20_02.png)]
图 2:菱形
如果将其展开会怎样?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2d0i50wO-1681568818816)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_20_03.png)]
图 3:菱形但变平
并非很容易识别,不是吗? 尽管如此,它是相同的信息。 当使用传统的神经网络而不是 CNN 时,也会发生类似的情况。 现在,当像素连续时,我们拥有的信息将丢失。
CNN 可以通过应用相关过滤器来捕获图像中的空间和时间相关性。 CNN 架构在数据集上表现更好,这是因为参数数量减少了,并且权重得到了重用。
现在,我们对 CNN 架构和图像的处理方式有了更好的了解,让我们考虑一下包含 CNN 的层。
CNN 中的层类型
CNN 通常使用以下类型的层:
输入层:此层直接获取原始图像数据。
卷积层:此层计算神经元与输入中各种贴片之间的卷积。 如果您需要快速了解图像卷积,可以查看以下链接。
卷积层基本上计算权重和前一层输出中的一个小补丁之间的点积。
整流线性单元层:此层将激活函数应用于上一层的输出。 此函数通常类似于max(0, x)
。 需要这一层来为网络增加非线性,以便可以很好地推广到任何类型的函数。
合并层:此层对上一层的输出进行采样,从而得到具有较小大小的结构。 池化有助于我们在网络发展过程中仅保留重要部分。 最大池化通常在池化层中使用,我们在给定的KxK
窗口中选择最大值。
全连接层:此层计算最后一层的输出分数。 结果输出的大小为1x1xL
,其中L
是训练数据集中的类数。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HWFkVzSy-1681568818816)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_20_04.png)]
图 4:CNN 层
当我们从网络中的输入层到输出层时,输入图像就从像素值转换为最终的类别分数。 已经为 CNN 提出了许多不同的架构,这是一个活跃的研究领域。 模型的准确率和鲁棒性取决于许多因素-层的类型,网络的深度,网络内各种类型的层的排列,为每个层选择的函数,训练数据,等等。
基于感知器建立线性回归器
在建立 CNN 之前,让我们为基础建立一个更基本的模型,并了解如何使用 CNN 进行改进。 在本节中,我们将看到如何使用感知器构建线性回归模型。 在前面的章节中我们已经看到了线性回归,但是本节是关于使用神经网络方法构建线性回归模型的。
我们将在本章中使用 TensorFlow。 这是一个流行的深度学习包,已广泛用于构建各种实际系统。 在本节中,我们将熟悉其工作原理。 在继续操作之前,请确保已安装它。 可以在这里找到安装说明。
确认已安装后,创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
我们将生成一些数据点,并查看如何使用这些数据点训练模型。
定义要生成的数据点数:
代码语言:javascript复制# Define the number of points to generate
num_points = 1200
定义将用于生成数据的参数。 我们将使用线的模型:y = mx c
:
# 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)
产生一些噪音以增加数据的差异:
代码语言:javascript复制 # Generate some noise
noise = np.random.normal(0.0, 0.04)
使用以下公式计算y
的值:
# Compute 'y'
y = m*x c noise
data.append([x, y])
完成迭代后,将数据分为输入和输出变量:
代码语言:javascript复制# Separate x and y
x_data = [d[0] for d in data]
y_data = [d[1] for d in data]
绘制数据:
代码语言:javascript复制# Plot the generated data
plt.plot(x_data, y_data, 'ro')
plt.title('Input data')
plt.show()
生成感知器的权重和偏差。 对于权重,我们将使用统一的随机数生成器并将偏差设置为零:
代码语言:javascript复制# Generate weights and biases
W = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
使用 TensorFlow 变量定义方程式:
代码语言:javascript复制# Define equation for 'y'
y = W * x_data b
定义在训练过程中可以使用的损失函数。 优化器将尝试尽可能减小此值。
代码语言:javascript复制# Define how to compute the loss
loss = tf.reduce_mean(tf.square(y - y_data))
定义梯度下降优化器并指定损失函数:
代码语言:javascript复制# Define the gradient descent optimizer
optimizer = tf.train.GradientDescentOptimizer(0.5)
train = optimizer.minimize(loss)
所有变量均已就位,但尚未初始化。 让我们这样做:
代码语言:javascript复制# Initialize all the variables
init = tf.initialize_all_variables()
启动 TensorFlow 会话并使用初始化器运行它:
代码语言:javascript复制# Start the tensorflow session and run it
sess = tf.Session()
sess.run(init)
开始训练过程:
代码语言:javascript复制# Start iterating
num_iterations = 10
for step in range(num_iterations):
# Run the session
sess.run(train)
打印训练过程的进度。 随着迭代的进行,loss
参数将继续减小:
# Print the progress
print('nITERATION', step 1)
print('W =', sess.run(W)[0])
print('b =', sess.run(b)[0])
print('loss =', sess.run(loss))
绘制生成的数据并将预测模型覆盖在顶部。 在这种情况下,模型是一行:
代码语言:javascript复制 # 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))
设置图的参数:
代码语言:javascript复制 # 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-1kIXrMkG-1681568818816)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_20_05.png)]
图 5:输入数据图
如果关闭此窗口,您将看到训练过程。 第一次迭代如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-18aED86J-1681568818816)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_20_06.png)]
图 6:训练过程的第一次迭代图
如我们所见,行完全关闭。 关闭此窗口以转到下一个迭代:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oUfXPX78-1681568818817)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_20_07.png)]
图 7:训练过程的后续迭代图
行似乎更好,但是仍然关闭。 让我们关闭此窗口,然后继续迭代:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oHJ9BhyC-1681568818817)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_20_08.png)]
图 8:训练过程的另一个后续迭代的图
看起来行越来越接近实际模型。 如果继续这样迭代,模型将变得更好。 第八次迭代如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IcZmj5Ob-1681568818817)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_20_09.png)]
图 9:训练过程的第八次迭代图
该行似乎非常适合该数据。 然后,您应该将其打印出来:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nIk4tVHX-1681568818817)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_20_10.png)]
图 10:周期的初始输出
完成训练后,您将看到以下内容:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4EamiHT9-1681568818817)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_20_11.png)]
图 11:周期的最终输出
我们可以看到w
和b
的值是如何不断调整的,我们还可以看到损失如何持续减小直到损失很小的程度,以至于我们不再看到它减小。 有趣的是,我们能够很快取得良好的结果,但是我们为我们的网络解决了一个相当简单的问题。 让我们开始吧。
使用单层神经网络构建图像分类器
让我们看看如何使用 TensorFlow 创建单层神经网络,并使用它来构建图像分类器。 我们将使用 MNIST 图像数据集来构建我们的系统。 它是一个包含手写数字图像的数据集。 我们的目标是建立一个可以正确识别每个图像中数字的分类器。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
提取 MNIST 图像数据。 one_hot
标志指定我们将在标签中使用单热编码。 这表示,如果我们有n
类,则给定数据点的标签将是长度为n
的数组。 该数组中的每个元素对应于一个给定的类。 要指定一个类,相应索引处的值将设置为1
,其他所有值都将为0
:
# Get the MNIST data
mnist = input_data.read_data_sets("./mnist_data", one_hot=True)
数据库中的图像是28x28
。 我们需要将其转换为一维数组以创建输入层:
# The images are 28x28, so create the input layer
# with 784 neurons (28x28=784)
x = tf.placeholder(tf.float32, [None, 784])
创建具有权重和偏差的单层神经网络。 数据库中有 10 个不同的数字。 输入层中神经元的数量为784
,输出层中神经元的数量为10
:
# 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]))
创建用于训练的方程式:
代码语言:javascript复制# Create the equation for 'y' using y = W*x b
y = tf.matmul(x, W) b
定义损失函数和梯度下降优化器:
代码语言:javascript复制# 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)
初始化所有变量:
代码语言:javascript复制# Initialize all the variables
init = tf.initialize_all_variables()
创建一个 TensorFlow 会话并运行它:
代码语言:javascript复制# Create a session
session = tf.Session()
session.run(init)
开始训练过程。 我们将使用批量进行训练,在该批量中,我们在当前批量上运行优化器,然后继续进行下一个批量以进行下一次迭代。 每次迭代的第一步是获取下一批要训练的图像:
代码语言:javascript复制# 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)
在这批图像上运行优化器:
代码语言:javascript复制 # Train on this batch of images
session.run(optimizer, feed_dict = {x: x_batch, y_loss: y_batch})
训练过程结束后,使用测试数据集计算准确率:
代码语言:javascript复制# 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-FrNR0IsL-1681568818818)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_20_12.png)]
图 12:精度输出
如输出所示,该模型的准确率为 92.1%。 这是一个相当低的分数。 让我们看看如何使用 CNN 来改善。
使用卷积神经网络构建图像分类器
上一节部分中的图像分类器效果不佳。 在 MNIST 数据集上获得 92.1% 相对容易。 让我们看看如何使用 CNN 来获得更高的准确率。 我们将使用相同的数据集构建图像分类器,但使用 CNN 而不是单层神经网络。
创建一个新的 Python 文件并导入以下包:
代码语言:javascript复制import argparse
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
定义一个函数来为每个层中的权重创建值:
代码语言:javascript复制def get_weights(shape):
data = tf.truncated_normal(shape, stddev=0.1)
return tf.Variable(data)
定义一个函数来为每个层中的偏差创建值:
代码语言:javascript复制def get_biases(shape):
data = tf.constant(0.1, shape=shape)
return tf.Variable(data)
定义一个函数,根据输入形状创建一个层:
代码语言:javascript复制def create_layer(shape):
# Get the weights and biases
W = get_weights(shape)
b = get_biases([shape[-1]])
return W, b
定义函数以执行 2D 卷积:
代码语言:javascript复制def convolution_2d(x, W):
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1],
padding='SAME')
定义一个函数来执行2×2
最大池化操作:
def max_pooling(x):
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1], padding='SAME')
提取 MNIST 图像数据:
代码语言:javascript复制# Get the MNIST data
mnist = input_data.read_data_sets(args.input_dir, one_hot=True)
使用784
神经元创建输入层:
# The images are 28x28, so create the input layer
# with 784 neurons (28x28=784)
x = tf.placeholder(tf.float32, [None, 784])
我们将使用利用图像 2D 结构的卷积神经网络。 因此,让我们将x
重塑为 4D 张量,其中第二维和第三维指定图像大小:
# Reshape 'x' into a 4D tensor
x_image = tf.reshape(x, [-1, 28, 28, 1])
创建第一个卷积层,它将为图像中的每个5×5
面片提取32
特征:
# Define the first convolutional layer
W_conv1, b_conv1 = create_layer([5, 5, 1, 32])
将图像与上一步中计算的权重张量进行卷积,然后向其添加偏差张量。 然后,我们需要将整流线性单元(ReLU)函数应用到输出:
代码语言:javascript复制# 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
最大池化运算符应用于上一步的输出:
# Apply the max pooling operator
h_pool1 = max_pooling(h_conv1)
创建第二个卷积层以为每个5×5
补丁计算64
特征:
# Define the second convolutional layer
W_conv2, b_conv2 = create_layer([5, 5, 32, 64])
将上一层的输出与在上一步中计算的权重张量进行卷积,然后向其添加偏差张量。 然后,我们需要将 ReLU 函数应用于输出:
代码语言:javascript复制# 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
最大池化运算符应用于上一步的输出:
# Apply the max pooling operator
h_pool2 = max_pooling(h_conv2)
现在将图像大小减小为7×7
。 用1024
神经元创建一个完全连接的层:
# Define the fully connected layer
W_fc1, b_fc1 = create_layer([7 * 7 * 64, 1024])
重塑上一层的输出:
代码语言:javascript复制# Reshape the output of the previous layer
h_pool2_flat = tf.reshape(h_pool2, [-1, 7`7`64])
将上一层的输出与完全连接的层的权重张量相乘,然后向其添加偏置张量。 然后,我们将 ReLU 函数应用于输出:
代码语言:javascript复制# 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 占位符,该占位符指定在删除过程中保留神经元输出的概率:
代码语言:javascript复制# 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
输出神经元定义读出层。 计算输出:
# 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
函数:
# 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)
定义精度的计算方法:
代码语言:javascript复制# 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))
初始化变量后创建并运行会话:
代码语言:javascript复制# Create and run a session
sess = tf.InteractiveSession()
init = tf.initialize_all_variables()
sess.run(init)
开始训练过程:
代码语言:javascript复制# 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
次迭代打印一次精度进度:
# 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)
在当前批量的上运行优化器:
代码语言:javascript复制 # Train on the current batch
optimizer.run(feed_dict = {x: batch[0], y_loss: batch[1], keep_prob: 0.5})
训练过程结束后,使用测试数据集计算准确率:
代码语言:javascript复制# 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-c4lOaL70-1681568818818)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_20_13.png)]
图 13:精度输出
随着继续迭代,精度不断提高,如以下屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w89RfP9u-1681568818818)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/ai-py/img/B15441_20_14.png)]
图 14:精度输出
现在我们有了的输出,我们可以看到的卷积神经网络的准确率比简单的神经网络要高得多。 与上一节中未使用 CNN 的准确率相比,这实际上是一个很大的改进。 CNN 可以解救!
总结
在本章中,我们学习了深度学习和 CNN。 我们讨论了什么是 CNN,以及我们为什么需要它们。 我们讨论了 CNN 的架构。 我们了解了 CNN 中使用的各种类型的层。 我们讨论了如何使用 TensorFlow。 我们使用它来构建基于感知器的线性回归器。 我们学习了如何使用单层神经网络构建图像分类器。 然后,我们使用 CNN 构建了图像分类器。
在下一章中,我们将了解 CNN 的另一个受欢迎的兄弟– 循环神经网络(RNN)。 像 CNN 一样,RNN 也已流行,并且现在非常流行。 与以前的模型相比,它们取得了令人印象深刻的结果。 在某些情况下,甚至在某些情况下甚至超过了人类的表现。
参考
- Yann LeCun 对有关 Quora 的问题的答复