一、聊天机器人简介
近年来,聊天机器人和人工智能(AI)已经成为科技领域和公众想象中的热门话题。聊天机器人,即可以使用自然语言交流的计算机程序,正在做从订购比萨饼到购买衣服到节省停车罚单的钱到它们之间的谈判的一切事情。最初,开发一个聊天机器人相当于开发一个与消息平台的集成。没有简单的方法用代码表示对话流。当微软创建 Bot 框架和 Bot Builder SDK 时,这种情况发生了变化。微软创造了一个丰富的环境,在这个环境中,开发人员从与单个频道集成的顾虑中解放出来,可以专注于编写代码来执行聊天机器人需要完成的对话任务。Bot Builder SDK 提供了一种开发对话体验的通用方法。微软的 Bot 连接器实现了将通用格式转换为特定于通道的消息的逻辑。
结果是聊天机器人的开发对于数百万开发者来说变得更加容易。工程师们不再需要学习如何与脸书的 Messenger APIs 或 Slack 的 Web API 集成。相反,开发人员专注于核心机器人逻辑和对话体验。微软担心剩下的。
Bot Builder SDK 可用于。NET 和 Node.js,并在 GitHub 上作为 MIT 授权的开源项目运行。团队在开发和应对开发团队遇到的各种问题方面都很活跃。而且球队很友好的开机!
2017 年 12 月,微软同时推出了 Bot 框架和语言理解智能服务(LUIS)。LUIS 是微软的自然语言服务,它将帮助我们为机器人添加对话智能。Bot 框架现在也被称为 Azure Bot 服务;这两者指的是同一个东西。顾名思义,Azure Bot 服务现在是微软 Azure 云产品的成熟部分。微软也提供了免费的服务层,所以我们可以尽情地玩这个框架。书中所有的样本和技术都可以免费实验!
在过去的几年里,所有大型科技公司,如微软、脸书和谷歌,以及许多较小的公司,都在尝试创建最好、最易用的聊天机器人开发框架。这个领域非常活跃。框架来来去去。事情似乎每天都在变化。尽管空间是动态的,微软的机器人框架仍然是开发强大、快速和灵活的聊天机器人的最佳平台。我很高兴用这个工具带你踏上聊天机器人开发之旅。
期望游戏
两年多来,我与客户交谈的大部分时间都花在讨论聊天机器人的功能,它们是什么,更重要的是,它们不是什么。我们的文化在很大程度上混淆了聊天机器人的能力和人工智能,很容易看出为什么。一些聊天机器人使用丰富的自然语言能力,让我们想象它们有更多的功能。同样,基于语音的数字助理,如 Cortana、Alexa 和 Google Assistant,生活在我们的家中,可以像真人一样说话。为什么聊天机器人不会显示出更高的智能?
这种文化还渗透着对 IBM 的沃森(Watson)在《危险边缘》(Jeopardy)上的引用、 4 《纽约时报》在谷歌大脑团队 5 上的特写,以及他们在语言翻译方面使用深度学习、无人驾驶汽车的壮举,以及 AlphaZero 在学习如何下棋仅四个小时后就摧毁了世界上最高评级的下棋引擎。 6
这些和其他许多故事凸显了对这些技术的投资和兴趣,预示着我们正在走向的人工智能驱动的设备交互。人工智能领域的发展改变了我们与技术互动的方式,以及我们对技术的期望。为我们的设备赋予人类属性和能力正变得越来越普遍。认知和科幻领域的思想家长期以来一直在努力解决这种可能性,阿西莫夫的机器人三定律(Three Laws of Robotics)推广了这种可能性,这是一套机器人必须遵守的规则,以确保机器人不会追逐人类。现在现实世界中已经有了一些清晰而具体的人工智能例子,这种现实似乎更加接近了。
然而,现实与人工智能在一些非常具体的问题领域的成功所设定的期望并不匹配。虽然我们在自然语言处理、计算机视觉、情感检测等方面取得了巨大的飞跃,但将所有这些组成一个类似人类的智能,通常被称为人工通用智能 AGI,还不在我们的掌握之中,也不是聊天机器人的现实目标。对于每一篇庆祝人工智能领域巨大成就的文章,都有一篇匹配的文章淡化了围绕同一技术的炒作,并展示了为什么这种类型的人工智能仍然远非完美的例子(想想那些展示计算机视觉算法仍然无法正确分类的所有图像的文章)。正如任何被媒体大肆宣传的技术一样,我们必须合理对待我们对它的期望。
我们的机器人会成为具有人类智能的代理人,与我们的用户进行对话吗?不。考虑到我们希望机器人完成的技术和任务,我们能让机器人很好地完成这些任务吗?绝对的。这本书旨在让读者掌握构建引人注目、引人入胜且有用的聊天机器人的必要技能。在这段旅程中,你想融入多少最新的人工智能技术,这取决于工程师。当然,对于一个优秀的聊天机器人来说,这些技术并不是必需的。
什么是聊天机器人?
在最基本的层面上,聊天机器人,在本书中也被简称为机器人,是一个计算机程序,它可以接受用户以自然语言输入的信息,并向用户返回文本或富媒体。用户通过 Facebook Messenger、Skype、Slack 等消息应用,或者通过亚马逊 Echo、谷歌 Home 或微软 Cortana 支持的 Harmon Kardon’s Invoke 等语音激活设备,与聊天机器人进行交流。
图 1-1 展示了我们第一个使用微软 bot 框架构建的 Bot。这个 bot 只是向用户返回相同的消息,并以字符串“echo:”作为前缀。在 Bot 框架上运行这种体验的逻辑非常简单。
图 1-1
一个简单的回声机器人
var bot = new builder.UniversalBot(connector, [
function (session) {
// for every message, send back the text prepended by echo:
session.send('echo: ' + session.message.text);
}
]);
这是一个聊天机器人。基本的,不是很有用,对吧?我们可以轻松地创建一个 YouTube 机器人,给定用户文本输入,搜索该主题的视频,并将这些视频的链接发送给用户(图 1-2 和 1-3 )。
图 1-2
猫也可以
这是另一个基本的机器人,只做一件事,而且做得很好。它与 YouTube API 集成,使用您的输入作为搜索参数,并返回在 Bot 框架中被称为 cards 的内容,我们将在本书的后面进行探讨。这些图像带来了更丰富、更引人入胜的体验——更有趣,但仍相当基础。
图 1-3
狗更好!
接下来显示了这个程序的代码。我们向 YouTube 发出请求,并将响应从 YouTube 格式翻译成 Bot 框架卡。
const bot = new builder.UniversalBot(connector, [
session => {
const url = vsprintf(urlTemplate, [session.message.text]);
request.get(url, (err, response, body) => {
if (err) {
console.log('error while fetching video:\n' + err);
session.endConversation('error while fetching video. please try again later.');
return;
}
const result = JSON.parse(body);
// we have at most 5 results
let cards = [];
result.items.forEach(item => {
const card = new builder.HeroCard(session)
.title(item.snippet.title)
.text(item.snippet.description)
.images([
builder.CardImage.create(session, item.snippet.thumbnails.medium.url)
])
.buttons([
builder.CardAction.openUrl(session, 'https://www.youtube.com/watch?v=' + item.id.videoId, 'Watch Video')
]);
cards.push(card);
});
const reply = new builder.Message(session)
.text('Here are some results for you')
.attachmentLayout(builder.AttachmentLayout.carousel)
.attachments(cards);
session.send(reply);
});
}
]);
好吧,这个怎么样?我们可以有一个机器人,给定一个陈述,它可以判断这是一个中性的、积极的还是消极的陈述,并返回一个适当的响应(图 1-4 )。我们没有展示它,但是这个例子的代码和前面的例子一样简单:我们从一个简单的情感 REST API 中获取一个情感分数,并使用它来呈现一个答案。
图 1-4
利用人工智能推动对话的一个简单例子
这是一个简单的例子,表明如果我们走这条路,我们的代码可以多么容易地与人工智能集成。机器人不必总是遵循问答模式。机器人可以主动接触用户。例如,我们可以有一个欺诈警报机器人(图 1-5 )。
图 1-5
主动用户信息
机器人可以更加任务驱动。例如,想象一个日历机器人,它可以创建约会,检查可用性,编辑或删除约会,并给你一个日历摘要(图 1-6 )。
图 1-6
一个简单的日历机器人集成了谷歌日历
现在事情开始变得有点有趣了。我们开始采用自然语言并付诸行动。
为什么不
为什么机器人变得如此重要?当然,它们以各种形式存在于像 IRC 7 和 AOL Instant Messenger 这样的老派应用中。 8 这些都不是小实验。IRC 机器人已经存在很长时间了。我记得通过 IRC 与相当多的机器人互动过。当谈到技术的时候,我还很年轻,很天真,最初我以为真的有人在回应我的信息。我很快意识到,某个地方有一台机器在回应我写的东西。我与 IRC 机器人互动得越多,我就越把它们当成命令行。然而,这在当时是非常小众的技术。公众不是每天都与机器人互动,所以没有必要迎合自然语言的互动。
今天,我们与周围技术互动的方式完全不同,这是由三种力量驱动的:人工智能的进步,消息应用作为对话智能平台的想法,以及语音激活的对话界面。
人工智能的进步
在整个 20 世纪,计算机科学家、生物学家、语言学家和经济学家在认知、人工智能、人工生命、机器学习和深度学习领域取得了巨大进步。执行指令的计算机程序的概念,通用图灵机 9 和可以数字存储代码并执行代码的计算机架构的想法,接受输入并产生输出,以及冯诺依曼架构, 10 在人类历史标准中是最近的,但却是我们在计算机上的工作所基于的基本概念。1943 年,麦卡洛克和皮茨在他们的论文《神经活动中内在思想的逻辑演算》中首次发表了关于神经网络的想法111950 年,阿西莫夫在其著作《机器人三定律 I、 机器人中收录了机器人三定律。 12 同年,第一篇描述计算机如何下棋的论文——克劳德·香农(Claude Shannon)的《为下棋编写计算机程序》发表。他继续从本质上发明了信息论领域。从 20 世纪 60 年代开始,这个领域的研究数量和增长速度令人震惊;我们每天都能在媒体对最新人工智能应用的报道中看到这方面的证据。
可以说,自 20 世纪 60 年代以来,机器学习和使用各种算法构建我们自己的模型的过程已经变得更好执行和更容易获得。像 scikit-learn for Python 和 Google 的 Tensor Flow 等库都有很好的记录,并得到了社区的大力支持。大型科技公司也在他们的计算能力和能力上投入了足够的资金,以便能够在合理的时间框架内完成一些计算量最大的任务。微软、亚马逊、谷歌、IBM 和其他公司现在都以这样或那样的方式涉足云平台。下一步是按需提供一些机器学习算法。如果我们简单地以微软的认知服务为例,我们会发现在撰写本文时有 30 个 API。这些包括计算机视觉工具,如面部和情感检测、内容审核和 OCR 功能。它还包括语言工具,如自然语言处理、语言和文本分析以及自然语言理解。它甚至包括搜索和知识工具,如推荐引擎和语义搜索。任何开发人员都可以在任何时间以合理的成本接入这些强大功能的服务的可用性是智能系统在我们生活中变得如此普遍的重要原因,也是我们的机器人可以利用的基础设施之一。我们将在第十章中探讨微软的认知服务。
作为对话智能平台的消息应用
近年来,移动通讯应用风靡一时。Snapchat、Slack、Telegram、iMessage、FB Messenger、WhatsApp 和微信是移动用户手机上最常用的一些应用。事实上,它们的使用率已经超过了脸书等社交网络。据 Business Insider 报道,在 2015 年第一季度的某个时候,即时通讯应用开始比社交网络更受欢迎,这一趋势一直持续到现在。虽然这本书不会详细介绍美国和全球市场的所有相关参与者,但关键是,微信和 LINE 等亚洲通讯应用已经找到了通过聊天应用增加使用量的最佳方式,以及如何利用这种使用来赚钱。货币化趋势尚未完全赶上美国市场,但苹果、Twitter 和脸书等公司已经领先,允许开发者创建简单的聊天机器人,甚至支付集成。我并不是说讨论仅限于上述玩家;开放消息平台的趋势是普遍的。
在现有的信息平台中托管这些机器人的能力为更多的客户打开了品牌。用户体验停留在消息应用中。机器人开发者不需要像移动应用开发者那样关心动画和内存管理;主要关注的是与用户的对话。我们将在整本书中遇到的一个有趣的概念是,机器人不仅仅是文本。它们可以包括图像、视频、音频以及调用其他命令的按钮。在现有消息应用的范围内创建对话体验是在应用中编写应用的练习;我们的 bot 受到消息传递平台支持的本机特性的限制。Bot 框架有必要的设施来最大限度地利用所有这些功能。
声控智能助理
另一个显著加速对话智能技术发展的因素是支持语音的硬件设备的发展。苹果公司在 2011 年推出了更重要的现代虚拟助手之一 Siri。Siri 现在是一个家喻户晓的名字,它是由最著名的桌面语音识别系统之一 Nuance 的语音到文本产品 Dragon NaturallySpeaking 背后的一些技术驱动的。
Siri 是第一个上市的,似乎鼓励了许多其他公司跳入语音助手的游戏。微软在 2014 年发布了 Cortana 助手,同年发布了第一款亚马逊 Echo 设备。Cortana 最初仅限于 Windows Phone 和 Windows 桌面操作系统,但后来在移动操作系统甚至 Xbox 上也可以使用。亚马逊的 Echo 采用了 Alexa 语音助手,是第一款商业上成功的独立硬件设备,并让亚马逊在早期主导了语音助手市场。在随后的几年里,脸书和谷歌分别推出了 2018 年初关闭)和谷歌助手。谷歌正通过 Google Home 进入语音设备领域。哈曼卡顿将一款名为 Invoke 的产品推向市场,这是一款基于微软 Cortana 的扬声器。许多其他参与者正在向该市场扩张,这进一步鼓励了该领域的创新。
人工智能和语音识别、自然语言处理和自然语言理解技术的进步加速了这种增加的活动和竞争。这些技术的显著发展增加了在标准、框架和工具方面为这些平台创建定制功能的活动。正如我们将很快看到的,这些自定义功能或技能可以由聊天机器人来支持。
我们为什么要创造机器人?
为什么我们要编写机器人,并使用消息应用作为平台?我们可以轻松地编写移动应用,发布到应用商店,然后一劳永逸,不是吗?不完全是。用户行为的各种趋势使得这种方法不太可行。
对于一些较大的品牌来说,下载他们的应用是一项简单的任务。我想用脸书?好吧,我去拿应用。我想查看我的电子邮件;我会使用应用。但是,我想和我当地的花店谈谈?我不需要一个应用。我不希望有这样的应用。为什么我要为我接触的每一家企业下载应用?理想情况下,我可以给他们打电话或者发短信,对吗?
公司在市场上采取的行动允许用户直接与企业对话。让我们以脸书为例。当地的花店可以有一个脸书页面,并在该页面上启用消息传递。企业员工可以在一个地方回答客户的询问。Twitter 在其新的直接消息 API 中也有类似的功能。这为企业提供了很多价值。应用下载摩擦的消除使得用户开始与企业对话变得更加容易。当然,下一步是自动化一些通信。这就是机器人的用武之地。消息传递平台负责许多问题,例如用户身份、身份验证、应用的整体稳定性等等。
这也转化为其他用例。让我们以 Slack 这样的生产力工具为例。Slack 是一个很棒的工作协作平台,它使人们能够跨多个主题进行聊天和协作。Slack 平台上的聊天机器人通常更注重生产力。例如,你可能很难让人们在 Slack 上使用约会机器人,而不是在脸书这样的社交网络上。图 1-7 显示了排名靠前的 Slack 机器人列表。这些类型的机器人更多地与工作任务相关联,如待办事项列表、站立、任务分配等。很明显,如果一个团队完全投入并沉浸在懈怠中,创建一个机器人来执行共同的任务可能比创建一个完全独立的网站更有效。
图 1-7
Slack bot 列表
尽管 Slack 的列表中包含一个名为机器人的特定类别,但事实是所有这些应用都是机器人。其中一些可能更具有对话性,而另一些可能更具有命令行的感觉;就我们而言,机器人只是简单地监听消息并根据消息采取行动。对于大量对话型聊天机器人来说,自然语言理解的主题,即理解人类语言的学科,对于良好的用户体验至关重要。因此,我们将章节 2 和 3 献给这个主题。
机器人解剖学
当我们深入 Bot 框架时,有必要将聊天机器人的开发分解成单独的组件。一般来说,每个组件都有几种方法。在下面的讨论中,我试图描述一般概念,然后强调微软在 Bot 框架中解决问题的方式。
-
机器人运行时
-
自然语言理解引擎
-
对话引擎
-
通道整合
机器人运行时
在最基本的层面上,聊天机器人是一种响应用户请求的网络服务。根据我们集成的消息传递平台的不同,细节会有所不同,但想法是相同的:消息传递平台通过 HTTP 端点用包含用户输入的消息调用 bot。我们的聊天机器人的角色是处理消息,并向平台发送消息,包括机器人的响应以及任何附件或特定于平台的数据。图 1-8 展示了一种通用方法。根据平台的不同,我们可能能够返回带有 HTTP 状态代码或其他格式的异常情况。当我们的 bot 处理消息时,它通过调用通道的 HTTP 端点进行响应。然后,通道将消息传递给用户。
图 1-8
用户、消息平台和通用机器人之间的消息交换
这种方法有一些问题,主要是我们将机器人绑定到特定的消息传递通道,而我们的机器人应该是通道不可知的,这样我们就可以尽可能多地重用逻辑。bot 框架通过提供位于消息传递平台和 Bot 之间的连接器服务来解决这个问题。实际上,交互看起来更像图 1-9 。请注意,通道连接器拥有与消息传递平台的连接和通信,并将消息转换为我们的 bot 可以识别的通用格式。我们将在本章后面的“通道集成”部分更详细地介绍通道。
图 1-9
使用 bot 框架在用户、消息传递平台、连接器服务和 Bot 之间进行消息交换
由于 bot 运行时只是一个监听 HTTP 端点的计算机程序,我们可以使用任何允许我们接收 HTTP 消息的技术来开发 bot。我们可以利用。NET、Node.js、Python 和 PHP。事实上,我们可以简单地使用 Bot 框架来获得连接器的优势,并使用我们喜欢的任何方法实现 HTTP 端点。然而,如果我们这样做了,我们将失去 Bot Builder SDK。我们将在本章后面的“对话引擎”部分介绍它的好处和使用它的理由。
自然语言理解引擎
编写一个能够阅读和理解用户话语的聊天机器人是一项挑战。人类语言是具有灵活和不一致规则的非结构化输入。然而,我们的机器人需要能够接受这些输入,并找出用户在谈论什么。在高层次上,自然语言理解引擎为机器人开发者解决了两个问题:意图分类和实体提取。
我们将通过例子来说明什么是意图和实体。比方说,我们正在开发一个恒温器控制机器人。最初,我们希望支持四个动作:打开、关闭、设置模式为制冷或制热,以及设置温度。用户可以用自然语言表达的动作类别(意味着开/关、设置模式或设置温度)被称为意图。模式本身(冷或热)和温度值是实体。NLU 引擎允许机器人开发者定义一组与应用相关的自定义意图和实体。表 1-1 列出了一些示例映射。
表 1-1
由 NLU 系统解析的用户输入到意图的示例映射
|说话
|
目的
|
实体
|
| — | — | — |
| “打开” | 接通开启 | 没有人 |
| “关闭电源” | 岔道 | 没有人 |
| “设置为 68 度” | 设定温度 | “68 度”类型:温度 |
| “将模式设置为冷” | 设置模式 | “酷”类型:模式 |
显然,我们的代码基于意图和实体值执行逻辑要比基于原始的用户话语更容易。
bot 开发人员可以利用几种服务来获得这种 NLU 功能。在当前的技术环境下,有大量基于云的 API 可用,如 LUIS、Wit.ai 和 Dialog flow 等。LUIS 是这一组中最富有和表现最好的,他是第三章中 NLU 深入探讨的主题。
对话引擎
在构建机器人时,我们通常会开发一个工作流来实现我们的机器人想要完成的任务。按照基本的恒温器示例,我们可以设想如图 1-10 所示的机器人架构。
图 1-10
示例 bot 对话设计图
工作流总是从机器人监听用户话语开始。用户说出的话语将被解析为表 1-1 中的意图。如果意图是打开或关闭,机器人可以执行正确的逻辑,并用确认消息进行响应。如果我们收到一个设置温度的意图,我们的机器人可以验证温度实体存在。如果没有,我们向用户询问。一旦我们收到它,我们就可以执行正确的逻辑并发送确认响应。SetMode 的工作方式类似于 SetTemperature,因为我们将确认实体的存在,如果它不存在,就引出它。
这种基于用户输入的对机器人行为的描述是一种对话。设计输入、输出和转换类型的活动被称为对话体验设计。我们将在第四章中深入讨论这个话题。
对话引擎是跟踪传入消息、处理它们并在对话图 Node(也称为对话)之间执行状态转换的引擎。它为每个用户分别执行此操作。对话的状态被存储起来,这样当下一条用户消息进入机器人时,机器人就知道用户的当前状态。Bot 框架在通过 Bot Builder SDK 提供对话引擎方面做得很好。
旁白:意图,实体,行动,插槽,哦,我的!
开发机器人有多种方法,但它们可以总结为两种方法:机器人引擎和我所说的机器人对话即服务。前面描述了 bot 引擎:我们将 bot 作为 web 服务运行,根据需要调用 NLU 平台,并使用对话引擎将消息路由到对话。像 Dialogflow 这样的公司推广了“机器人对话即服务”的方法。该方法意味着 NLU 解析、对话映射、状态和转换发生在 Dialogflow 基础设施上的云中。然后,Dialogflow 调用您的 bot 来修改响应或与其他系统集成。
当用户的话语映射到一个意图和一组定义的实体时,它被称为一个动作。一个动作有一个意图和一组参数。基于我们的恒温器机器人,我们可以定义一个名为 SetTemperatureAction 的动作。此操作是使用温度参数设置温度的目的。温度参数的类型是温度实体。当 Dialogflow 解析一个动作时,它可以调用您的 bot 来完成该动作。在该模型中,bot 逻辑关注于基于 NLU 服务的解析逻辑的逻辑执行;对话引擎外包给 NLU 服务。
这种 bot 开发方法的一个高级主题是槽填充。这是一个过程,通过这个过程,服务注意到一个动作只有部分被用户输入填充,并自动要求用户填充剩余的槽,也就是我们所说的动作参数。表格 1-2 和 1-3 展示了两个示例动作。
表 1-3
基于机票预订机器人的更复杂的动作
|行动
|
名字
|
类型
|
必需的?
|
提示
|
| — | — | — | — | — |
| 订机票 | 从 | 城市 | 是 | 出发城市 |
| 到 | 城市 | 是 | 目的地城市 |
| 日期 | 日期时间 | 是 | 你什么时候旅行? |
表 1-2
在我们的恒温器机器人中设置温度的动作定义
|行动
|
名字
|
类型
|
必需的?
|
提示
|
| — | — | — | — | — |
| 设定温度 | 温度 | 温度 | 是 | 你想要设置什么温度? |
图 1-11 展示了在这个对话即服务模型中,用户、消息传递平台、连接器、NLU 服务和机器人之间的整个端到端流程。
图 1-11
典型的 bot 对话即服务流
对话即服务方法善于在短时间内启动并运行某些东西。不幸的是,这样会失去一些控制和灵活性。使用 bot 框架可以让我们完全控制 Bot 引擎,从而避开这些问题。
通道整合
构建机器人意味着处理多个消息平台。你的老板让你写一个 Facebook 信使机器人。你发布它,你的老板祝贺你的伟大工作。然后他问你,“我们能把这个作为网络聊天添加到我们的 FAQ 页面吗?”您的 bot 代码绑定到 Messenger Webhooks 和 Send API。您四处游荡,认为您可以在传输接口后面隔离一些与 Messenger 通信的逻辑。您创建了同一个接口的第二个实现,它通过 web 套接字与您的聊天机器人对话。现在,您已经创建了自己的机器人和消息传递平台之间接口的抽象。
我们希望我们的机器人逻辑尽可能地从单个消息传递平台中抽象出来。如何从通道接收消息和发送响应的细节是我们不想过多关注的细节,除非我们是构建各种平台连接器的专业人员。我不认为你会读这本书,如果你是。你想开发一个机器人,而不是基础设施。幸运的是,市场上不同的 bot 框架通常会为我们完成所有这些工作,如图 1-12 所示。这些框架允许我们以一种与通道无关的方式编写一个机器人,然后通过几次点击和输入一些数据来连接到这些通道。这些功能通常被称为频道或频道集成。
与许多通用框架一样,也有一些框架不支持的边缘情况,因为平台特性要么太新,要么特定于平台。在这种情况下,框架应该允许我们以其本地格式与平台进行通信。Bot 框架为此提供了一种机制。
此外,我们的框架应该足够灵活,允许我们创建定制的通道连接器。例如,如果我们希望构建一个提供聊天机器人界面的移动应用,框架应该允许我们这样做。如果我们的企业正在使用一个不受微软连接器支持的即时消息通道,我们应该能够创建一个。微软的 Bot 框架通过我最喜欢的特性之一:Directline API 实现了这种程度的集成。
图 1-12
你的机器人不应该关心与哪个通道对话。应该为你抽象出来。
我们将在第 9 和 10 章中介绍通道和定制通道集成。
结论
在这一章中,我们快速浏览了可用于构建机器人的不同组件的表面。在我的工作中,Bot 框架明显战胜了使用对话作为服务方法的竞争对手。Bot 框架提供的灵活性和控制是许多企业场景的需求。Bot 框架还提供了更好、更丰富的抽象,更深层次的连接器集成,以及开放和多样化的社区。机器人框架团队已经创建了一个非常强大的套件,可以作为任何对话机器人的基础。我和我的团队使用 Bot 框架已经快两年了,没有找到放弃这个平台的理由。事实上,该框架的对话引擎方法和连接器架构已经被证明对我们抛出的任何用例都具有弹性。
由于这些和许多其他原因,这本书围绕使用微软的 Bot 框架作为框架的选择。该框架适用于 C#/。NET 和 Node.js 开发平台。出于本书的目的,我们将使用 Node.js 版本。我们不会利用任何额外的工具,如 TypeScript 或 CoffeeScript。我们简单地使用普通的 JavaScript 来展示使用 Bot Framework SDK for Node.js(又名 Bot Builder)开始编写 Bot 是多么容易和简单。
不管宣传与否,用于构建机器人的技术和技巧确实令人惊叹。作为这次冒险的一部分,我想确保我们不仅涵盖了构建机器人的基础知识,还学习了更多关于一些底层技术和方法的知识。我们不会非常深入地研究这些主题,但我会涵盖足够多的内容,让读者对如何实现机器人中的智能有一个初步的了解,以便探索更复杂的场景。为了书籍的整体重点,当我涉及这些主题时,我会提供额外阅读材料的链接和信息来补充内容。我不是数据科学家,但我已经尽我所能介绍了相关的机器学习(ML)概念。
我们即将踏上一段激动人心的旅程,穿越对话设计、自然语言理解和应用于聊天机器人的机器学习的世界。当我们讨论这些主题和构建机器人时,请记住,这些技术适用于从聊天机器人到语音助手技能的所有东西。随着自然语言和语音界面在家庭和工作场所变得越来越流行,我保证你会在当前的项目和未来的自然语言应用中应用这些概念。我们走吧!
机器人律师为违规停车罚单辩护: http://www.npr.org/2017/01/16/510096767/robot-lawyer-makes-the-case-against-parking-tickets
2
成交还是不成交?训练 AI 机器人谈判: https://code.facebook.com/posts/1686672014972296/deal-or-no-deal-training-ai-bots-to-negotiate/
3
GitHub 上的微软 Bot Builder SDK:https://github.com/Microsoft/BotBuilder
4
IBM Watson:赢得 Jeopardy 的超级计算机如何诞生的内幕,以及它下一步想做什么: http://www.techrepublic.com/article/ibm-watson-the-inside-story-of-how-the-jeopardy-winning-supercomputer-was-born-and-what-it-wants-to-do-next/
5
伟大的人工智能觉醒: https://www.nytimes.com/2016/12/14/magazine/the-great-ai-awakening.html
6
Google 的 AlphaZero 在百场比赛中消灭 stock fish:https://www.chess.com/news/view/google-s-alphazero-destroys-stockfish-in-100-game-match
7
IRC 机器人: https://en.wikipedia.org/wiki/IRC_bot
8
SmarterChild: https://en.wikipedia.org/wiki/SmarterChild
9
万能图灵机: https://en.wikipedia.org/wiki/Universal_Turing_machine
10
冯·诺依曼建筑: https://en.wikipedia.org/wiki/Von_Neumann_architecture
11
一种内在于神经活动的逻辑思维: http://www.cs.cmu.edu/~epxing/Class/10715/reading/McCulloch.and.Pitts.pdf
12
机器人三定律: https://en.wikipedia.org/wiki/Three_Laws_of_Robotics
13
二、聊天机器人自然语言理解
在我们开始创建机器人和花哨的自然语言模型之前,我们将快速了解自然语言理解(NLU)及其一些机器学习(ML)基础。在下一章中,我们将使用微软的语言理解智能服务(LUIS)来实现这些 NLU 概念。您可以使用其他服务(例如,微软的认知服务)或 Python/R ML 工具来探索其他一些概念。这一章旨在为你提供一个关于 ML 领域的快速入门,因为它与自然语言任务有关。如果你熟悉这些概念,请务必跳到第三章。否则,我们希望传授对 NLU 的根源以及如何将其应用于机器人领域的基本理解。互联网上有大量深入讨论所有这些话题的内容;如果你喜欢冒险,我们会提供合适的参考资料!
如果我们选择开发一个 NLU 集成的聊天机器人,我们的日常工程将涉及与系统的持续互动,这些系统可以理解用户所说的话。这是一项艰巨的任务。考虑使用强力编码来理解与我们的自然语言控制器恒温器相关的自由文本用户输入。我们在第一章中介绍了这个用例。我们有四个目标:通电、断电、设置模式和设置温度。让我们考虑一下设定温度的目的。你如何编码一个系统,让它理解用户想要设置一个温度,以及用户输入的哪一部分代表温度?
我们可以使用一个正则表达式来尝试匹配像“将温度设置为{温度}”、“设置为{温度}”和“设置{温度}”这样的句子你测试一下。你感觉很好,然后一个测试者过来说,“我想要 80 度。”好吧,没什么大不了的。我们加上“我希望它是{温度}”第二天,有人过来说,“把温度降低 2 度。”我们可以加上“温度降低{diff}”和“温度升高{diff}”但是现在我们需要检测字降低和增加。我们如何解释这些词的变体呢?不要让我们开始使用多个命令,比如“白天设置为 68,晚上设置为 64。”仔细想想,我们在谈论什么温度单位?
当我们思考我们想要在聊天机器人上支持的交互时,我们很快注意到使用暴力方法会导致一个非常乏味的系统,最终,鉴于自然语言交流的迷人和令人讨厌的不一致性,它不会执行得很好。如果我们想利用蛮力方法,我们可以得到的最接近的方法,并且仍然可以获得相当好的性能,就是使用正则表达式。Bot 框架支持这一点,我们将在第五章中看到。如果我们使用这样的方法,假设你不是一个正则表达式迷,我们的交互模型出于维护的原因需要保持简单。
自然语言理解(NLU)是复杂的自然语言处理(NLP)领域的一个子集,涉及人类语言的机器理解。NLU 和 NLP 与我们对人工智能的理解有着千丝万缕的联系,可能是因为我们经常将智力与沟通技能联系起来。这可能也有潜在的心理本质;我们认为,如果机器人理解我们在说什么,它就更聪明,不管我们的智力水平和我们说话的复杂程度如何。事实上,我们可能会对那种能够理解我们在想什么而不是我们在说什么的人工智能感到最高兴。但是我跑题了。
在这种假设下,命令行并不智能,因为它要求命令采用特定的格式。如果我们可以通过请求命令行“启动 node……我不确定是哪个文件,”来启动 Node.js 脚本,我们会认为它是智能的吗?你能帮我吗?."使用现代 NLU 技术,我们可以建立似乎了解某些专业或任务的模型。随后,从表面上看,机器人似乎有点智能。是吗?
事实是,我们还没有开发出计算能力和技术来创建一个与人类智能相匹配的 NLU 系统。如果一个问题只有在我们能让计算机像人类一样聪明的情况下才能解决,那么这个问题就被称为“人工智能难题”。一个像人类一样行为和理解自然语言输入的合适的 NLU 系统还不在我们的掌握之中;但是,我们可以创建狭窄而聪明的系统,能够很好地理解一些事情,从而创建合理的对话体验。
考虑到最近围绕 ML 和 AI 的大肆宣传,我们从一开始就设定这些期望是很重要的。在我们与客户关于对话智能的最初对话中,我总是首先提到的一件事是,期望和现实之间存在差距。我喜欢说,房间里的任何人都可以很容易地用人类可以理解但机器人不能理解的方式来表达事物,从而击败机器人。这项技术是有局限性的,在可用的预算和时间范围内它能做的事情也是有限的。没关系。只要我们创建一个专注于特定任务的聊天机器人,让我们的用户生活得更好,我们就在正确的道路上。如果我们能通过在聊天机器人中加入一些 NLU 来取悦用户,那就太好了!
自然语言机器学习背景
NLP 领域的起源可以追溯到艾伦·图灵,特别是图灵测试, 1 一种确定机器是否能够智能运行的测试。在测试中,评估者可以向两个参与者提问。以参与者之一是人类的身份回答;计算机是第二个参与者。基于评估者从两个参与者那里得到的答案,如果评估者不能确定哪个参与者是人类,哪个是计算机,那么就说计算机通过了图灵测试。一些系统声称能够通过图灵测试,但这些声明被认为是不成熟的。有一种批评认为,编写一个机器人来试图欺骗人类相信它是人类和理解人类的输入是两码事。我们距离通过图灵测试还有好几年的时间。
自然语言处理领域最著名的早期成功案例之一是伊莱扎,这是约瑟夫·韦森鲍姆写的一个心理学家的模拟。它写于 20 世纪 60 年代中期,是一个简单且看似智能的机器人的好例子。该机器人由一个脚本驱动,该脚本根据关键字为输入赋值,并将得分输入与输出进行匹配,这与机器人框架中的识别器没有什么不同。JavaScript 实现可以在网上找到;见图 2-1 。许多其他类似的系统被创造出来,并取得了不同程度的成功。
图 2-1
与 JavaScript 版本的 Eliza 交互的示例
NLU 引擎通常是基于规则的;它们用结构化的知识表示进行编码,供系统在处理用户输入时使用。大约在 20 世纪 80 年代,机器学习领域开始普及。机器学习是让计算机在没有为任务编码的情况下学习的过程——这似乎比基于规则的方法更接近智能。例如,我们简要地探讨了如何构建一个强力 NLU 引擎,以及使用各种规则进行编码的繁琐工作。使用机器学习,我们的系统不需要提前知道关于我们的领域和意图分类的任何事情,尽管我们当然可以从预先训练的模型开始。相反,我们将创建一个引擎,显示标有特定意图名称的样本输入。这被称为训练数据集。基于输入和标记的意图,我们训练一个模型来将输入识别为呈现的标签。一旦经过训练,模型就能够接收它尚未看到的输入,并为每个意图分配分数。我们训练模型的例子越多,它的性能就越好。这就是人工智能的用武之地:用高质量数据训练模型的净效果是,通过使用统计模型,系统可以开始对它尚未遇到的输入进行标签预测。
刚才描述的是监督学习的简化版本。该名称来源于输入数据被标记的事实。监督学习的性能可以很好地进行定量分析,因为我们知道真实的标签,并且能够将它们与预测的标签进行比较,以获得定量值,这种技术被称为交叉验证。最适合监督学习的任务类型是分类和回归问题。对于 C 类,分类是确定输入 I 是否属于 C 类的任务;例如,照片是熊猫的吗?或者,我们可以给定一组类 S,确定输入 I 的类。常见的分类算法包括支持向量机和决策树。图 2-2 、 2-3 和 2-4 展示了一个典型的监督学习场景。
图 2-2,2-3,2-4
一个监督学习的例子。图 2-2 是我们的训练数据,我们想请系统对图 2-3 中带问号的数据点进行分类。分类算法将利用数据点根据标记数据计算出边界,然后预测输入数据点的标记(图 2-4 )。
回归与此类似,但与预测连续值有关。例如,假设我们有一个跨机场的天气特征数据集。也许我们有温度、湿度、云量、风速、降雨量以及当天纽约 JFK、旧金山国际机场和芝加哥奥黑尔机场取消的航班数量的数据。我们可以将这些数据输入一个回归模型,并使用它来估计纽约、旧金山和芝加哥的一些假设天气的取消数量。
除了监督学习,还有其他形式的机器学习。无监督学习是理解未标记数据的任务,通常是如图 2-5 和 2-6 所示的数据聚类任务。
图 2-5 和 2-6
无监督学习,其中算法识别三组数据
半监督学习是用一些标记数据和一些未标记数据训练模型的想法。强化学习是一种系统学习的思想,通过进行观察,并基于所述观察,做出最大化某种回报函数的决策。如果决策产生了更好的回报,它就会被强化。否则,该决定将受到处罚。关于不同类型学习的更多信息可以在别处找到。 5
在斯坦福的 CS 页面上有一个关于深度强化学习的迷人插图。, 6 如图 2-7 所示。在这个演示中,一个代理人导航一个空间,并学习导航到有积极奖励的红苹果,并避免有毒的绿苹果。
图 2-7
深度强化学习算法的可视化
需要强调的一个有趣的点是,NLU 和 NLP 的普遍流行的 bot 应用都相当肤浅。事实上,有人批评称沃森在 Jeopardy 上做的事情或者机器人在 NLU 做的事情。正如雷·库兹韦尔在《华尔街日报》的一篇文章中所说,沃森不知道它赢了《危险边缘》。理解和分类/提取信息是两个不同的任务。这是一个公平的批评,但当涉及到在特定的狭窄背景下理解人类语言时,一个构建良好的意图和实体模型可以证明是有用的,这正是聊天机器人所做的。
除了意图分类问题之外,NLP 还关注诸如语音标记、语义分析、翻译、命名实体识别、自动摘要、自然语言生成、情感分析等任务。我们将在第十章的多语言机器人环境中研究翻译。
在 20 世纪 80 年代,对人工神经网络(ANN)研究的兴趣日益增加。在接下来的几十年里,该领域的进一步研究取得了引人入胜的成果。对人工神经网络中神经元的一种简单看法是,将其视为一个具有 N 个权重/输入和一个输出的简单函数。人工神经网络是一组相互连接的神经元。作为一个单元,神经网络接受一组输入并产生一个输出。训练神经网络的过程就是设置神经元权重值的过程。研究人员专注于分析许多不同类型的神经网络。深度学习是训练深度神经网络的过程,深度神经网络是在输入和输出之间有许多隐藏层的 ann(图 2-8 )。
谷歌的 Translate、AlphaGo 和微软的语音识别都通过利用深度神经网络取得了积极的成果。深度学习的成功是对隐藏层内各种连接架构进行研究的结果。一些更流行的架构是卷积神经网络(CNN)和递归神经网络(RNNs)。 8 与机器人相关的应用可能包括翻译、文本摘要、语言生成等。如果你想深入研究人工神经网络如何应用于自然语言任务,还有许多其他资源可供你探索。 9
当数据在神经元之间来回传递时,许多人工神经网络层内发生了什么?似乎没有人十分确定。例如,人们观察到谷歌的翻译创造了一种语言的中间表现形式。脸书创造可以与其他机器人或人类谈判的人工智能的项目导致人工智能创造了自己的速记甚至撒谎。这被认为是人工智能正在接管世界的一些迹象,而在现实中,尽管这些是迷人的和值得讨论的行为,但它们是训练过程的副作用。在未来,随着网络的复杂性产生更多意想不到的突发行为,这些副作用中的一些可能会变得更加令人毛骨悚然和可怕。目前,我们是安全的,不会被人工智能接管。
通过使用微软的认知工具包 10 和谷歌的张量流 11 等工具包来开发深度学习模型的便利性也是最近人工神经网络模型受欢迎程度上升的一个重要推动因素。
图 2-8
安
深度学习技术在自然语言处理任务中得到了非常成功的应用。特别是,语音识别和翻译已经从深度学习的引入中受益匪浅。事实上,微软研究院已经创建了语音识别软件,“它可以识别对话以及专业的人类转录员”, 12 ,谷歌通过利用深度学习,将其翻译算法的错误率降低了 55%至 85%。 13 然而,在意图分类等 NLU 任务中的有效性并不像深度学习宣传的那样强。这里的关键见解是,深度学习是 ML 工具包中的另一个工具,而不是银弹。
常见 NLP 任务
一般来说,NLP 处理大量的问题,其中的一个子集就是我们认为的 NLU 任务。在高层次上,主题可以与语言句法、语义和话语分析相关。并非每个 NLP 任务都与聊天机器人开发直接相关;其中一些是更相关的高阶特征的基础,例如意图分类和实体提取。
句法
语法任务通常处理与获取文本输入并将其分解成组成部分相对应的问题。这些任务中有许多是基础性的,不会被机器人直接使用。将输入分割成更小的语音单元,称为语素,并在一些语法中建立代表语音的结构就是两个例子。部分语音标记,用其词性(例如,名词、动词、代词)标记用户输入中的每个单词的过程,可用于细化用户查询。
语义学
语义任务与在自然语言输入中寻找意义有关。这些任务对聊天机器人有实际应用,包括以下内容:
-
命名实体提取:给定一些文本,确定哪些单词映射到名称以及名称的类型(例如,位置、人)。这直接适用于我们希望聊天机器人做的事情。
-
情感分析:识别某个文本的内容总体上是正面的、负面的还是中性的。这可以用于确定用户对机器人响应的情绪,重定向到人类代理,或者在机器人分析中理解用户在哪里出错以及对机器人的反应不好。
-
主题分割:给定一段文字,将其分割成与主题相关的片段,提取出那些主题。
-
关系提取:提取文本中对象之间的关系。
话语分析
话语分析是着眼于更大的自然语言结构并把它们作为一个单元来理解的过程。在这个领域,我们感兴趣的是从一个文本体的上下文中推导出意义。自动摘要用于摘要大量的内容,如公司财务报表。与聊天机器人更相关的是共同引用解析的概念。共指解析是确定多个单词所指的实体的思想。在下面的输入中, I 指的是 Szymon:
My name is Szymon. I am piling up cereal for my son.
常见的机器人 NLU 任务
如果我们计划在聊天机器人中使用 NLU,在评估解决方案时需要考虑几个特性。最基本的功能是识别定制意图和实体的能力。以下是一些需要考虑的功能:
-
多语言支持:在 NLU 实现中对多语言的支持充分说明了 NLU 平台的重要性。不同语言的优化经验可以很好地反映团队对 NLU 的整体体验。
-
包含预建模型的能力:领先总是值得的,许多系统将包含许多与特定领域相关的预建意图和实体,供您开始使用。
-
预构建的实体:我们希望现有系统能够轻松地为我们提供许多类型的实体,例如数字和日期/时间对象。
-
实体类型:应该能够指定不同类型的实体(比如列表和非列表)。
-
同义词:系统应该接受显示给实体分配同义词的能力。
-
通过主动学习进行持续培训:系统应支持利用真实用户输入作为 NLU 模型培训数据的能力。
-
虽然这些工具会为你实现某种用户界面来训练模型,但是应该有一个你可以利用的 API。
-
导出/导入:该工具应该允许您导入/导出模型,最好是像 JSON 这样的开放文本格式。
利用现有服务的另一种方法是编写自己的服务。这是一个高级话题。如果你正在读这本书,很可能你没有足够的经验和知识来完成它。有一些易于使用的 ML 包,如 scikit-learn,可能会给人这样的印象,即创建这样的东西很容易,但这需要大量的优化、调优、测试和操作。从这些通用 NLU 系统中获得正确的性能需要大量的时间、精力和专业知识。如果你对这些技术的工作原理感兴趣,网上有大量的资料供你自学。 14
基于云的 NLU 系统
来自云计算领域的好消息是,大型技术公司正在对 ML 即服务领域进行投资,我们的机器人需要的任务的基本功能可以作为服务获得。从实践的角度来看,这里有许多好处:开发人员不必关心为我们的分类问题选择最佳算法,没有必要扩展实现,有现有的高效用户界面和升级,优化是无缝的。如果你正在创建一个聊天机器人,并且需要基本的分类和实体提取功能,那么使用基于云的服务是最好的选择。
这个领域是非常动态的,这些系统的特征和焦点随着时间而变化。无论如何,在撰写本文时,以下是最佳选择,排名不分先后:
-
微软的语言理解情报服务(LUIS) :这是一个最纯粹的 LU 系统的例子,因为它完全独立于一个对话引擎。LUIS 允许开发人员添加意图和实体,对 LUIS 应用进行版本控制,在发布之前测试应用,并最终发布到测试或生产端点。此外,它还包括一些非常有趣的主动学习功能。
-
Google 的 dialog flow(Api.ai):dialog flow,以前叫 API . ai,已经有一段时间了。它允许开发人员创建 NLU 模型,定义转换流,并在满足特定条件时调用 webhooks 或云函数。可以通过 API 或通过集成到许多消息传递通道来访问对话。
-
亚马逊的 Lex :亚马逊的 Alexa 早就允许用户创建意图分类和实体提取模型。随着 Lex 的引入,Amazon 通过 bot 开发为 NLU 带来了更好的用户界面。在撰写本文时,Lex 有一些通道集成,可以通过 API 访问。像 Dialogflow 一样,Lex 允许开发人员使用 API 与机器人对话。
-
IBM Watson Conversation :另一个类似的系统 Watson Conversation 允许用户定义意图、实体和基于云的对话。该对话可通过 API 访问。在撰写本文时,还没有预构建的通道连接器;尽管存在示例,但代理必须由 bot 开发人员编写。
-
脸书的 Wit.ai : Wit.ai 已经存在了一段时间,它包括一个定义意图和实体的接口。截至 2017 年 7 月,它正在重新关注 NLU,并移除 bot 引擎部分。Wit.ai 也正在与 Facebook Messenger 生态系统更紧密地结合在一起。
在下一章的 NLU 深海潜水中,我们将利用路易斯。作为一个纯粹的 NLU 系统,LUIS 具有显著的优势,尤其是在 Bot 框架集成方面。尽管目前在 NLU 领域没有太多的基准,LUIS 仍然是市场上表现最好的 NLU 系统之一。 十五
企业空间
在企业领域还有许多其他选择,实在是不胜枚举。你可能会遇到一些更大的公司和产品名称,如 IPsoft 的 Amelia 和 Nuance 的 Nina。这一领域的产品通常都很先进,包含多年的企业级投资。一些公司专注于 IT 或其他流程自动化。一些公司关注内部用例。一些公司专注于特定的垂直行业。然而,其他公司完全专注于围绕特定用例预先构建的 NLU 模型。在一些产品中,我们将通过专有语言而不是开放语言来编写 bot 实现。
最终,企业的决策是一个典型的购买还是构建的两难选择。利基解决方案可能会持续一段时间,但有理由认为,随着 IBM、亚马逊、微软、谷歌和脸书投入这一领域的投资金额,资金支持较少的公司可能会受到阻碍。不解决一般 bot 问题的利基参与者肯定会蓬勃发展,我认为我们会发现更多的公司在专业 NLU 和 bot 解决方案方面进行创造和创新,这些解决方案由大型科技公司提供支持。
结论
我们确实看到了人工智能在 NLU 领域的民主化。几年前,bot 开发人员必须使用现有的 NLU 和 ML 库来创建一个系统,该系统可以像我们现在可用的云选项一样容易地训练和使用。现在,创建一个集成了 NLU、情感分析和共同参照的机器人非常容易。这些公司在这些系统背后的努力也不值得嘲笑;最大的技术公司正在挖掘这个领域,为他们的用户提供工具,为他们自己的平台建立对话体验。对于身为机器人开发者的你来说,这太棒了。这意味着竞争将不断推动该领域的创新,随着该领域研究的进展,分类、实体提取和主动学习方面的改进将提高 NLU 系统的性能。Bot 开发者将从所有这些 NLP 服务的加速研究和改进性能中获益。
图灵测试: https://en.wikipedia.org/wiki/Turing_test
2
问雷|对聊天机器人尤金·古斯特曼通过图灵测试的声明的回应: http://www.kurzweilai.net/ask-ray-response-to-announcement-of-chatbot-eugene-goostman-passing-the-turing-test
3
伊莱莎: https://en.wikipedia.org/wiki/ELIZA
4
以利沙伯: http://www.masswerk.at/elizabot/
5
机器学习讲解:理解有监督、无监督、强化学习,罗纳德·房龙: https://www.datasciencecentral.com/profiles/blogs/machine-learning-explained-understanding-supervised-unsupervised
6
深度强化学习可视化: http://cs.stanford.edu/people/karpathy/convnetjs/demo/rldemo.html
7
卷积神经网络(CNN):http://ufldl.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/
8
递归神经网络及其相关架构: https://en.wikipedia.org/wiki/Recurrent_neural_network
9
CNN 与 RNN 自然语言处理的比较研究: https://arxiv.org/pdf/1702.01923.pdf
10
微软认知工具包: https://www.microsoft.com/en-us/cognitive-toolkit/
11
张量流: https://www.tensorflow.org/
12
微软研究人员实现新的对话式语音识别里程碑: https://www.microsoft.com/en-us/research/blog/microsoft-researchers-achieve-new-conversational-speech-recognition-milestone
13
一个用于机器翻译的神经网络,在生产规模- https://research.googleblog.com/2016/09/a-neural-network-for-machine.html
14
机器学习,NLP:使用 scikit-learn 的文本分类,python 和 NLTK: https://towardsdatascience.com/machine-learning-nlp-text-classification-using-scikit-learn-python-and-nltk-c52b92a7c73
a
15
会话式问答系统的自然语言理解服务评估: http://www.sigdial.org/workshops/conference18/proceedings/pdf/SIGDIAL22.pdf
三、语言理解智能服务(LUIS)
LUIS 是我和我的团队广泛使用的 NLU 系统,是应用意图分类和实体提取的重要概念的完美学习工具。进入 https://luis.ai
即可进入系统。使用 Microsoft 帐户登录后,将显示一个页面,描述如何构建 LUIS 应用。这很好地介绍了我们将在本章中完成的不同任务。完成后,点击底部附近的创建路易斯应用按钮。您将进入 LUIS 申请页面。点击新建 app 按钮,输入名称;将为您创建一个 LUIS 应用,您可以在其中创建一个新模型,并训练、测试和发布它,以便在准备就绪时通过 API 使用。
在这一章中,我们将创建一个 LUIS 应用,让我们为日历礼宾机器人供电。日历礼宾机器人将能够添加、编辑和删除约会;总结我们的日历;并在一天内找到空房。这项任务将带我们浏览 LUIS 的各种功能。到本章结束时,我们将开发出一个 LUIS 应用,它不仅可以用来创建一个有用的机器人,而且可以不断进化,表现得更好。
首先,让我们在 LUIS 中创建新的应用。当我们点击新建 app 按钮时,会弹出如图 3-1 所示的窗口。填写名称和描述字段。路易斯不仅用英语工作,还支持其他文化。不同的语言需要不同的语言模型和优化。该选择通知 LUIS 您的应用将使用哪种文化,以便可以利用这些优化。在撰写本文时,LUIS 支持巴西葡萄牙语、中文、荷兰语、英语、法语、加拿大法语、德语、意大利语、日语、韩语、西班牙语和墨西哥西班牙语。随着系统的成熟,可能会引入更广泛的文化支持。
图 3-1
创建新的 LUIS 应用
一旦应用被创建,你将会看到 LUIS 界面的构建部分(图 3-2 )。如您所见,除了 None 意图之外,它是空的。一旦我们开始训练意图,我们将进入那个。您还会看到“审阅端点话语”链接。这是 LUIS 的主动学习特性,我们将在后续章节中探讨。
图 3-2
LUIS 构建部分
请注意,在撰写本文时,LUIS 应用仅限于 500 个 intents、30 个实体和 50 个 list 实体。当 LUIS 第一次发布时,限制更接近于 10 个意图和 10 个实体。最新的数字总是可以在网上找到。 1
在页面顶部,您将看到您的应用名称、活动版本以及 LUIS 的仪表板、构建、发布和设置部分的链接。我们还可以从界面中轻松地训练和测试模型。在构建日历礼宾应用时,我们将探索 LUIS 的每个部分。
分类意图
我们在前一章中介绍了意图分类的概念,但这将是我们第一次在实践中深入探讨。再次重申,我们想创建一个 LUIS 应用,让我们添加、编辑或删除日历条目;显示日历的摘要;并在我们的日历中查看是否有空。我们将创建以下意图:
-
AddCalendarEntry
-
RemoveCalendarEntry
-
编辑日历目录
-
ShowCalendarSummary
-
检查可用性
我们在构建部分停止了。在左侧窗格中,我们选择了 Intents 项。系统中只有一个意图:没有。每当用户的输入与任何其他意图都不匹配时,就会解析这个意图。我们可以在我们的机器人中使用这一点来告诉用户,他们正在试图问机器人专业领域之外的问题,并提醒他们机器人有什么能力。
使用 LUIS 的典型工作流程是添加一个意图,并向 LUIS 展示几个代表该意图的示例话语。这正是我们要做的。图 3-3 说明了创建意图的过程。UI 允许我们在自由文本输入字段中输入话语。我们输入一个样本,按 enter 键,输入另一个样本,按 Enter 键,等等。一旦我们添加了足够多的示例话语,我们点击保存按钮,我们就完成了意图(图 3-4 )。
图 3-4
为 AddCalendarEntry 目的添加话语
图 3-3
添加新的 LUIS 意向
请注意,用户界面允许我们搜索话语、删除话语、将意图重新分配给话语,并以几种不同的格式显示数据。您可以随意探索这一功能。
在我们添加其余的意图之前,让我们看看到目前为止我们是否可以训练和测试应用。注意右上方的火车按钮有一个红色的指示灯;这意味着应用有尚未训练的变化。请点击“培训”按钮。您的请求将被发送到 LUIS 服务器,您的应用将排队等待培训。您可能会注意到出现一条消息,通知您 LUIS 正在训练您的应用,并且“0/2 已完成”2 是您的应用当前包含的分类器模型的数量。一个用于 None intent,一个用于 AddCalendarEntry。训练完成后,训练按钮指示器将变为绿色,表示应用是最新的。
意图界面还为我们提供了关于最新训练的应用对每个话语的哪个意图得分最高的信息(图 3-5 )。这段数据很重要,因为我们可以很容易地看到,当一个应用被训练为将一个话语分类为一个意图,但将最高分分配给不同的意图。训练和结果意图之间的差异通常表明在一个或多个模型中有东西在错误的方向上影响结果。我们将在本章的故障排除部分讨论这种情况和其他情况。目前,似乎我们所有的话语都已经被成功地训练,在 AddCalendarEntry 意图上的得分为 1,在 None 意图上的得分在 0.05 到 0.07 之间(见图 3-6);这些数字可能会根据您的确切发言以及 LUIS 工程团队所做的更改而有所不同。
图 3-6
我们应用中每个意图的话语得分
图 3-5
AddCalendarEntry 意向得分最高的意向(也称为预测意向)
训练完成后,我们可以使用“训练”按钮旁边的“测试”幻灯片来测试模型,并查看它们如何响应不同的输入(图 3-7 )。批量测试面板链接允许执行更大量的测试。出于我们的目的,我们将坚持交互模式。
图 3-7
交互式测试我们的模型
LUIS 的工作方式是,它在我们应用的训练阶段训练的所有模型中运行每个输入。对于每个模型,我们都会得到一个介于 0 到 1 之间的分数。得分最高的意向会突出显示。请注意,分数并不对应于概率。分数取决于所使用的算法,通常表示输入与意图的理想形式之间的距离。如果 LUIS 在一个以上的意向上给一个输入打了相似的分数,我们可能需要做一些额外的培训。
在训练和测试我们的应用后,它似乎表现良好,直到我们试图打破它。然后,它很快开始看起来不对劲。图 3-8 说明了这一点。
图 3-8
测试古怪和荒谬的输入
哎呀。这并不十分令人惊讶。我们已经用有限数量的话语训练了一个意图。我们向无意图者提供了零样本话语。这是一个训练不足的模型会表现出来的行为。让我们添加一些这些愚蠢的短语到无意图,训练,再测试。你可以尝试添加一些无意义的测试用例,如图 3-9 所示。应该效果更好。我们现在不会解决像这样的所有问题。这需要一些时间、奉献和用户反馈。但我们应该意识到,训练应用不应该知道的东西和训练应用应该知道的东西一样重要。
图 3-9
我们取得了一些进展!
接下来,我们将添加剩余的意图。图 3-10 、图 3-11 、图 3-12 和图 3-13 显示了 CheckAvailability、EditCalendarEntry、DeleteCalendarEntry 和 ShowCalendarSummary 意图的一些示例语句。
图 3-13
ShowCalendarSummary 意图示例话语
图 3-12
DeleteCalendarEntry 意图示例话语
图 3-11
EditCalendarEntry 意图示例话语
图 3-10
检查可用性意图示例话语
一旦所有的意图都被创建并用样本话语填充,我们就训练并确认预测的意图看起来是准确的。你可能会注意到,尽管每个话语的得分最高的意图是正确的,但得分相当低(图 3-14 )。这是我们进一步培训应用的机会。事实上,我们永远不能假设我们可以用如此有限的词汇和数据集来训练一个被识别的意图。正确对待 NLU 需要耐心、奉献和思考。在接下来的练习中,我们将向我们的应用添加更多话语。
图 3-14
分数看起来不太好。这是一个进一步训练的机会。
练习 3-1
训练路易斯意图
前面的示例显示了我们训练的意图的一些示例输入。你的任务是创建一个 LUIS 应用,创建相同的意图集,并用足够的话语样本训练该应用,以便所有意图得分都在 0.80 以上。
-
创建以下意图,并为每个意图输入至少十个示例话语:
-
AddCalendarEntry
-
RemoveCalendarEntry
-
编辑日历目录
-
ShowCalendarSummary
-
检查可用性
-
-
给无意图增加一些训练。关注在这个应用中没有意义或者没有意义的输入,比如“我喜欢咖啡”这是有意义的,但不适用于此应用。
-
训练 LUIS 应用,并通过访问意图页面观察每个话语的预测分数。也可以使用交互式测试选项卡。
-
分数是多少?它们是否高于 0.80?更低?不断给每一个意图添加示例话语以提高分数。请确保经常训练应用,并重新加载意图话语,以查看更新的分数。需要多少话语才能让你对自己的应用充满信心?
一旦你完成了这些练习,你就积累了训练和测试路易斯意图的经验。
发布应用
显然,我们还没有完成开发我们的应用。有相当多的东西丢失,路易斯的许多细节我们还没有探索。我们也还没有看到任何真实的用户数据。但是,我们可以并行开发 LUIS 应用和消费应用。获取我们训练过的应用并通过 HTTP 访问它的过程被称为发布我们的应用。
在应用的顶部导航栏上,在构建部分的旁边,我们可以找到发布部分。当我们点击这个按钮时,我们会看到一个页面,允许我们部署 LUIS 应用(图 3-15 )。LUIS 允许我们在两个部署位置之一发布应用:试运行或生产。Staging 是指在我们仍在开发和测试 LUIS 应用时使用。生产插槽旨在供生产应用使用。这两个插槽背后的想法是,您可以将 LUIS 应用的先前稳定版本部署到生产中,同时在 staging 插槽中开发新的应用功能。
图 3-15
LUIS 发布页面
我们将从“发布到”下拉列表中选择分段插槽。一旦发布,我们就可以通过 HTTP 端点访问应用。
在我们使用 cURL(一个通过 HTTP(以及许多其他协议)传输数据的命令行工具)测试最终的端点之前,您可能已经注意到在 publish 设置下面有一个 Add Key 按钮和一组用于几个部署区域的键。当访问 LUIS 应用时,我们必须提供一个密钥,这是 LUIS 可以为 API 使用向我们收费的方式。LUIS 被部署到几个地区;一个键必须与一个区域相关联。密钥是使用微软的 Azure 门户创建的。Azure 是微软的云服务保护伞。我们将在第五章利用它来注册和部署一个机器人。要将密钥与应用相关联,我们必须使用 Add Key 按钮。幸运的是,LUIS 提供了一个免费的启动密钥,可以用来攻击发布在 Staging slot 中的应用。
一旦我们发布到暂存槽,就会发生一些事情。我们现在有了关于应用版本和上次发布时间的信息。Starter_Key 下的 URL 现在可以使用了。我们可能会通过 URL 查询参数启用详细结果(我们稍后将对此进行研究)或 Bing 拼写检查集成(我们将在本章稍后讨论)。让我们仔细看看网址。
https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/3a26be6f-6227-4136-8bf4-c1074c9d14b6?
subscription-key=a9fe39aca38541db97d7e4e74d92268e&
staging=true&
verbose=true&
timezoneOffset=0&
q=
URL 的第一行是美国西部地区 Azure 认知服务的服务端点,特别是我们的 LUIS 应用。以下是查询参数:
-
订阅密钥,在这种情况下是启动密钥。这个密钥也可以通过 Ocp-Apim-Subscription-Key 头传递。
-
指示是使用分段插槽还是生产插槽的标志。不包括此参数假定生产插槽。
-
详细标志,指示是返回所有意向及其得分,还是仅返回得分最高的意向。
-
时区偏移有助于时间标记日期时间解析,这是我们在探索内置日期时间实体时将深入探讨的主题。
-
q 表示用户的查询。
我们可以通过使用 curl 发出请求并查看响应来使用 API。在其核心,curl 是一个命令行工具,通过各种协议传输数据。我们将用它在 HTTPS 传输数据。你可以在 https://curl.haxx.se/
找到更多信息。我们可以使用的命令如下。注意,我们将订阅密钥作为 HTTP 头传递。
curl -X GET -G -H "Ocp-Apim-Subscription-Key: a9fe39aca38541db97d7e4e74d92268e" -d staging=true -d verbose=true -d timezoneOffset=0 "https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/3a26be6f-6227-4136-8bf4-c1074c9d14b6" --data-urlencode "q=hello world"
该查询产生以下 JSON。它为我们的 LUIS 应用中的每个意向给出了分数。
{
"query": "hello world",
"topScoringIntent": {
"intent": "None",
"score": 0.24031198
},
"intents": [
{
"intent": "None",
"score": 0.24031198
},
{
"intent": "DeleteCalendarEntry",
"score": 0.1572571
},
{
"intent": "AddCalendarEntry",
"score": 0.123305522
},
{
"intent": "EditCalendarEntry",
"score": 0.0837310851
},
{
"intent": "CheckAvailability",
"score": 0.07568088
},
{
"intent": "ShowCalendarSummary",
"score": 0.0100482805
}
],
"entities": []
}
你可能会想,哇,我们刚刚了解到我们可以有多达 500 个意图,所以这个响应的大小将是荒谬的。你这样想是非常正确的(尽管 gzip 在这里肯定会有所帮助)!将 verbose query 参数设置为 false 会产生一个非常紧凑的 JSON 列表。
{
"query": "hello world",
"topScoringIntent": {
"intent": "None",
"score": 0.24031198
},
"entities": []
}
一旦我们准备好部署到生产中,我们将把 LUIS 应用发布到生产插槽中,并从 URL 请求中删除 staging 参数。实现这一点最简单的方法就是让您的开发和测试配置文件指向登台槽 URL,让生产配置指向生产槽 URL。
当然,您也可以使用任何其他您熟悉的 HTTP 工具。此外,微软提供了一个易于使用的控制台来测试在线 API 文档中的 LUIS API。 2
练习 3-2
发布 LUIS 应用
现在,您将发布练习 3-1 中的 LUIS 应用,并通过 curl 访问它。
-
按照上一节中的步骤,将 LUIS 应用发布到登台槽中。
-
使用 curl 从 LUIS API 为您输入的示例话语和您能想到的其他话语获取预测意图的 JSON。
-
确保 curl 命令使用您的应用 ID 和 starter 键。
将应用发布到插槽中的过程非常简单。习惯使用 curl 测试 HTTP 端点非常重要,因为您通常需要访问 API 来检查 LUIS 的结果。
提取实体
到目前为止,我们已经开发了一个简单的基于意图的 LUIS 应用。但是除了它能够告诉我们的机器人用户的意图,我们真的不能做太多。LUIS 给我们提供用户想要添加日历条目的信息是一回事,但是最好能够告诉我们日期和时间、地点、持续时间以及与谁一起。我们可以开发一个机器人,每当它看到 AddCalendarEntry 时,就以线性顺序向用户询问所有这些细节。然而,这是乏味的,并且忽略了这样一个事实,即用户很可能向机器人呈现这样的话语:
"add meeting with Huck tomorrow at 6pm"
要求用户重新输入所有这些数据将是糟糕的用户体验。机器人应该立即知道“明天下午 6 点”的日期时间值是多少,并且应该将“Huck”添加到邀请中。
让我们从基础开始。我们如何确保“明天下午 6 点”“一周后”和“下个月”是机器可读的吗?这就是实体识别的用武之地。幸运的是,LUIS 配备了许多内置实体,我们可以将它们添加到我们的应用中。通过这样做,日期时间提取将“正常工作”
如果我们回到 LUIS 应用的构建部分并单击实体标题,我们将看到一个空的实体列表(图 3-16 )。我们可以添加三种不同类型的实体。现在,我们将简单地添加一个预构建的实体。我们将在后面的章节中讨论普通实体和预构建的域实体。
图 3-16
空实体页面
预先构建的实体是预先训练的定义,可以在话语中识别。实体在输入中被自动标记,我们不能改变预构建实体的识别方式。它们中有大量的逻辑,我们可以在我们的应用中利用,在构建我们自己的实体之前,最好理解微软已经构建了什么。
有许多不同的预构建实体。并非所有实体在所有支持的区域性中都可用。LUIS 文档提供了跨文化可用的预构建实体的详细信息 3 (图 3-17 )。
图 3-17
LUIS 跨不同文化的内置实体支持
其中一些实体包括所谓的值解析。值解析是获取文本输入并将其转换为计算机可以解释的值的过程。例如,“十万”应解析为 100000,“明年 5 月 10 日”应解析为 05/10/2019,依此类推。
您可能已经注意到 LUIS 的 JSON 结果包含了一个名为 entities 的空数组。这是用户输入中识别的所有实体的占位符。LUIS 应用可以识别输入中任意数量的实体。每个实体的格式如下:
{
"entity": "[entity text]",
"type": "[entity type]",
"startIndex": [number],
"endIndex": [number],
"resolution": {
"values": [
{
"value": "[machine readable string of resolved value]"
}
]
}
}
根据检测到的实体类型,解析对象可能包含额外的属性。让我们看看不同的预建实体类型,它们允许我们做什么,以及 LUIS API 的结果是什么样子。
年龄、维度、金钱和温度
年龄实体允许我们检测年龄表达式,如“五个月大”、“100 岁”和“两天大”result 对象包括数字格式的值和一个单位参数,如日、月或年。
{
"entity": "five months old",
"type": "builtin.age",
"startIndex": 0,
"endIndex": 14,
"resolution": {
"unit": "Month",
"value": "5"
}
}
使用尺寸实体可以检测任何长度、重量、体积和面积度量。输入可以从“10 英里”到“1 厘米”到“50 平方米”不等像年龄实体一样,结果解析将包括一个值和一个单位。
{
"entity": "two milliliters",
"type": "builtin.dimension",
"startIndex": 0,
"endIndex": 14,
"resolution": {
"unit": "Milliliter",
"value": "2"
}
}
货币实体可以帮助我们检测输入中使用的货币。分辨率再次包括一个单元和值属性。
{
"entity": "12 yen",
"type": "builtin.currency",
"startIndex": 0,
"endIndex": 5,
"resolution": {
"unit": "Japanese yen",
"value": "12"
}
}
温度实体帮助我们检测温度,并在分辨率中包含一个单位和值属性。
{
"entity": "98 celsius",
"type": "builtin.temperature",
"startIndex": 0,
"endIndex": 9,
"resolution": {
"unit": "C",
"value": "98"
}
}
日期时间 V2
DatetimeV2 是一个强大的分层实体,它取代了之前的 datetime 实体。分层实体定义类别及其成员;当某些实体相似且密切相关,但具有不同的含义时,使用它是有意义的。datetimeV2 实体还尝试以机器可读的格式解析日期时间,如 TIMEX(代表“时间表达式”);TIMEX3 是 TimeML 的一部分)和以下格式:yyyy:MM:dd、HH:mm:ss 和 yyyy:MM:dd HH:mm:ss(分别表示日期、时间和日期时间)。下面是一个基本的例子。
{
"entity": "tomorrow at 5pm",
"type": "builtin.datetimeV2.datetime",
"startIndex": 0,
"endIndex": 14,
"resolution": {
"values": [
{
"timex": "2018-02-18T17",
"type": "datetime",
"value": "2018-02-18 17:00:00"
}
]
}
}
除了前面示例中的 datetime 子类型之外,DatetimeV2 实体还可以检测各种子类型。以下是示例响应列表。
这将显示 builtin.datetimeV2.date,其中包含“昨天”、“下周一”和“2015 年 8 月 23 日”等短语:
{
"entity": "yesterday",
"type": "builtin.datetimeV2.date",
"startIndex": 0,
"endIndex": 8,
"resolution": {
"values": [
{
"timex": "2018-02-16",
"type": "date",
"value": "2018-02-16"
}
]
}
}
这将显示 builtin.datetimeV2.time,其中包含诸如“下午 1 点”、“上午 5 点 43 分”、“8 点”或“上午 8 点半”之类的短语:
{
"entity": "half past eight in the morning",
"type": "builtin.datetimeV2.time",
"startIndex": 0,
"endIndex": 29,
"resolution": {
"values": [
{
"timex": "T08:30",
"type": "time",
"value": "08:30:00"
}
]
}
}
这将显示 builtin.datetimeV2.daterange,其中包含诸如“下周”、“去年”或“2 月 1 日至 2 月 20 日”等短语:
{
"entity": "next week",
"type": "builtin.datetimeV2.daterange",
"startIndex": 0,
"endIndex": 8,
"resolution": {
"values": [
{
"timex": "2018-W08",
"type": "daterange",
"start": "2018-02-19",
"end": "2018-02-26"
}
]
}
}
这显示了 building.datetimeV2.timerange,带有诸如“1 到 5p”和“1 到 5pm”的短语:
{
"entity": "from 1 to 5pm",
"type": "builtin.datetimeV2.timerange",
"startIndex": 0,
"endIndex": 12,
"resolution": {
"values": [
{
"timex": "(T13,T17,PT4H)",
"type": "timerange",
"start": "13:00:00",
"end": "17:00:00"
}
]
}
}
这将显示 builtin . datetime v2 . datetime range,其中包含诸如“明天早上”或“昨晚”之类的短语:
{
"entity": "tomorrow morning",
"type": "builtin.datetimeV2.datetimerange",
"startIndex": 0,
"endIndex": 15,
"resolution": {
"values": [
{
"timex": "2018-02-19TMO",
"type": "datetimerange",
"start": "2018-02-19 08:00:00",
"end": "2018-02-19 12:00:00"
}
]
}
}
这显示了 builtin.datetimeV2.duration,带有诸如“一小时”、“20 分钟”或“一整天”的短语该值以秒为单位进行解析。
{
"entity": "an hour",
"type": "builtin.datetimeV2.duration",
"startIndex": 0,
"endIndex": 6,
"resolution": {
"values": [
{
"timex": "PT1H",
"type": "duration",
"value": "3600"
}
]
}
}
builtin.datetimeV2.set 类型表示一组日期,可以通过包含“每日”、“每月”、“每周”或“每周四”这样的短语来检测这种类型的分辨率不同,因为没有单个值来表示一个集合。timex 分辨率将通过两种方式之一进行解析。首先,timex 字符串将遵循模式 P[n][u],其中[n]是一个数字,而[u]是日期单位,如 D 代表天,M 代表月,W 代表周,Y 代表年。意思是“每[n] [u]个单位。”P4W 表示每四周,P2Y 表示每隔一年。第二个 timex 解析是一个日期模式,其中 Xs 代表任意值。例如,XXXX-10 表示每年十月,XXXX-WXX-6 表示一年中任何一周的每个星期六。
{
"entity": "daily",
"type": "builtin.datetimeV2.set",
"startIndex": 0,
"endIndex": 4,
"resolution": {
"values": [
{
"timex": "P1D",
"type": "set",
"value": "not resolved"
}
]
}
}
{
"entity": "every saturday",
"type": "builtin.datetimeV2.set",
"startIndex": 0,
"endIndex": 13,
"resolution": {
"values": [
{
"timex": "XXXX-WXX-6",
"type": "set",
"value": "not resolved"
}
]
}
}
如果在日期和/或时间上有歧义,LUIS 将返回多个决议来证明选项。例如,日期的不确定性意味着如果今天是 7 月 20 日,我们输入“7 月 21 日”,系统将返回今年和去年的 7 月 21 日。同样,如果您的查询没有指定上午或下午,LUIS 将返回这两个时间。你可以在这里看到这两种情况:
{
"entity": "july 21",
"type": "builtin.datetimeV2.date",
"startIndex": 0,
"endIndex": 6,
"resolution": {
"values": [
{
"timex": "XXXX-07-21",
"type": "date",
"value": "2017-07-21"
},
{
"timex": "XXXX-07-21",
"type": "date",
"value": "2018-07-21"
}
]
}
}
{
"entity": "tomorrow at 5",
"type": "builtin.datetimeV2.datetime",
"startIndex": 0,
"endIndex": 12,
"resolution": {
"values": [
{
"timex": "2018-02-19T05",
"type": "datetime",
"value": "2018-02-19 05:00:00"
},
{
"timex": "2018-02-19T17",
"type": "datetime",
"value": "2018-02-19 17:00:00"
}
]
}
}
日期时间 V2 实体是强大的,并真正展示了路易斯 NLU 的一些伟大的功能。
电子邮件、电话号码和网址
这三种类型都是基于文本的。LUIS 可以识别用户输入中何时存在其中之一。由 LUIS 来做这件事很方便,而不是必须在我们的系统中实现正则表达式逻辑。我们在这里演示三种类型:
{
"entity": "srozga@bluemetal.com",
"type": "builtin.email",
"startIndex": 0,
"endIndex": 19
}
{
"entity": "212-222-1234",
"type": "builtin.phonenumber",
"startIndex": 0,
"endIndex": 11
}
{
"entity": "https://luis.ai",
"type": "builtin.url",
"startIndex": 0,
"endIndex": 14
}
数字、百分比和序数
LUIS 也可以为我们提取和解析数字和百分比。用户输入可以是数字或文本格式。它甚至可以处理像“38.5”这样的输入
{
"entity": "one hundred",
"type": "builtin.number",
"startIndex": 0,
"endIndex": 10,
"resolution": {
"value": "100"
}
}
{
"entity": "52 percent",
"type": "builtin.percentage",
"startIndex": 0,
"endIndex": 9,
"resolution": {
"value": "52%"
}
}
序数实体允许我们识别文本或数字形式的序数。
{
"entity": "second",
"type": "builtin.ordinal",
"startIndex": 0,
"endIndex": 5,
"resolution": {
"value": "2"
}
}
实体培训
让我们回到我们的应用,应用一些我们刚刚学到的东西。因为我们正在编写一个与日历相关的应用,所以最明显的首选预建实体是 datetimeV2。在实体页面,点击“管理预置实体”,选择日期时间 V2,如图 3-18 所示。
图 3-18
向模型中添加 datetimeV2 实体
添加实体后,我们应该训练我们的模型。在交互测试 UI 中,当我们输入“添加明天下午 5 点的日历条目”时,我们应该会看到如图 3-19 所示的结果。
图 3-19
datetimeV2 实体处于活动状态!
那很容易。我们再次将应用发布到登台槽。使用 curl 运行相同的查询,我们会收到以下 JSON:
{
"query": "add calendar entry tomorrow at 5pm",
"topScoringIntent": {
"intent": "AddCalendarEntry",
"score": 0.42710492
},
"entities": [
{
"entity": "tomorrow at 5pm",
"type": "builtin.datetimeV2.datetime",
"startIndex": 19,
"endIndex": 33,
"resolution": {
"values": [
{
"timex": "2018-02-19T17",
"type": "datetime",
"value": "2018-02-19 17:00:00"
}
]
}
}
]
}
太好了。我们现在可以在任何意图中使用日期时间实体。这将与我们所有应用的意图相关,而不仅仅是 AddCalendarEntry。此外,我们将继续添加电子邮件预构建实体,重新培训,并再次发布到暂存槽。现在我们可以试着说“明天下午 5 点见”,来得到我们想要的结果。
{
"query": "meet with szymon.rozga@gmail.com at 5p tomorrow",
"topScoringIntent": {
"intent": "AddCalendarEntry",
"score": 0.3665758
},
"entities": [
{
"entity": "szymon.rozga@gmail.com",
"type": "builtin.email",
"startIndex": 10,
"endIndex": 31
},
{
"entity": "5p tomorrow",
"type": "builtin.datetimeV2.datetime",
"startIndex": 36,
"endIndex": 46,
"resolution": {
"values": [
{
"timex": "2018-02-19T17",
"type": "datetime",
"value": "2018-02-19 17:00:00"
}
]
}
}
]
}
练习 3-3
添加日期时间和电子邮件实体支持
在本练习中,您将在您目前正在使用的 LUIS 应用上启用预构建实体。
-
将 email 和 datetimev2 预构建实体添加到您的应用中。训练你的应用。
-
进入您的 AddCalendarEntry 意图,尝试添加几个带有日期时间和电子邮件表达式的话语。请注意,LUIS 为您突出显示了这些实体。
-
将 LUIS 应用发布到暂存槽中。
-
使用 curl 检查生成的 JSON。
预构建的实体非常容易使用。作为进一步的练习,向您的模型中添加一些其他预构建的实体,以了解它们如何工作以及它们如何在不同类型的输入中被拾取。如果您想防止 LUIS 识别它们,只需将它们从应用的实体中移除即可。
自定义实体
预构建的实体可以为我们的模型做很多事情,而不需要任何额外的培训。如果我们需要的一切都可以由现有的预构建实体提供,那将是令人惊讶的。在我们的日历应用的例子中,根据定义,日历条目包括一些我们感兴趣的属性。
首先,我们通常希望给会议一个主题(不仅仅是“会见 Bob”)和一个地点。两者都是会议主题和地点的任意字符串。我们如何做到这一点?
LUIS 让我们能够训练自定义实体来检测这些概念,并从用户输入中提取它们的值。这就是实体提取算法真正发挥作用的地方;我们向 LUIS 展示了单词何时应该被识别为实体,何时应该被忽略的例子。NLP 算法考虑了上下文。例如,给定多个话语样本,我们可以教路易斯,并确保它不会将星巴克与莫比·迪克中的角色星巴克混淆。
在 LUIS 中,我们可以利用四种不同类型的定制实体:简单、复合、分层和列表。让我们逐一检查。
简单实体
简单的自定义实体是一种实体,如日历条目主题或预建的电子邮件、电话号码和 URL 实体。基于用户输入的一个片段在话语中的位置及其周围单词的上下文,可以将其识别为所述类型的实体。LUIS 使创建和训练这些类型的实体变得很容易。让我们创建日历主题实体。
比方说,当我们告诉日历机器人条目的主题名称时,我们想弄清楚。假设我们想要接受输入,比如“下午 5 点与 Kim 会面,讨论抵押申请。”在这个例子中,主题是“抵押申请”让我们把这个放好。
导航到实体页面,点击“创建新实体”按钮,创建一个名为 Subject 的新的简单实体,如图 3-20 所示。
图 3-20
创建新的简单实体
单击完成后,该条目将被添加到应用的实体列表中。训练实体的过程发生在与训练意图相同的界面中。让我们导航到 AddCalendarEntry 意图,并添加语句“在下午 5 点与 Kim 会面,讨论抵押申请”,如图 3-21 所示。请注意,这只是一个普通的说法,没有实体被识别。
图 3-21
添加话语。路易斯对科目还不了解。
我们现在将鼠标悬停在抵押贷款和申请词语上,注意到路易斯允许我们选择词语。点击抵押,然后点击申请,这样路易斯就选择了短语“抵押申请”。弹出窗口将列出应用中的所有自定义实体类型。选择主题。LUIS 的话语现在应该如图 3-22 所示。
图 3-22
突出显示和分配的实体
保存话语并训练您的应用。在这一点上,路易斯还不太擅长辨别对象。毕竟我们只是提供了一个例子,实体识别比意图分类更难做好。它需要更多的样本。我们可以在话语编辑器中为添加日历条目输入更多的话语。一些样品如图 3-23 所示。
图 3-23
添加更多带有主题的话语。在用一个样本训练 LUIS 之后,没有一个被识别出来。
请注意,没有确定任何受试者。我们来强化一下这个概念。系统开始识别实体需要相当多的例子。我添加了十多条在话语中某处有某种主题的话语,如图 3-24 所示。此外,一定要标记你自己添加的任何话语的主题。我称之为“让路易斯屈从于你的意志”的过程与其说是一门科学,不如说是一门艺术。要记住的关键点是,将会有一个拐点,算法开始意识到,根据统计推断,一个词后面的东西总是一个实体,直到其他一些关键词。想象一个你正在慢慢尝试平衡的天平。我们的话语应该精心制作,以确保我们捕捉到尽可能多的变化,以展示给路易斯。通常,每个变体还需要包括一些样本,以真正捕捉算法可以在话语的上下文中找到特定实体的本质。
图 3-24
用许多不同风格的主题话语训练路易斯。请注意,我们将“实体”下拉列表右侧的切换更改为“令牌”视图。这允许我们看到哪些令牌被识别为实体。
在训练这个数据集之后,我们看到交互式测试工具在识别实体方面越来越好。我输入“嗨,让我们在 1:45p 见面讨论草坪护理和口琴”(不要问我是怎么想到的…),并收到图 3-25 中的结果。我们正在取得良好的进展。但是,如果我们开始输入不同长度和变化的输入,LUIS 可能无法正确识别实体。这只是意味着我们需要进一步训练我们的实体模型。我们将把这作为一个练习留给读者。
图 3-25
我们的模型现在在一些测试案例中识别主题。太好了。
我们现在已经很好地掌握了日历主题实体,尽管可能有许多情况还不能工作。说实话,除非你有一个好的测试阶段,否则你不可能捕捉到用户提问的所有不同类型的方式。这就是 LUIS 应用开发的方式。发布这个应用时,值得看一看生成的 JSON。
{
"query": "hi let's meet about lawn care and harmonicas at 1:45pm",
"topScoringIntent": {
"intent": "AddCalendarEntry",
"score": 0.8653278
},
"entities": [
{
"entity": "1:45pm",
"type": "builtin.datetimeV2.time",
"startIndex": 48,
"endIndex": 53,
"resolution": {
"values": [
{
"timex": "T13:45",
"type": "time",
"value": "13:45:00"
}
]
}
},
{
"entity": "lawn care and harmonicas",
"type": "Subject",
"startIndex": 20,
"endIndex": 43,
"score": 0.587688446
}
]
}
请注意,时间实体是按照预期进行标识的。主体实体返回相关的实体值。它还会返回一个分数。在这种情况下,分数也是与意图分数相似的度量;这是与理想实体距离的度量。与 intents 不同,LUIS 不会返回您的所有实体及其分数。LUIS 将只返回分数高于阈值的简单和分层实体。对于内置实体,该分数是隐藏的。
对实体进行定型的好处是,即使带有实体的样本是在 AddCalendarEntry 意图中定义的,它们也可以用于其他意图。意图和实体并不直接联系在一起。我可以说“取消关于奥林匹克曲棍球的会议”,它的工作如图 3-26 所示。
图 3-26
一个意向中的实体培训可以延续到其他意向中
另一个观察结果是在识别 DeleteCalendarEntry 意图方面得分较低。我们在 AddCalendarEntry 意图中添加了更多的语句,但是 DeleteCalendarEntry 和 EditCalendarEntry 的示例要少得多。花些时间来改善这一点。在我们继续之前,为我们的新主题实体添加一些替换的措辞和例子。
练习 3-4
培训主题实体,强化我们的 LUIS App
在本练习中,我们将通过训练 LUIS 应用进行一些额外的训练来改进它。
-
按照上一节中的说明,添加一个主题实体。
-
在你的意图中加入话语来支持主题实体。经常训练和测试,看看你的进步。
-
LUIS 开始时至少要准备 25 到 30 个样本。确保传达不同表达方式的多个实例。
-
确保你所有的意图都引起了你的注意。确保每个意向有 15 到 20 个样本。在每个意向中包含实体。
-
训练 LUIS 应用并将其发布到暂存槽中。
-
使用 curl 检查生成的 JSON。
训练自定义实体,尤其是那些在定位和上下文方面有点模糊的实体,可能具有挑战性,但是经过一些练习,您将开始看到 LUIS 提取它们的能力中的模式。注意需要明确训练的东西:主题中的字数、带有单词和的主题、后跟日期时间的主题等等。你可能已经注意到了样品数量的明确提及。这些只是起点。像路易斯这样的 NLU 系统拥有越多的样本数据就越好。不要忽视这一点。如果 LUIS 的行为不符合您的预期,很可能不是 LUIS 的性能问题,而是您的应用需要更多的培训。
我们计划添加的第二个实体是位置实体。让我们创建一个新的简单的定制实体,并将其命名为 Location。像主题实体一样,位置将是一个自由文本实体,所以我们需要用许多样本来训练 LUIS。
我们将再次尝试在 AddCalendarEntry 意图中添加语句。我们需要添加以下形式的话语:
Meet with kim to talk about {Subject} at {Location}
Meet about {Subject} at {Location}
Add entry with teddy for {Subject} at {Location}
Add meeting at {Location}
Meet at {Location}
Meet in {Location} at {Subject}
你明白了。您还应该在这些话语中添加日期时间实例。训练位置将更加棘手,因为我们正在教 LUIS 区分位置和主题,这两个概念只需要 LUIS 开始区分的大量数据,因为这是两个自由文本实体。最后,我添加了 30 多个句子,要么只包含一个位置,要么包含一个与其他实体相结合的位置。经过这么多的训练,我们取得了不错的成绩。我可以输入“明天晚上 8 点在餐馆见面吃晚饭”,然后得到下面的 JSON 结果:
{
"query": "meet for dinner at the diner tomorrow at 8pm",
"topScoringIntent": {
"intent": "AddCalendarEntry",
"score": 0.979418
},
"entities": [
{
"entity": "tomorrow at 8pm",
"type": "builtin.datetimeV2.datetime",
"startIndex": 29,
"endIndex": 43,
"resolution": {
"values": [
{
"timex": "2018-02-19T20",
"type": "datetime",
"value": "2018-02-19 20:00:00"
}
]
}
},
{
"entity": "the diner tomorrow",
"type": "Location",
"startIndex": 19,
"endIndex": 36,
"score": 0.392795324
},
{
"entity": "dinner",
"type": "Subject",
"startIndex": 9,
"endIndex": 14,
"score": 0.5891273
}
]
}
我们建议你花些时间来进一步强化这些实体。这将是一次很好的经历,能够真正理解自然语言的复杂性和模糊性,并训练像 LUIS 这样的 NLU 系统。
练习 3-5
训练定位实体
在本练习中,您将在 LUIS 应用中添加位置实体。你会发现这比主题实体本身花费的时间要长一些。
-
按照上一节中的说明,添加一个主题实体。
-
将话语添加到 AddCalendarEntry 中以支持位置实体。经常训练和测试,看看你的进步。
-
LUIS 的目标是从 35 到 40 个样本开始,可能更多。由于您的意图支持更多的实体,您可能需要向 LUIS 提供更多的样本,以便正确区分。当你添加话语时,不断地训练和测试,看看 LUIS 是如何学习的。确保使用许多变体和例子。
-
将 LUIS 应用发布到暂存槽中。
-
使用 curl 检查生成的 JSON。
当单个发声包含许多实体时,这种练习在加强实体解析方面应该是一种很好的体验。
复合实体
恭喜你。到目前为止,我们所做的工作是 LUIS 能够完成的工作的重要部分。使用所描述的意图分类和简单的实体提取技术,我们可以开始处理我们的日历应用。尽管我们检查了简单的实体,但是我们很快遇到了一些复杂的 NLU 场景。如果没有像 LUIS 这样的工具,进行这种语言识别将会非常乏味和具有挑战性。
自然语言中还有另一个有趣的场景。我们的模型目前支持用户说出这样一句话:
"Meet at Starbucks for coffee at 2pm"
如果用户想要添加多个日历条目,该怎么办?如果用户想说类似下面这样的话该怎么办?
"Meet at trademark for lunch at noon and at Starbucks for coffee at 2pm"
现在没有什么是不允许用户这么说的。如果我们对我们的应用进行了足够的训练,它肯定会处理这个输入,并且会识别两个主题实例、两个位置实例和两个日期时间实例,如下所示:
{
"query": "meet at culture for coffee at 11am and at the office for a code review at noon",
"topScoringIntent": {
"intent": "AddCalendarEntry",
"score": 0.996190667
},
"entities": [
{
"entity": "11am",
"type": "builtin.datetimeV2.time",
"startIndex": 30,
"endIndex": 33,
"resolution": {
"values": [
{
"timex": "T11",
"type": "time",
"value": "11:00:00"
}
]
}
},
{
"entity": "noon",
"type": "builtin.datetimeV2.time",
"startIndex": 74,
"endIndex": 77,
"resolution": {
"values": [
{
"timex": "T12",
"type": "time",
"value": "12:00:00"
}
]
}
},
{
"entity": "culture",
"type": "Location",
"startIndex": 8,
"endIndex": 14,
"score": 0.770069957
},
{
"entity": "the office",
"type": "Location",
"startIndex": 42,
"endIndex": 51,
"score": 0.9432623
},
{
"entity": "coffee",
"type": "Subject",
"startIndex": 20,
"endIndex": 25,
"score": 0.9667959
},
{
"entity": "a code review",
"type": "Subject",
"startIndex": 57,
"endIndex": 69,
"score": 0.9293087
}
]
}
然而,使用代码解析这一点是相当具有挑战性的。我们如何判断哪些实体应该被分组在一起?哪个地点与哪个主题相匹配?我认为您应该能够使用 startIndex 属性来解决这个问题,但是这并不总是那么明显。
幸运的是,LUIS 可以将实体分组为所谓的复合实体。LUIS 将告诉我们哪些实体是哪个复合实体的一部分,而不是之前显示的混乱结果。这使我们更容易知道有两个单独的 AddCalendar 请求,一个是上午 11 点在文化中心喝咖啡,另一个是中午在办公室进行代码审查。
可以在 LUIS 的实体页面上创建复合实体。图 3-27 说明了该过程。点击创建新实体按钮,输入实体名称,选择复合实体类型,并选择子实体类型作为新实体的一部分。我们将使用名称 CalendarEntry 来标识我们的复合实体。
图 3-27
创建新的复合实体
一旦它被创建,我们需要适当地训练路易斯去识别它。让我们再次看看 AddCalendarEntry 意图。训练 LUIS 最简单的方法是找到所有具有所需的三个实体的话语,并将这些实体包装成复合实体。图 3-28 显示了一个例子。
图 3-28
带有日期时间、主题和位置的“正确的”CalendarEntry。这是包装在复合实体中的完美选择。
单击第一个位置实体。将出现一个弹出窗口,要求您重新标记实体或将其包装在一个复合实体中。点击复合实体(图 3-29 )。
图 3-29
单击位置实体将允许我们在一个复合实体中包装部分话语
我们将鼠标移到 Subject 和 datetimeV2 实体上。请注意,绿色下划线会扩展以覆盖每个实体(图 3-30 )。单击 datetimeV2,使其包含在复合实体中,然后单击 CalendarEntry 名称。
图 3-30
一旦选择了复合实体的开始,就需要向 LUIS 显示它的结束位置
对 CalendarEntry 实体的第二个实例执行相同的操作。结果应该如图 3-31 所示。
图 3-31
LUIS 现在有了一个如何包装复合实体的例子
我们应该对我们能找到的包括这三个实体的任何其他话语做同样的事情。一旦我们训练并发布了应用,LUIS 应该开始提取这个复合实体。这里我们只展示相关的 API 部分:
"compositeEntities": [
{
"parentType": "CalendarEntry",
"value": "culture for coffee at 11am",
"children": [
{
"type": "builtin.datetimeV2.time",
"value": "11am"
},
{
"type": "Subject",
"value": "coffee"
},
{
"type": "Location",
"value": "culture"
}
]
},
{
"parentType": "CalendarEntry",
"value": "the office for a code review at noon",
"children": [
{
"type": "builtin.datetimeV2.time",
"value": "noon"
},
{
"type": "Subject",
"value": "a code review"
},
{
"type": "Location",
"value": "the office"
}
]
}
]
练习 3-6
复合实体
在本练习中,您将向 LUIS 应用添加复合实体。
-
创建一个名为 CalendarEntry 的复合实体,由 datetimeV2、Subject 和 Location 实体组成。
-
训练每一个有这三个实体的话语来识别复合实体。
-
使用 CalendarEntry 复合实体的多个实例训练其他示例。记住,做好这件事需要时间、奉献和坚持。
-
将 LUIS 应用发布到暂存槽中。
-
使用 curl 检查生成的 JSON。
复合实体是将实体分组为逻辑数据对象的一个很好的特性。复合实体允许我们封装更复杂的表达式。
分层实体
分层实体允许我们定义一类实体及其子实体。您可以将分层实体视为定义实体之间的父/子类型关系。我们以前遇到过这种情况。您还记得 Datetimev2 实体吗?它有七个子类型,如日期范围、集合和时间。
LUIS 允许我们轻松创建自己的子类型。假设我们希望在模型中添加支持,将日历条目的可见性指定为公共或私有。我们可以添加这样的语句支持:
"create private entry for interview with competitor at starbucks"
"create invisible entry for interview with recruiter at trademark"
这里的私人或不可见表示日历的可见性字段。为什么我们要创建一个层次实体而不是一个简单的实体?难道我们不能只看一个 Visibility 属性的值来确定它是否应该是一个私人会议吗?是和不是。如果用户坚持这两个词,是的。但是记住,自然语言是模棱两可的,模糊的。措辞变了。用户可以说不可见、私有、私密、隐藏。公家也一样。如果我们在代码中对一组封闭的选项进行假设,那么每当出现新的选项时,我们就必须修改代码。应该使用层次实体而不是简单实体的原因是,层次实体在上下文中出现的位置的统计模型是由子类型共享的。一旦被识别,识别子实体的步骤本质上是一个分类问题。与两个简单的实体相比,使实体具有层次结构可以获得更好的 LUIS 性能。更不用说,让 LUIS 在我们的应用的上下文中对实体的含义进行分类比编写代码更有效。
图 3-32 展示了一个新的层级实体的创建。我们通过访问“实体”页面,单击“创建新实体”,并从“实体类型”下拉列表中选择“层次结构”来完成此操作。我们给父实体一个名称,并添加子实体。一旦我们点击完成,这是一个进入意图话语和训练路易斯的问题。让我们进入 AddCalendarEntry 并添加几个示例。
图 3-32
创建新的分层实体
您可能会注意到一两个样本是不够的。我们需要给 LUIS 一个很好的主意,在它开始识别我们输入中的实体之前,它可能在哪里以及如何遇到公共和私有可见性修饰符。图 3-33 中的十个样本是一个良好的开端。
图 3-33
样本可见性分层实体话语
一旦我们进行了训练和发布,我们就可以通过 curl 查看生成的 JSON,如下所示:
{
"query": "create private meeting for tomorrow 6pm with teddy",
"topScoringIntent": {
"intent": "AddCalendarEntry",
"score": 0.9856489
},
"entities": [
{
"entity": "tomorrow 6pm",
"type": "builtin.datetimeV2.datetime",
"startIndex": 27,
"endIndex": 38,
"resolution": {
"values": [
{
"timex": "2018-02-19T18",
"type": "datetime",
"value": "2018-02-19 18:00:00"
}
]
}
},
{
"entity": "private",
"type": "Visibility::Private",
"startIndex": 7,
"endIndex": 13,
"score": 0.9018322
}
]
}
{
"query": "create public meeting with jeff",
"topScoringIntent": {
"intent": "AddCalendarEntry",
"score": 0.975892961
},
"entities": [
{
"entity": "public",
"type": "Visibility::Public",
"startIndex": 7,
"endIndex": 12,
"score": 0.6018059
}
]
}
列出实体
到目前为止,预先构建的、简单的、复合的和分层的实体都是通过机器学习技术从用户输入中提取的。每当我们添加一个这样的实体并训练 LUIS 时,您可能会注意到被训练的模型数量在增加。回想一下,LUIS 应用由每个意向/实体的一个模型组成。现在,我们应该有十个模型了。每当我们训练我们的应用时,这些都会被重新构建。
列表实体存在于这个机器学习世界之外。列表实体只是术语和这些术语的同义词的集合。例如,如果我们想识别城市,我们可以添加一个纽约条目,它有同义词 NY、大苹果、不夜城、哥谭、新阿姆斯特丹等。路易斯会把这些化名解析到纽约。
一旦创建了自定义列表实体类型,我们就被重定向到列表实体编辑器,在这里我们可以输入规范术语和同义词。这个接口允许我们添加新的术语及其同义词。它还建议添加额外的术语,这些术语似乎与我们到目前为止添加的术语相关。列表实体限于 20,000 个术语,包括同义词。我们每个应用可以有多达 50 个列表实体,因此基于 LUIS 的术语和同义词查找功能有很大的潜力。图 3-34 显示了一个示例自定义列表实体定义。
图 3-34
LUIS 列表实体用户界面
由于 LUIS 没有学习列表实体,因此无法基于上下文识别新值。如果 LUIS 看到“Gotham”,它会将其识别为纽约。如果它看到“Gohtam”,它不会。它实际上是一个查找列表。
{
"query": "meet in the big apple",
"topScoringIntent": {
"intent": "AddCalendarEntry",
"score": 0.943692744
},
"entities": [
{
"entity": "the big apple",
"type": "Cities",
"startIndex": 8,
"endIndex": 20,
"resolution": {
"values": [
"New York"
]
}
}
]
}
使用 API 时,LUIS 将突出显示与列表实体类型匹配的术语,并将在解析值中返回规范名称。这允许您的消费应用忽略一个术语的所有可能的同义词,并基于规范名称执行逻辑。列表实体在您提前知道术语的一组可能值的情况下非常有用。
正则表达式实体
LUIS 允许我们创建正则表达式实体。与列表实体一样,这些实体不是基于上下文,而是基于严格的正则表达式。例如,如果我们期望一个知识库 id 总是使用语法 KB143230 来表示,其中文本 KB 后跟 6 个数字,我们可以用正则表达式 kb[0-9]{6,6} 来创建一个实体。一旦被训练,如果任何用户话语片段匹配该表达,该实体将总是被识别。
预构建的域
我希望你已经了解了建立 NLU 模型的一些挑战。机器学习工具允许我们让计算机开始学习,但我们需要确保我们正在用大量好的数据训练它们。人类需要多年的日常互动才能沉浸在一种语言中,才能真正理解它。然而,我们假设人工智能意味着一台计算机将能够用十个样本获得概念。当事实并非如此时,有时我们会对自己说,“哦,得了吧,你现在应该知道了!”
为了帮助我们的旅程,许多 NLU 平台提供了所谓的预构建模型或域。本质上,LUIS 和其他平台的创建者希望给我们一些领域的先机,我们可以很容易地将它们包含在我们的应用中,训练 LUIS,然后开始比赛。路易斯的一些预建模型如图 3-35 所示。
图 3-35
预构建的域
我们可以在 LUIS 中找到预构建的域,方法是导航到 Build 部分,然后单击左下角的预构建的域链接。在撰写本文时,该特性仍处于预览模式。这就是它如此孤立的原因,也是为什么它是动态的,在你读到这篇文章的时候可能会改变。LUIS 包括各种领域,从相机到家庭自动化到游戏到音乐,甚至日历,这与我们在本章中一直在开发的应用类似。事实上,我们将在练习 3-7 中这样做。“了解更多”文本链接到一个页面,该页面详细描述了每个域引入的意图和实体,以及哪些域受哪些文化的支持。 4
当我们向您的应用添加一个域时,LUIS 会将该域的所有意图和实体添加到我们的应用中,它们将计入应用的最大值。在这一点上,我们能够修改我们认为合适的意图和实体。有时,您可能想要删除某些意图,或者添加新的意图来补充预先构建的意图。其他时候,我们可能需要用更多的样本来训练系统。我们建议将预构建的域视为起点。我们的目标是扩展它们,并在它们的基础上建立更好的体验。
历史观点
这些年来,路易斯变了很多。即使在写这本书的过程中,系统也改变了用户界面和功能集。LUIS 曾经有一个 Cortana 应用,任何人都可以通过使用已知的应用 ID 和订阅密钥来访问该应用。Cortana 应用定义了许多预构建的意图和实体,但它是一个封闭的系统。你不能以任何方式根据自己的喜好对它进行定制或强化。从那以后,微软已经放弃了这个特性,转而支持预构建的域。但是,与他人公开共享您的模型以便他们可以使用自己的订阅密钥调用它的想法仍然可用,并可通过设置页面访问。
练习 3-7
利用预先构建的域
在本练习中,您将利用预构建的日历域创建一个 LUIS 应用,该应用类似于我们在本章中构建的应用。
-
创建新的 LUIS 应用。
-
导航到预构建域部分并添加日历域。
-
训练应用。
-
使用交互式测试用户界面来检查应用的性能。它在探测意图和实体方面有多好?在设计和性能方面,它与我们创建的应用相比如何?
预构建的域对于一个域的开始可能是有用的,但是 LUIS 需要勤奋的训练来拥有一个真正表现良好的模型。
短语列表
到目前为止,我们一直在探索不同的技术来创建伟大的模型。我们拥有所需的工具来确保我们能够为用户创造良好的对话体验。当我们训练 LUIS 时,有些情况下模型性能不如我们希望的那样好。实体可能不会像我们希望的那样得到认可。也许我们正在构建一个 LUIS 应用,专门处理内部术语,而这些术语并不完全是您的应用所使用的文化的一部分。也许我们还没有机会用一个实体的每一个已知的可能值来训练 LUIS entities,列表实体没有削减它,因为我们希望我们的实体保持灵活性。
在这种情况下,提高 LUIS 性能的一个方法是使用短语列表。短语列表是 LUIS 在训练我们的应用时使用的提示,而不是严格的规则。他们不是银弹,但可以非常有效。短语列表允许我们向 LUIS 呈现一类彼此相关的单词或短语。这种分组是对 LUIS 的一种暗示,即以类似的方式对待该类别中的单词。在实体值未被正确识别的情况下,我们可以将所有已知的可能值作为短语列表输入,并将该列表标记为可交换的,这向 LUIS 表明,在实体的上下文中,这些值可以以相同的方式处理。如果我们试图用 LUIS 可能不熟悉的单词来提高他的词汇量,短语列表就不会被标记为不可替换。
假设我们想要提高日历模型的私有可见性实体的性能。毕竟,有很多方式来表达我们想要一个私人会议。作为起点,我们可以添加一个短语列表,其中包含我们希望模型看到的所有不同的单词。图 3-36 显示了用于处理短语列表的 LUIS 用户界面。你可以通过选择构建页面下的短语列表项并点击创建新短语列表来到达这里。
图 3-36
我可能有点过火了。我责怪相关值函数。
短语列表需要一个名称和一些值。我们在值字段中逐个输入值。当我们按 Enter 键时,它会将它们添加到短语列表值字段中。相关值字段包含 LUIS 自动加载的同义词。然后,我们选中复选框,告诉 LUIS 这些值是可以互换的。
在培训之前,让我们在不启用短语列表的情况下尝试一些不同的私人会议用语。如果你尝试像“私下会面”、“秘密会面”或“创建一个隐藏的会议”这样的话语,路易斯不会识别这个实体。然而,如果我们用短语列表训练应用,LUIS 在这些样本和许多其他样本中识别实体没有问题。 5
练习 3-8
训练特点
在本练习中,您将通过添加功能来改进我们的 LUIS 应用。
-
将可见性分层实体添加到 LUIS 应用中。
-
添加您自己的短语列表以提高私有可见性实体的性能。
-
将 LUIS 应用发布到暂存槽中。
-
使用 curl 检查生成的 JSON。
-
将短语列表设置为不可互换对其性能有何影响?
短语列表是帮助我们的应用更好地识别不同实体的强大功能。
练习 3-9
添加被邀请人实体
您可能已经注意到,我们还没有谈到我们如何捕获与会者,到目前为止,我们已经忽略了这个问题。在本练习中,我们将解决这个问题。
-
添加名为“被邀请者”的新自定义实体。
-
检查到目前为止的每个样本话语,并识别话语中的被邀请者实体。
-
如果它需要额外的训练,增加更多的样本。确保包含样本,其中被邀请者是话语中的唯一实体或者是许多实体中的一个。
-
对于奖励积分,将被邀请者实体添加到 CalendarEntry 复合实体。
-
训练并确保所有的意图和实体仍然表现良好。
-
将 LUIS 应用发布到暂存槽中。
-
使用 curl 检查生成的 JSON。
如果您成功完成了这个练习,那么恭喜您!你越来越擅长利用路易斯了。
主动学习
我们花了几周时间训练一个模型,我们经历了一轮测试,我们已经将应用部署到生产中,我们已经打开了我们的机器人。现在怎么办?我们如何知道模型是否尽了最大努力?我们如何知道某个用户是否向我们的应用抛出了意外的输入,从而破坏了我们的 bot 并导致了糟糕的用户体验?错误报告肯定是一种方法,但是我们依赖于获得反馈。如果我们能在这些问题出现时就发现它们,会怎么样呢?我们可以利用路易斯主动学习的能力来做到这一点。
回想一下,监督学习是从有标签的数据进行机器学习,无监督学习是从无标签的数据进行机器学习。半监督学习介于两者之间。主动学习是一种半监督式的学习,学习者要求监督者标记新的数据样本。根据 LUIS 看到的输入,它可以要求您(LUIS app trainer)帮助标记来自用户的数据。这提高了模型性能,随着时间的推移,通过使用真实的用户输入作为样本数据,使我们的应用更加智能。
您可以通过构建页面上的审查端点发言链接来访问此功能(图 3-37 )。在应用的整个培训过程中,我们一直在利用发布的应用端点来测试各种话语。LUIS 的主动学习基于对端点的输入,而不是交互式测试功能。
图 3-37
主动学习界面
该界面允许我们回顾过去的话语及其得分最高的意图,称为一致的意图。作为训练者,我们可以将话语添加到对齐意图中,重新分配给不同的意图,或者完全删除话语。如果我们知道它们中的任何一个有问题,我们也可以聚焦在特定的意图或实体上。
在将话语添加到对齐的意图之前,我们需要确认话语被正确标记并且任何实体都被正确识别。我们建议使用这个接口来改进 LUIS 应用是任何团队的惯例。
仪表板概述
既然我们已经训练了我们的应用并利用它进行测试,那么突出显示仪表板提供的数据是非常值得的。仪表板让我们可以很好地了解应用的整体状态、使用情况以及我们用来训练它的数据量。
根据图 3-38 ,最上面提供了有关我们上次培训和发布应用的信息。我们还可以获得一些指标,比如我们正在使用的意图和实体的数量,我们拥有的列表实体的数量,以及我们的应用到目前为止总共有多少带标签的话语。
图 3-38
申请状态
下一节将展示应用通过 API 获得的使用类型。我们可以监控从上周到去年的端点点击量。只有当应用发布到生产插槽时,这些数据才可用。如图 3-39 所示。
图 3-39
API 端点使用摘要
最后,我们会看到一个意图和实体的分解,如图 3-40 所示。这里我们看到了用于训练每个意图的话语百分比的分布。你可以清楚地看到,我们的一些意图比其他意图包含更多的示例语句。实体也一样。分布不均并不一定意味着某个实体或意向需要更多的培训。
图 3-40
关于意图/实体话语计数和分布的统计。单击一个意图导航到该意图的话语页面。
管理和版本化您的应用
到目前为止,我们所做的一切都是添加示例、培训和发布 LUIS 应用的通用工作流的一部分。在开发阶段,这个工作流程会一遍又一遍地重复。一旦你的应用投入生产,你应该小心你对你的应用做了什么。添加新的意图或实体的过程可能会对应用的其余部分产生不可预见的影响,最好是单独编辑现有的应用,以便可以正确地对其进行测试。
我们对试运行和生产部署槽的概念有经验。这当然有帮助;我们知道,我们可以在不发布到生产端点的情况下测试变更。一个常见的规则是让登台槽托管应用的开发/测试版本,让生产槽托管生产版本。每当一个新的应用准备好投入生产时,我们就把它从准备槽移到生产槽。但是如果我们在模型中犯了一个错误呢?如果我们需要回滚生产插槽呢?这就是版本出现的原因。
LUIS 允许您随时创建应用的命名版本。到目前为止,默认情况下,我们一直在 0.1 版本上工作。一旦它为生产做好准备,我们就可以发布它并将其克隆到新的版本 0.2 中。此时,您将 0.2 版本设置为活动的。现在,LUIS 界面正在编辑 0.2 版本。如果我们不小心将版本 0.2 发布到生产插槽中,我们可以很容易地返回到版本 0.1 并发布它。一旦版本 0.2 准备好生产,我们就将其部署到生产插槽中,克隆到版本 0.3 中,并将该版本设置为活动版本。等等。如果在任何时候您将一个版本部署到生产插槽中并需要恢复,您可以将 LUIS active 设置回 0.2,并将该版本发布到生产插槽中。工作流程如图 3-41 所示。
图 3-41
LUIS 开发、培训、测试和发布工作流程
我们通过设置页面访问应用版本信息。图 3-42 和图 3-43 显示了将 0.1 版本克隆到 0.2 版本后的界面和外观。
图 3-43
0.1 版被克隆到 0.2 版
图 3-42
“设置”页面上的版本控制功能
请注意,关闭 0.1 后,它仍保留在登台槽中,但 0.2 成为活动版本。LUIS 也不允许简单的分支。如果多个用户想要对单个版本进行更改,他们不能创建新版本,然后使用用户界面合并他们的更改。一种方法是通过点击图 3-42 中的导出版本按钮下载 LUIS App JSON,利用 Git 等源代码控制工具进行分支和合并,最后使用“导入新版本”按钮从 JSON 文件上传新版本。
该页面还允许我们向应用添加协作者。这是一种很好的方式,可以让您的组织中的其他人帮助编辑、培训和测试应用版本。在撰写本文时,没有微调的审计控制;除了添加/删除其他合作者之外,所有合作者都可以对应用执行任何操作(图 3-44 )。
图 3-44
向 LUIS 应用添加协作者
与拼写检查集成
LUIS 的一个高级特性是能够与拼写检查器集成,自动修复用户输入中的拼写错误。用户输入本质上是混乱的。拼写错误非常普遍。将这一点与消息应用的常见用法结合起来,你就有了一个一致的拼写错误输入的诀窍。
拼写检查器集成通过 Bing 的拼写检查器服务运行用户查询,得到一个可能被修改过的查询,并修复了拼写错误,然后通过 LUIS 运行这个修改过的查询。这个特性是通过包含查询参数 spellCheck 和 bing-spell-check-subscription-key 来调用的。你可以从 Azure 门户获得一个订阅密钥,我们将在第五章中介绍。我们还将在第十章中更直接地使用拼写检查 API。
这个功能很有帮助,我们通常会带着警告推荐它。如果我们的实体包含特定于域的值或产品名称,严格来说,它们不是英语的一部分,那么我们可能会得到一个修改后的查询,LUIS 无法在其中检测到实体。例如,当这种行为不受欢迎时,它可能会将一个单词分解成多个单词。或者,如果我们的应用期待金融行情,它可能只是改变它们。例如,Vanguard ETF 的 VEA 被改为 VA。在美国,这是对弗吉尼亚州的常见引用。意义的丧失是相当显著的;我建议谨慎使用这一功能。
拼写检查对 LUIS API 结果的影响很容易发现。结果现在包括一个名为 alteredQuery 的字段。这是传递给 LUIS 模型的文本。这里给出了一个 curl 请求和响应 JSON 示例:
curl -X GET -G -H "Ocp-Apim-Subscription-Key: a9fe39aca38541db97d7e4e74d92268e" -d staging=true -d spellCheck=true -d bing-spell-check-subscription-key=c23d51fc861b45c4b3401a6f8d37e47c -d verbose=true -d timezoneOffset=0 "https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/3a26be6f-6227-4136-8bf4-c1074c9d14b6" --data-urlencode "q=add privtae meeting wth kim tomoorow at 5pm"
{
"query": "add privtae meeting wth kim tomoorow at 5pm",
"alteredQuery": "add private meeting with kim tomorrow at 5pm",
"topScoringIntent": {
"intent": "AddCalendarEntry",
"score": 0.9612303
},
"entities": [
{
"entity": "tomorrow at 5pm",
"type": "builtin.datetimeV2.datetime",
"startIndex": 29,
"endIndex": 43,
"resolution": {
"values": [
{
"timex": "2018-02-20T17",
"type": "datetime",
"value": "2018-02-20 17:00:00"
}
]
}
}
]
}
进口/出口申请
LUIS 中构建的任何应用都可以导出到 JSON 文件中,然后再导入到 LUIS 中。JSON 文件格式正是我们所期望的。有些元素定义了应用使用哪些自定义意图、自定义实体和预构建实体。还有其他元素来捕获短语列表。毫不奇怪,有相当大的一段描述了所有的样本话语、它们的意图标签以及话语中任何实体的开始和结束索引。我们可以通过点击 LUIS 的我的应用部分的导出应用或者设置页面的导出版本来导出应用,如图 3-41 。
尽管导出的应用的格式特定于 LUIS,但是很容易想象我们如何编写代码来解释其他应用的数据。从治理的角度来看,导出我们的应用并将 JSON 存储在源代码控制中是一种很好的做法,因为发布操作的操作是不可逆的。如果我们的团队遵循这样的策略,即发布到产品插槽意味着创建新的应用版本,这应该不是问题,但是错误确实会发生。
在与 LUIS 的合作中,我们收到的最常见的问题之一是“为什么我们不能将应用导入到现有的应用中?”原因是,这相当于一个聪明的合并,特别是在有不同意图的重叠话语或具有完全不同应用内涵的相同名称意图的情况下。因为每个应用都有不同的语义,所以这种合并将是一项艰巨的任务。我们建议要么利用 Git 来管理和合并应用 JSON 代码,要么使用 LUIS Authoring API 创建自定义代码来进行合并。
使用 LUIS 创作 API
当谈到 LUIS 及其功能时,开发人员的第一个问题是,“这可以通过 API 来实现吗?”答案是肯定的!创作 API 允许我们通过 API 使用用户界面执行所有的任务。创作 API 分为以下资源:
-
应用:添加、管理、删除、发布应用。
-
示例:将一组示例话语上传到应用的特定版本中。
-
特性:在应用的特定版本中添加、管理或删除短语或模式特性。
-
模型:添加、管理或删除自定义意图分类器和实体提取器;添加/删除预构建的实体;添加/删除预构建的域意图和实体。
-
权限:添加、管理和删除应用中的用户。
-
培训:排队申请培训版本,获取培训状态。
-
用户:管理 LUIS 应用中的 LUIS 订阅密钥和外部密钥。
-
版本:添加和删除版本;将密钥与版本相关联;导出、导入、克隆版本
API 非常丰富,支持培训、自定义主动学习,并支持 CI/CD 类型的场景。API 参考文档 6 是学习 API 的好地方。
排除模型故障
我们重点关注 LUIS 本身以及通过将定制意图分类器和定制实体提取器与预构建的实体和预构建的域相结合来创建应用的过程。在这个过程中,我们注意到系统的一些有趣的行为。机器学习并不完美。我们几乎肯定会遇到奇怪的情况,在这些情况下,我们的意图或实体会遇到麻烦。以下是我们应该如何解决 LUIS 问题的列表:
-
最常见的问题之一是训练模型而不发布它。如果您正在使用登台槽测试应用,请确保将其发布到登台槽中。如果您调用应用的生产插槽,请确保应用已发布。并确保在调用 API 时根据需要传递暂存标志。
-
如果意向被错误分类,为有问题的意向提供更多的意向例子。如果问题仍然存在,花些时间分析意图本身。它们真的是两种不同的意图吗?或者这真的是一种意图,我们需要一个自定义实体来区分这两者?此外,确保用一些与您的应用完全不相关的输入来训练 None 意图。测试数据非常适合这个目的。
-
如果应用难以识别实体,请考虑您正在创建的实体的类型。有一些实体通常是一个单词的修饰语,出现在意图的同一个地方,比如我们的可见性实体。另一方面,还有更微妙的实体,它们可以出现在话语的任何地方,通常以一些单词为前缀和后缀。前者不需要像后者那样多的话语样本。通常,实体识别问题可以通过执行以下操作来解决:
-
根据不同的变体和同一变体的多个样本添加更多的话语样本。
-
值得一问的是,这个实体是否应该是一个列表实体。一个好的经验法则是,这个实体是一个查找列表吗?或者应用在识别这种类型的实体时需要灵活性吗?
-
考虑使用短语列表向 LUIS 展示一个实体可能的样子。
-
-
LUIS 是否在两个实体之间感到困惑?实体是否相似,只是基于上下文略有不同?如果是,这可能是一个分层实体的候选。
-
如果您的用户试图交流由多个实体组成的更高层次的概念,请使用复合实体。
构建 LUIS 应用与其说是科学,不如说是艺术。你有时会花很多时间教 LUIS 一些实体之间的区别,或者一个实体可以出现在句子的什么地方。耐心点。要彻底。并且总是用统计学的术语来思考问题;系统需要看到足够多的样本,才能真正开始理解正在发生的事情。作为人,我们可以认为我们的智力和语言理解是理所当然的。相对而言,我们能如此迅速地训练出像路易斯这样的系统是相当令人惊讶的。当你与路易斯或任何其他 NLU 系统一起工作时,请记住这一点。
结论
这是相当多的信息!祝贺您,我们现在已经准备好使用 LUIS 这样的工具开始构建我们自己的 NLU 模型。概括地说,我们通过利用预构建的实体、自定义意图和自定义实体来完成创建应用的练习。我们探索了各种预构建实体的功能,并涉猎了 LUIS 提供的预构建域。在将应用发布到不同类型的插槽中并使用 curl 测试 API 端点之前,我们花时间训练和测试了我们的应用。我们使用短语特性优化了我们的应用,并利用 LUIS 的主动学习能力进一步改进了它。我们探讨了 LUIS 应用中的版本控制、协作、集成拼写检查、应用的导出和导入、使用创作 API 以及常见的故障排除技术。
我必须重申,你刚刚学到的概念和技术都适用于其他 NLU 平台。训练意图和实体以及优化模型的过程是你工具包中的一项强大技能,无论是对于机器人、语音助手还是任何其他自然语言界面。我们现在准备开始思考如何构建一个机器人。正如我们所做的,我们将继续检查这个 LUIS 应用,因为它被我们的 bot 使用。
路易斯界限: https://docs.microsoft.com/en-us/azure/cognitive-services/luis/luis-boundaries
2
LUIS 端点 API 文档: https://westus.dev.cognitive.microsoft.com/docs/services/5819c76f40a6350ce09de1ac/operations/5819c77140a63516d81aee78
3
预制实体参考: https://docs.microsoft.com/en-us/azure/cognitive-services/luis/luis-reference-prebuilt-entities
4
路易斯预建域名: https://docs.microsoft.com/en-us/azure/cognitive-services/luis/luis-reference-prebuilt-domains
5
微软。识别器.正文: https://github.com/Microsoft/Recognizers-Text
6
路易斯创作 API 参考文档: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff