带你读《实用Bot开发指南:基于Node.js与Bot框架设计并构建聊天机器人》之三:语言理解智能服务

简介: 本书讲述机器人设计和实现背后的基本概念。每一章都建立在前面的主题之上,并且在适当的地方显示了实现这些概念的实际工作代码。通过选择一个代码编辑器,你可以开始体验创建智能、迷人和有用的机器人。本书将教你如何在Facebook Messenger和Slack等平台上创建自己的机器人,整合扩展API,并在云中应用人工智能和机器学习算法。在本书的最后,你将会有足够的信息通过你创建的机器人来接触成千上万的新用户。

点击查看第一章
点击查看第二章

第3章

语言理解智能服务
语言理解智能服务(LUIS)是我和我的团队广泛使用的NLU系统,也是对自然语言进行意图分类和实体抽取的完美工具。开发者可以通过https://luis.ai 访问LUIS,使用微软账户登录之后,网站将首先向开发者展示一个关于如何创建LUIS应用程序的页面,本章将基于该页面开始介绍LUIS。点击页面下方的Create LUIS app(创建LUIS应用)按钮将会跳转到LUIS应用页面,点击Create new app(创建新应用)按钮并输入应用的名称,便会创建一个LUIS应用。在该创建的新应用中,开发者可以通过LUIS的应用程序接口来创建、训练、测试和发布模型。
在本章中,我们将创建一个LUIS应用,并用它支持我们创建Calendar Concierge Bot,Calendar Concierge Bot支持增加、编辑、删除约会,管理日历上的空余时间。通过本章的实践,开发者可以了解到LUIS多种多样的功能。在本章结束时,我们将开发一个既可以集成到机器人中又可以自我不断训练改进的LUIS应用。
首先,我们在LUIS中创建一个新LUIS应用CalendarBotModel,当我们在LUIS页面点击Create new app按钮时,会出现如图3-1所示的弹窗,在窗口中填写应用的名称和描述域。另外LUIS支持多个国家的多种语言,开发者可以在窗口选项中根据需要选择相应的语言,不同的语言需要不同的语言模型和不同的优化方法。截至写作本书时,LUIS支持巴西葡萄牙语、中文、荷兰语、英语、法语、加拿大法语、德语、意大利语、日语、韩语、西班牙语和墨西哥西班牙语。随着LUIS的不断成熟,更多的语言将会被LUIS支持。

image.png

创建应用后,将进入LUIS中与BUILD相关的内容页面,如图3-2所示。可以看到,里面只有空的意图,我们将在训练意图时对意图进行更详细的介绍。此外,读者还将看到Review endpoint utterances链接,这是LUIS的主动学习功能,我们将在后续章节中进行探讨。

image.png

注意在开始书写本书时,LUIS应用限定使用500个意图、30个实体和50个列表实体。而在最初发布LUIS的时候,其仅支持在一个应用中最多使用10个意图和10个实体。LUIS所支持的意图和实体等最新数量可以通过参考链接进行查阅。
页面的顶部从左到右分别是:开发者开发的应用名称、当前活动的版本,以及DASHBOARD(仪表板)、BUILD(构建)、PUBLISH(发布)和SETTING(设置)。开发者还可以通过点击页面顶部最右侧的按钮,分别对模型进行训练和测试。在构建Calendar Concierge Bot应用时,我们将逐一对这些功能进行介绍。

3.1 意图分类

前一章介绍了意图分类的概念,本节我们将在开发实践中深入理解意图分类。回顾一下,在本章我们打算创建一个LUIS应用程序CalendarBotModel,它能让我们添加、编辑或删除日历条目,显示日历摘要并查看我们日历中可用的空闲时间。我们将创建以下意图:

  • AddCalendarEntry
  • RemoveCalendarEntry
  • EditCalendarEntry
  • ShowCalendarSummary
  • CheckAvailability

回到图3-2页面的BUILD部分,我们在左侧窗格中点击选择Intents项。可以发现此时只有一个为None的意图,当用户的输入与任何其他意图都不匹配时就称为None意图。在机器人中,我们可以通过使用None意图来告诉用户他们询问的问题超过了机器人所擅长的知识领域并提醒用户该机器人能实现的功能有哪些。
对意图进行分类的一般流程是在页面中创建意图,然后向LUIS提供一些能表示该意图的话语实例,图3-3展示了创建意图的过程。接着,我们可以在相应的文本框中输入话语实例;我们不断输入话语实例,并不断点击回车对输入进行确认。在输入足够多的话语实例后,点击Save(保存)按钮,此时便完成了一个意图的添加,如图3-4所示。

image.png

image.png

LUIS还允许开发者在用户界面上搜索、删除某个话语实例,以及为话语实例重新分配意图,开发者在开发过程中会逐渐摸索到更多的功能。
在创建其余的意图之前,我们先对LUIS应用进行检验,确保到目前为止LUIS应用可以被训练和测试。页面右上方的Train(训练)按钮为红色时,表明LUIS应用已经被编辑修改并且修改之后没有重新训练。点击Train按钮时,重新训练应用的请求将被发送到LUIS服务器,开始重新训练,同时开发者会在页面上收到应用正在被训练以及像“0/2 completed”这样的进度消息。在进度消息中,2表示当前应用包含的需要分类的意图有两个。在本章的开发实例中,截至目前的开发程度,一个意图是None,另一个意图是AddCalendarEntry。训练完成后,Train按钮将变为绿色,表示该应用程序此时是最新的。
针对每个话语实例,我们还可以查看刚才训练的应用对哪个意图的打分最高,如图3-5所示。这些得分数据非常重要,因为我们可以从中很容易地看出有时话语被标记在一个意图上,但应用在对话语的意图进行分类时,却将最高分数分配给了别的意图。在训练集上的运行结果与训练标签之间的差异通常表明模型中存在导致错误结果的东西。我们将在3.14节对这个问题和其他类似情况进行探讨。现在,似乎成功训练完所有的话语,并在AddCalendarEntry意图上产生结果为1的得分,在None意图上产生0.05~0.07的得分(如图3-6所示);这些得分取决于开发者具体输入的话语实例和微软LUIS开发团队对LUIS版本的更新。

image.png

训练完意图之后,我们可以使用Train按钮旁边的Test按钮来测试模型,看一下它对输入的各种测试话语的运行结果(如图3-7所示)。Batch testing panel支持批量的运行测试话语,我们在开发本实例中依旧使用交互模型(interactive mode)。

image.png

LUIS将每个话语输入到训练阶段所训练的意图分类模型中运行,每一个意图都会得到一个介于0到1之间的打分,得分最高的意图被突出显示。注意这一打分并不是该话语属于该意图的概率,它依赖于LUIS使用的算法,通常用于表示话语输入和意图之间的距离。如果对于一个输入,LUIS在多个意图上给出的打分都很接近,则说明我们可能还需要对LUIS应用进行更多的训练。
在应用训练结束并经过上面的测试之后,似乎一切顺利。事实上,我们可以用几个奇怪的语句进行测试,发现应用的运行结果并不正确,如图3-8所示。

image.png

这是因为在上面的开发实践中,我们使用了一些话语实例作为训练数据来对AddCalendarEntry意图进行训练,但我们没有为None意图提供任何训练数据,所以我们再为None意图加一些话语并重新训练、测试。现在,我们再胡乱地输入一些没有意义的测试语句,如图3-9所示,此时便解决了图3-8中所示的问题。在开发阶段,我们做不到一劳永逸地解决所有的类似问题,这需要让应用不断地在线上运行并分析用户的使用反馈。这提醒开发者,使用与应用中的意图相关的话语来训练应用和使用不相关的话语来训练应用同等重要,开发者应该兼顾这两种数据。

image.png

刚才我们已经添加了AddCalendarEntry意图,接下来我们将添加剩下的意图。图3-10、图3-11、图3-12和图3-13分别展示了CheckAvailability意图、EditCalendarEntry意图、DeleteCalendarEntry意图和ShowCalendarSummary意图的一些话语实例。
一旦创建完所有意图并添加了话语样本,我们就会训练应用并在训练结束后检查预测的意图是否准确。另外值得注意的是,对于一个话语样本而言,尽管得分最高的意图是该话语正确的意图分类结果,但这个分值可能很低(如图3-14所示),这表明我们还需要进一步提升训练效果。事实上,用如此有限的词汇和数据集就能彻底训练好一个意图是不可能的,对一个意图达到好的识别效果需要耐心和工程优化思想。在下面的练习中,我们将为应用添加更多话语。

image.png
image.png
image.png

练习3-1
训练LUIS的意图
前面的几个例子展示了将一些话语实例输入到所训练的意图中。我们的开发任务是创建一个LUIS应用、为LUIS应用创建符合其功能的一系列意图以及用足够的话语样例来训练LUIS应用,直到所有意图的得分都超过0.8为止。

  • 创建下列意图并为每个意图输入至少10个话语实例:

    • AddCalendarEntry
    • RemoveCalendarEntry
    • EditCalendarEntry
    • ShowCalendarSummary
    • CheckAvailability
  • 为None意图添加更多的话语,添加的话语集中在话语内容本身没有意义或者话语内容对于本应用而言无意义。比如“I like coffee”,这句话本身有意义,但对于LUIS应用的几个意图(如AddCalendarEntry)而言没有意义。
  • 训练LUIS应用并观察每个话语实例在每个意图上的得分。测试时,使用交互测试模式。
  • 得分是多少?超过0.8了吗?如果分值比较低则说明训练效果不好,因此继续为每个意图分别添加话语实例,重新训练,直到分值超过0.8。观察一下每个意图总共需要添加多少个话语才能使LUIS应用达到可用的训练效果。

在完成这些练习之后,读者便具备构建、训练和测试LUIS意图的开发经验了。

3.2 发布LUIS应用

显然,截至目前我们还没有完成LUIS应用的开发,还有很多细节我们也没有展开探索,我们甚至没有看到真实的用户使用数据。但是,我们可以并行地开发LUIS应用和消费者级的应用。让经过训练的应用程序可以通过HTTP访问的过程称为应用程序发布。
在页面最上方的导航栏,BUILD的旁边可以找到PUBLISH(发布)。点击PUBLISH,便会跳转到部署LUIS应用的页面,如图3-15所示。LUIS支持开发者用两种模式部署应用:Staging slot和Production slot。staging表示我们还在开发和测试LUIS应用,还处于开发环境中;production则表明应用已经发布到线上的生产环境中。设计两种部署模式的思想是让开发者既可以维护一个发布在生产环境中的稳定版本,同时又能在staging中继续开发新的版本,增加新的功能。

image.png

在页面的“Publish to”下拉选项中,我们选择“Staging slot”,发布之后我们可以通过HTTP端点访问LUIS应用。
cURL是一个通过HTTP(在许多其他协议中)传输数据的命令行工具,在我们使用cURL测试生成的端点之前,你可能已经注意到,在发布设置页面下有一个Add Key按钮和一组key选项,用于选择部署地区。开发者必须提供一个key才能使LUIS应用能够被访问,同时LUIS通过该订阅的key向开发者使用API进行收费。由于LUIS在多个地区均有部署,因此key必须和所属地区进行关联。key通过使用微软Azure Portal创建,Azure是微软提供的云服务。我们将在第5章介绍使用Azure注册和部署机器人。通过Add Key按钮可以将key和应用关联,幸运的是,LUIS提供了一个免费的入门key(starter key),可以用于在Staging slot中发布的应用程序。
将LUIS应用发布到Staging slot之后,我们会立即得到应用版本号和应用最新发布时间的信息,此时Starter_Key中的URL便可以正常访问。通过URL查询参数还可以获得更多详细信息(我们将马上介绍这部分内容)以及加入Bing(必应)拼写检查(参见3.11节)。首先,看一下URL的内容。
image.png

URL内容的第一行是部署在美国西部地区的Azure上的认知服务的service endpoint(服务端点),具体来讲就是我们的LUIS应用的service endpoint。查询参数的含义如下:

  • subscription key:开发者订阅的key,在本开发教程中指的是starter key。该key也可以通过Ocp-Apim-Subscription-Key的头部进行传递。
  • staging:true表示使用Staging slot,false表示使用Production slot。如果此参数的值为空白,则LUIS认为使用的是Production slot。
  • verbose:true表示返回所有意图和得分,false表示仅返回得分最高的意图。
  • time zone offset:用于帮助解析不同格式的日期时间,在介绍内建的Datetime实体时我们会介绍时间解析。
  • q:表示用户自定义的查询。

我们可以通过发送请求和API交互,也可以通过使用curl看到响应。curl是一个支持多种传输协议的用于数据转换的命令行工具。我们使用curl在HTTPS上转换数据,如需了解更多关于curl的内容,可以访问https://curl.haxx.se/。我们所使用的curl命令如下,注意其中的-H参数,我们将订阅的key作为HTTP头部进行传递。
image.png
image.png

查询的结果如下,格式为JSON,它会对我们的LUIS应用中的每一个意图进行打分。
image.png

可能你会想,刚才说我们最多可以有500个意图,那么该响应中意图的数量明显不合逻辑。的确如此,将查询参数verbose设置为false将产生紧凑的JSON列表,并进行返回。
image.png
image.png

一旦准备好将应用部署到生产环境中,就可以把LUIS应用发布到Production slot,并且从URL请求中移除staging参数。最简单的方式是让你的开发和测试配置文件指向Staging slot URL,让生产发布的配置文件指向Production slot URL。
开发者可以使用任何自己顺手的HTTP工具,此外微软还提供了一个快速使用终端(easy-to-use console)用于测试LUIS API,具体使用请查询网上在线的API文档。
练习3-2
发布LUIS应用
此练习是发布练习3-1中的LUIS应用,并且通过curl访问它。

  • 按照前面章节的步骤,将LUIS应用发布到Staging slot。
  • 使用curl从作为样例输入的话语和其他你能想到的话语中得到JSON数据格式响应的预测意图。
  • 确保curl命令使用的是所发布应用的ID和入门key。

将LUIS应用发布到slot的过程非常直接,但熟悉使用curl对HTTP端点进行检测非常重要,因为后续开发中我们需要经常使用它来访问端点,以检测LUIS应用的返回结果。

3.3 实体抽取

目前为止,我们开发了一个简单的基于意图的LUIS应用,但除了能让LUIS应用识别用户话语中的意图并告诉机器人之外,我们还没有使用LUIS进行其他更深入的开发。通过上面的开发实例,LUIS可以通过识别AddCalendarEntry意图来告诉用户想要增加一个日历,但我们更希望能自动增加日历的具体日期、时刻、地点、持续时间以及对象。我们可以让机器人在识别到AddCalendarEntry意图时就按顺序依次询问所有的这些细节,但这种开发太愚笨和烦琐,尤其是在用户的话语已经很好地将时间、地点等细节内容表达完整时,比如下面的话语:
image.png

如果此时采用上面的方法,仍然依次询问用户有关时间、地点等细节信息,并要求用户再次输入这些数据,那用户体验会变得非常差。机器人应该能立即识别话语中的时间“tomorrow at 6pm”以及会议所邀请的人“Huck”。
如何保证“tomorrow at 6pm”“a week from now”和“next month”这些内容对于机器而言是可阅读的呢?这就需要引入实体识别(entity recognition)。幸运的是,LUIS内建了很多实体,我们可以将它们直接添加到LUIS应用中,这样LUIS应用便可以提取出日期时间(datetime)这一实体。
如果回到前面图3-2关于“BUILD”的那部分内容,并且在BUILD页面点击左侧顶部的“Entities”,那么我们将看到一个空的实体列表,如图3-16所示。我们可以选择添加三种不同的实体,在本实例中我们选择添加预建实体(prebuilt entity),即点击“Create new entity”(新建实体)。我们将在本章的后续章节中介绍自定义的一般实体以及预建领域实体(prebuilt domain entity)。

image.png

预建实体是一个预先训练的定义,可以在话语中被识别出来。实体在输入中被自动标记,我们无法更改识别预建实体的方式。在应用程序中,我们可以利用大量逻辑,最好在构建实体之前了解微软已经构建了什么。
LUIS提供了很多不同的预建实体,但不是所有的实体对每种语言(人类语言)都可用。LUIS官方文档中提供了不同预建实体对不同语言的支持,如图3-17所示。
一些实体还包括值消解(value resolution),值消解就是接收文本输入并将其转换成计算机可以解释的一个值。比如,“one hundred thousand”被解析成100000,“next May 10th”被解析成05/10/2019(相对于2018年而言),等等。
我们回头看LUIS返回的JSON结果,可以发现其中包含了一个名为entities的空数组。该空数组是从用户的话语输入中识别出的实体的占位符(placeholder)。LUIS应用从输入中识别实体没有数量限制,可以识别任意数量的实体,识别出的每一个实体的数据格式如下:

image.png

image.png

被消解的对象会依据所识别到的不同实体类型的差异来包含一些额外的属性信息。下面我们将介绍各种不同的预建实体类型、它们所支持的一些特性,以及允许开发者做哪些事情。

3.3.1 Age、Dimension、Money和Temperature

Age实体可以让LUIS应用识别检测出输入中的年龄表达,比如“five months old”“100 years”和“2 days old”。返回的结果对象包括数字格式的值和单位名称参数,如日、月或年,在结果对象的“resolution”中。
image.png

任意长度、重量、体积和面积都可以使用Dimension实体来检测,输入可以是“10 miles”“1 centimeter”和“50 square meters”等多种多样的变化形式。和Age实体一样,Dimension实体也包含值和单位。
image.png

Currency实体可以帮助LUIS应用检测识别出用户输入中的钱,在Currency实体的返回对象里同样包含单位和值两个属性。
image.png

Temperature实体可以帮助识别输入中的温度,同样也将单位和值两个属性包含在了返回结果的resolution中。
image.png

3.3.2 DatetimeV2

DatetimeV2是一个强大的分层次的实体,它替代了之前版本的datetime实体。层次实体(hierarchical entity)定义了分类和分类的成员,当某些实体相似且密切相关但具有不同含义时,使用层次实体是非常合理的。此外,DatetimeV2实体将时间解析为机器可以阅读的格式,比如TIMEX格式(代表“时间表示”;TIMEX3是TimesML的一部分),以及yyyy:MM:dd、HH:mm:ss和yyyy:MM:dd HH:mm:ss(三种格式分别对应date、time和datatime)。DatetimeV2 datetime类实体的例子如下:
image.png

DatetimeV2实体可以识别不同类、不同格式的时间。下面再给出一些实体的例子,它们分别表示其他各类DatetimeV2实体,并给出识别之后的返回对象。
第一个例子显示的是“builtin.datetimeV2.date”类实体,它对应输入中存在的日期信息,比如“yesterday”“next Monday”和“August 23, 2015”:
image.png
image.png

第二个例子显示的是“builtin.datetimeV2.time”类实体,它对应输入中存在的时刻信息,比如“1pm”“5:43am”“8:00”或“half past eight in the morning”:
image.png

第三个例子显示的是“builtin.datetimeV2.daterange”类实体,它对应输入中存在的以日期为粒度的时间段信息,比如“next week”“last year”或“feb 1 until feb 20th”:
image.png

第四个例子显示的是“builtin.datetimeV2.timerange”类实体。顾名思义,它对应输入中存在的以某一时刻为起点和终点的时间段信息,比如“1 to 5p”或“1 to 5pm”:
image.png

第五个例子显示的是“builtin.datetimeV2.datetimerange”类实体。顾名思义,它也对应输入中的时间段,但该时间段的起点和终点同时包括日期和时刻两部分内容,比如“tomorrow morning”或“last night”:
image.png

第六个例子显示的是“builtin.datetimeV2.duration”类实体,它对应输入中的持续时间信息,比如“for an hour”“20 minutes”或“all day”。此时,值的单位被解析为秒:
image.png
image.png

builtin.datetimeV2.set类实体表示一系列日期组成的集合,比如“daily”“monthly”
“every week”“every Thursday”。该类型的解析和上面几个datetimeV2类型的实体有所不同,实体中timex的解析主要包括两种方式。第一种,timex字符串符合Pn这种模式,其中[n]是数字,[u]是日期的单位—D表示天、M表示月、W表示周、Y表示年。Pn表示“every n units”,比如P4W表示每四个星期、P2Y表示每两年;第二种,timex字符串按照日期的格式和Xs的模式表达任意的值,比如XXXX-10表示每个十月份、XXXX-WXX-6表示某一年中任意一周的周六。
image.png
image.png

如果输入中存在含义模糊不清的日期或时间,那么LUIS将返回多个解析的选项。举一个例子,如果今天是July 21,我们输入的话语是“July 21”,那么LUIS将返回今年和去年的July 21。同样,对于时间而言,如果输入话语中没有明确a.m.或p.m.,那么LUIS会把a.m.和p.m.的时间都返回。两种模糊不清的具体例子如下:
image.png
image.png

DateimeV2实体功能十分强大,展示了LUIS强大的自然语言理解能力。

3.3.3 Email、Phone Number和URL

Email、Phone Number和URL这三个类型的实体都是基于本文的。LUIS可以从用户的输入中识别出这些实体,LUIS完成此操作非常方便,开发者完全不必自己在系统中通过正则表达式的方法来实现这三类实体的识别。我们通过三个例子分别来展示LUIS识别这三类实体的返回结果:
image.png

3.3.4 Number、Percentage和Ordinal

LUIS可以抽取和处理数字(Number)与百分数(Percentage),用户的输入既可以是数字的形式(比如100)也可以是文本(比如one hundred)的形式,甚至可以处理像“thirty-eight and a half”这类形式复杂的数字。
image.png
image.png

LUIS的序数词(Ordinal)实体能识别话语输入中的序数词,序数词实体同样支持数字或者文本形式的输入。
image.png

3.4 实体训练

我们继续回到Bot应用程序开发,
并应用刚才学到的一些东西。正如在编写与日历相关的Bot应用程序时,我们选择的最明显的预建实体是datetimeV2。在LUIS的Entity页面中,点击“Manage prebuilt entities”,并选择datetimeV2,如图3-18所示。

image.png

在添加实体之后,我们开始训练模型。在交互式测试UI中,当输入“add calendar entry tomorrow at 5pm”时,我们将看到如图3-19所示的结果。
可以看到,整个过程非常简单。接
着,我们再次将LUIS应用发布到Staging slot,并使用curl运行相同的查询,我们收到以下JSON:

image.png

image.png

到现在,我们可以在LUIS应用的任何意图中识别出datetime实体了,datetime实体会和LUIS应用的所有意图都关联,而不仅是和AddCalendarEntry这一个意图相关。此外,我们将继续添加Email预建实体,再次训练并发布到Staging slot。我们可以尝试一下“meet with szymon.rozga@gmail.com at 5p tomorrow”这样的话语输入,并获得我们所期望的结果。
image.png

练习3-3
添加对Datetime和Email实体识别的支持
我们会在该练习中将刚才学习的预建实体加入到我们正在开发的LUIS应用Calendar-BotModel中。

  • 添加email和datetimeV2预建实体到LUIS应用中,并且重新训练模型。
  • 转到AddCalendarEntry意图中,输入一些包含Datetime和Email的话语,查看LUIS是否识别了这些实体并将其高亮显示。
  • 将LUIS应用发布到Staging slot。
  • 使用curl命令查看返回的JSON结果。

预建的实体非常容易使用。作为进一步的练习,在模型中添加一些其他预建实体,以了解它们如何工作以及如何在不同类型的输入中获取它们。如果你想阻止LUIS识别它们,则只需将它们从LUIS应用程序的实体中删除。

3.5 自定义实体

预建实体无需任何额外的训练就可以为我们的模型做很多事情。在我们的CalendarBotModel LUIS应用程序中,日历条目(calendar entry)里包含一些我们感兴趣的属性。但如果说我们需要的一切都可以由现有的预建实体提供,那不太现实。
我们通常希望为会议提供一个会议主题(不仅是类似“与Bob会面”这么简单)和会议位置。会议主题和位置都是任意字符串,那么此时我们如何实现实体抽取的目标呢?
LUIS支持开发者训练自定义实体来检测这些概念,并从用户的输入中抽取它们的值,这就是实体抽取算法的强大功能所在。在自定义实体中,我们可以决定LUIS何时应该将词语识别为实体,何时应该忽略它们。NLP算法考虑上下文。例如,给定多个话语样本,我们可以教LUIS并确保它不会混淆Starbucks(星巴克)与Starbuck—小说《Moby Dick》中的角色名。
我们可以在LUIS中使用四种不同类型的自定义实体:简单、复合、层次和列表。下面我们分别介绍每一类自定义实体。

3.5.1 简单实体

简单的自定义实体是诸如日历条目或预构建的电子邮件(Email)、电话号码(Phone Number)和URL之类的实体,用户输入中的词语可以基于其在话语中的位置和它周围词语的上下文而被识别成相应类型的实体。LUIS可以轻松创建和训练简单实体,我们首先创建一个名为“Subject”的简单实体。
当我们要告诉日历机器人关于条目的主题名称时,我们应该想清楚。假设我们希望接受诸如“meet with Kim about mortgage application at 5pm”这样的输入。在这个例子中,Subject实体将是“mortgage application”,下面我们来实现一下。
回到Entity页面并单击Create new entity按钮以创建一个名为Subject的简单实体,如图3-20所示。

image.png

点击完成之后,该条目会被添加到LUIS应用的实体列表里,训练实体的过程与训练意图在相同的页面中,我们点击跳转到AddCalendarEntry意图的页面,并添加“meet with Kim about mortgage application at 5pm”话语,如图3-21所示。注意,此时这只是一个普通的话语,没有自定义的简单实体被识别。

image.png

现在,我们移动鼠标到词语mortgage和application上会发现词语可以被选定,点击词语mortgage和application,此时短语“mortgage application”被选定,同时页面会在浮窗中枚举展示你LUIS应用程序中所有的自定义实体;选定出现的Subject实体。此时,输入到LUIS中的话语将如图3-22所示。

image.png

保存话语并重新训练应用程序。由于我们目前只提供一个话语输入,因此当前LUIS在确定Subject实体方面的效果还没那么出色。实体识别比意图分类更难做,它需要更多输入样本,我们可以在话语编辑器(utterance editor)里向日历条目意图添加更多的话语输入。图3-23中展示了添加的一些输入样本。

image.png

注意,在添加完之后,此时没有Subject实体被检测到,我们再次强调,系统能够识别实体的前提是有相当多的输入样本被输入并训练。我添加了十多句在某一位置具有Subject实体的话语,如图3-24所示,并在具有该实体的词汇或短语上标记了自己定义的简单实体。我们的话语应该精心设计,以确保向LUIS提供尽可能多的变化。通常,每个变体还需要包括一些样本以保证算法训练的模型可以足够准确地在话语的上下文中找到特定实体。
在使用训练数据训练完模型后,我们通过页面的交互式测试工具发现模型在实体识别方面变好了很多。现在,我们随机地输入一句测试话语“hi let us meet about lawn care and harmonicas at 1:45p”,便会看到如图3-25所示的结果,实体都被识别出来了。但是,如果我们变换一下输入的长度或者换用同义表达,那么LUIS可能无法正确识别这些实体。这意味着我们还需要添加训练数据,进一步训练实体识别模型;我们会将此作为练习留给读者。

image.png

image.png

尽管表达方式的变化导致模型可能对某些输入中的实体无法识别,但我们现在很好地掌握了自定义的简单实体—日历Subject实体的定义、使用和训练。事实上,开发者很难考虑到用户对一句话所有可能的表达方式,但LUIS应用程序开发的方式就是这样。因此,在发布此应用程序前,必须反复地进行交互测试,查看返回的JSON结果,这非常重要。

image.png

可以看到,time实体被正确地识别,Subject实体返回相关的实体值。此外Subject实体还返回了一个得分,这里的得分与前文所述的意图得分含义相似,它是相对理想实体的距离度量。与意图不同的是,LUIS不会返回所有的实体及其得分,而仅返回分数高于阈值的简单实体和分层实体;对于预建实体,此分数是隐藏的。
训练实体的好处在于,即使包含实体的话语样本在AddCalendarEntry意图中定义,这些实体也会被其他意图共享。意图和实体并不直接相互关联。比如,我们输入“cancel meeting about olympic hockey”,它在交互式测试工具中的返回结果如图3-26所示。

image.png

对于该输入,另一个观察结果是在识别为DeleteCalendarEntry意图方面得分较低(0.64)。这是因为我们在AddCalendarEntry意图中添加了更多的话语,而在DeleteCalendarEntry和EditCalendarEntry中添加的话语样例要少得多。我们可以继续添加一些包含Subject实体的同义替换表达作为输入样例,然后重新训练模型,就可以改善这一点。
练习3-4
训练Subject实体并增强我们的LUIS应用
我们将通过本练习掌握提升LUIS应用对实体识别的方法。

  • 添加自定义简单实体Subject。
  • 在意图中添加包含Subject实体的话语,并交互式地进行训练和测试,反复迭代这个过程,直到LUIS应用对实体的识别结果满足开发者的要求。
  • 一开始,至少保证在LUIS中输入25到30个话语样本,同时确保为每个话语样本提供多个同义语句。
  • 后续输入中,确保所有的意图都具有15到20个话语样本作为输入,同时每个意图对应的输入话语要包含所有的实体。
  • 训练并将LUIS应用发布到Staging slot。
  • 使用curl工具查看返回的JSON结果。

训练自定义实体相对而言更有难度一点,但经过反复的交互训练,我们会看到LUIS逐渐可以识别这些自定义的实体。注意话语输入中需要明确训练的实体:Subject实体和实体后面的日期、时间等。开发者可能已经注意到训练数据的数量非常重要,像LUIS这样的自然语言理解系统获得的训练数据越多,效果就越好。如果LUIS最终的训练结果与你的期望有差距,那么很可能不是LUIS的性能问题,而是你的LUIS应用需要更多的训练数据来进行训练。
我们要创建的第二个自定义简单实体名为Location。和前面创建的Subject实体类似,Location实体可以是任意与位置相关的文本内容,因此我们需要使用许多训练样本来训练LUIS。
接下来我们将尝试在AddCalendarEntry意图中添加训练话语,所添加的话语采用下面的这些形式:
image.png

此外,我们还应该在话语中添加一些datetime实例。因为我们要让LUIS能区分Location和Subject两个实体的识别,所以训练Location实体将变得更加棘手。Location和Subject都可以是任意与它们含义(位置、主题)相同的词语,所以在训练这两个实体时,需要给LUIS提供大量训练数据。在正在开发的LUIS应用程序中,我们添加了30多个话语,这些话语中要么仅包含Location实体,要么既有Location实体又有其他的实体。经过多次训练、测试之后,我们得到了满意的结果;当输入“meet for dinner at the diner tomorrow at 8pm”之后,得到了如下的JSON返回结果:
image.png
image.png

我们建议开发者在实体识别的训练阶段投入较多的精力进行调优,这对理解自然语言和训练自然语言理解系统(比如LUIS应用)的复杂性和模糊性非常有帮助。
练习3-5
训练Location实体
在本练习中,我们将向LUIS应用中添加Location实体,通过本练习,读者会发现这比之前只单独训练Subject实体要花费更多的时间。

  • 按照前面章节内容的步骤,添加Subject实体。
  • 向AddCalendarEntry意图中添加包含Location内容的话语。训练并通过页面的交互式测试工具进行测试。
  • 向LUIS中添加至少35到40个话语样本,应用中设计的意图越多,则需要的话语样本就越多。在添加话语的过程中,同时训练和测试LUIS应用。注意输入的话语样本的多样性,通过同义表达来使话语变化。
  • 将LUIS应用发布到Staging slot。
  • 使用curl工具查看返回的JSON结果。

该练习有助于提升LUIS应用(在一个话语中存在多个实体的时候)对实体的识别能力。

3.5.2 复合实体

到目前为止,我们完成了对LUIS大部分内容的实践。使用前面介绍的意图分类和简单实体抽取,我们初步开发了一个和日历相关的LUIS应用程序。虽然我们讨论了一些简单实体,但很快也会遇到一些复杂的自然语言理解场景。没有像LUIS那样的工具,实现这些语言理解任务将非常复杂和困难。
现在我们再来看一个自然语言理解中的新场景。我们的LUIS应用当前支持用户输入如下形式的话语:
image.png

但如果用户想一次性添加多个日历条目该怎么办?比如用户想输入如下形式的话语:
image.png

如果我们对LUIS应用训练得足够充分,那么当然可以处理这类输入,并且LUIS应用会识别出两个Subject实体、两个Location实体和两个datetime实体。对于输入“meet at culture for coffee at 11am and at the off?ice for a code review at noon”,返回的JSON结果如下:
image.png
image.png

但是,我们如何区分哪些实体分在哪一组里呢?每个Location实体分别和哪个Subject实体匹配并分在一个组?使用JSON里的startIndex属性似乎有效,但并不能确保对每一个输入都有效。
幸运的是,LUIS可以将实体分组成复合实体(composite entity)。不必像上面的返回结果那样烦琐,LUIS将向我们返回:每个实体分别属于哪一个复合实体。这样,我们就能很容易地看出有两个独立的AddCalendar请求:一个是“11 a.m. coffee at Culture”,另一个是“a code review in the office at noon”。
复合实体可以在LUIS的Entity页面中创建。图3-27展示了在LUIS中创建复合实体的过程,点击Create new entity按钮,在Entity name中输入所要创建的复合实体的名字,在Entity type选项上选择Composite实体类型,在Child entity选项上为新建的复合实体添加子实体。我们使用CalendarEntry作为该复合实体的名字。

image.png

在创建CalendarEntry复合实体之后,我们需要训练LUIS应用,使其能识别复合实体。回顾AddCalendarEntry意图,训练LUIS应用最简单的方式是使用大量包含三种实体并将其打包成复合实体的话语来进行训练。图3-28提供了一个这样的例子。

image.png

CalendarEntry;这是典型的复合实体的例子
点击第一个Location实体,会出现一个浮窗,浮窗里的选项包括重新标记实体或将其打包到复合实体中。我们点击“Wrap in composite entity”选项,并点击选择我们创建的CalendarEntry复合实体,如图3-29所示。

image.png

接着移动鼠标至Subject实体和datetimeV2实体,重复上述过程。注意完成之后下划线会扩展到刚才所操作的每一个实体,如图3-30所示。

image.png

接着标记第二个CalendarEntry实体,完成之后结果如图3-31所示。

image.png

对于后续输入的包含三类实体的其他话语,我们同样按照上面的过程对实体进行标记,并对LUIS应用进行训练和发布。在完成所有这些步骤之后,LUIS便可以识别复合实体了。我们只展示识别结果中相关的属性,如下所示。image.png
image.png

练习3-6
复合实体
在本练习中,我们将回顾:向LUIS应用中添加复合实体。

  • 创建名为CalendarEntry的复合实体,并且指定其子实体(child entity)为datetimeV2、Subject和Location实体。
  • 对包含datetimeV2、Subject和Location三种实体的话语进行实体标注(label),并对LUIS应用进行训练。
  • 再输入一些标注为CalendarEntry复合实体的话语样本,对LUIS应用进行训练。
  • 将LUIS应用发布到Staging slot。
  • 使用curl工具查看返回的JSON结果。

复合实体是几种实体的组合,它支持LUIS应用对更复杂的表达进行封装,相当于把几种标注的实体分成一组、打包封装。

3.5.3 层次实体

层次实体支持开发者定义实体和该实体的子实体。开发者可以把层次实体视为在实体之间定义父/子类型关系。我们之前遇到过这种类型的实体,比如datetimeV2实体。datetimeV2实体包括daterange、set和time等诸多子类型实体。
LUIS支持开发者轻松地创建自定义的子类型实体。假设我们想在模型中添加支持将日历条目可见性指定为公共或私有的功能,那么我们可能添加如下的话语:
image.png

话语中的词汇“private”“invisible”是用于表明日历条目是否可见的简单实体。但是,这种情况下创建层次实体比使用简单实体更加高效。我们能否通过只看Visibility属性的值来确定它是否应该是私人会议?如果开发者坚持选择“是”或者“否”作为唯一的答案,那么答案为“是”。但需要注意的是,自然语言具有模糊和不确定的特性,一个意思可以有多种表达。比如对于私人日历条目,用户的话语中可能说的是“invisible”“private”“privately”
“hidden”,同样的道理,对于公开的日历条目,用户的表达也可能多种多样。如果我们采取这种提供一组封闭表达的方法(即在代码中写死),那么我们将不得不在每次出现新表达时更改代码。应该使用层次实体而不是简单实体的原因是层次实体在上下文中的统计模型可以被子类型共享。一旦层次实体被识别出来,下一步识别子实体的过程本质上就变成了一个分类问题。在上述这类话语场景中,比起使用两个简单实体,使用层次实体能更好地发挥LUIS的性能。而且,这种方式也比手动写大量规则的代码具有更高的开发效率。
图3-32展示了如何创建一个新的层次实体的过程。我们访问LUIS的Entity页面,点击Create new entity,然后从Entity type下拉选项中选择Hierarchical。接着,我们对parent entity进行命名并且添加child entity。完成这些之后,仍然是进入意图页面,添加话语并训练LUIS应用。我们选择进入之前的AddCalendarEntry意图并添加一些话语。

image.png

在开始识别输入中的实体之前,需要对输入进行标注,让LUIS非常清楚它在何处以及如何遇到表示公开和私人的修饰符。图3-33显示我们输入了10条话语,并进行了标注。

image.png

一旦完成训练并发布应用之后,我们可以通过curl工具来看一下LUIS返回的测试结果,如下所示:
image.png

3.5.4 列表实体

到目前为止,我们可以从用户的输入中识别出预建实体、简单实体、复合实体和层次实体。每次我们添加一类新的实体并训练LUIS应用时,我们都会注意到正在被训练的模型数量有所增加,这是因为LUIS应用中每一个意图/实体对都与一个模型对应。到目前为止,我们至少应该有10个模型,每个模型在我们训练LUIS应用时都会被重建。
列表实体和上述实体不同,LUIS应用不需要使用列表实体进行训练,所以列表实体与机器学习无关。列表实体由一系列的术语和这些术语的同义词组成,比如,我们想定义City,则可以添加New York,以及它的一些同义词NY、The Big Apple、The City That Never Sleeps、Gotham、New Amsterdam等。LUIS会将这些替代词统统解析为New York。
在创建列表实体类型之后,我们会重定向到列表实体编辑器页面,在这里开发者可以为刚才创建的该类列表实体添加典型术语(canonical term)以及它的同义词。该页面还支持用户添加新的术语和同义词,并且会额外地推荐一些和已添加的术语密切相关的术语。列表实体最多支持20?000个术语(含同义词),每个LUIS应用最多支持50个列表实体。图3-34展示了一个自定义列表实体的例子。

image.png

由于列表实体不需要LUIS应用训练学习,因此列表之外的词语不会被识别出来。比如,LUIS看到输入中的“Gotham”时,会将其识别为New York,但对于“Gohtam”则不会被LUIS识别。顾名思义,列表实体其实就是一个查询列表。
image.pngimage.png

在使用列表实体API时,LUIS会将识别出的实体高亮显示,并返回列表实体的典型名称。这允许我们消费应用程序,以忽略术语的所有可能的同义词,并根据典型名称执行逻辑。列表实体在开发者提前知道某一内容的所有可能值时会非常好用。

3.5.5 正则表达式实体

LUIS支持开发者创建正则表达式实体。正则表达式实体就像列表实体那样,和上下文无关。比如,如果我们期望始终使用语法KB143230(KB后面有6位数字)来呈现某一知识,那么我们可以创建一个带有kb[0-9]{6,6}正则表达式的实体。经过训练之后,话语输入中任何匹配到该正则表达式的内容都将被识别为该正则表达式实体。

3.6 预建域

通过上面的实践,我们对构建NLU模型的一些挑战已经有所了解。可用一些机器学习工具快速地进行开发,但我们必须保证使用了质量好的数据来进行训练。人类需要浸入到一门语言中并进行多年的日常使用,才能真正理解一门语言,但现在我们假设的是LUIS应用仅使用10个样例进行学习便能从话语输入中识别出实体。因此,在遇到LUIS无法识别和理解的情况时,我们需要让LUIS学习新的内容。
为了实现这一目标,很多NLU平台提供了预建模型和预建域。从本质上讲,LUIS和其他平台的创建者希望为我们提供一些开发优势和捷径,让我们可以轻松地将这些域包含在我们的应用程序中并训练LUIS。图3-35展示了一些预构建的模型。
我们可以通过在BUILD页面中点击左下角的“Prebuilt Domains”链接,从而找到预建域。在撰写本书时,这一功能尚处于预览阶段,而当读者阅读此书时则可能已经做了修改。LUIS包括一系列域,比如相机、家庭自动化、游戏、音乐甚至类似于我们一直在学习开发的日历。事实上,我们会在练习3-7中完成这些。“Learn more”文本链接会跳转到一个页面,该页面详细描述了每个域所引入的意图和实体,以及不同的语言都支持哪些域。

image.png

在开发者向应用中添加了一个域之后,LUIS会将该域下的所有意图和实体加入所开发的LUIS应用中,以使开发的LUIS应用的功能更强大。但有时我们可能需要去掉某些意图或添加新意图以补充预先构建的意图;或者我们还需要使用更多样本来训练预构建的域。我们建议开发者将预构建的域视为基础,我们的目标是在预构建域的基础上扩展它们以构建更出色的体验。
LUIS的一个转折点
LUIS几年来已经更迭了多个版本,甚至在作者撰写本书期间LUIS还更改过UI和一些功能。LUIS曾经包含Cortana应用(微软小娜),任何开发者都可以利用已知的App ID,并使用他们的订阅密钥来进入。Cortana应用内定义了许多预构建的意图和实体,但它是一个封闭的系统,因此开发者无法对它进行个性化的定制或者增强。从那以后,微软就摆脱了在LUIS中内建Cortana这一功能,转而支持预建域。但是,将自己的模型公开和共享以便其他开发者可以通过使用自己订阅的密钥来调用该共享的模型的理念并没有被去掉,开发者仍然可以通过Settings页面获得该功能。
练习3-7
使用预建域
在本练习中,我们将使用预建域Calendar来创建一个LUIS应用,该应用和我们在前文内容中创建的应用功能相似。

  • 创建一个新的LUIS应用。
  • 在LUIS页面中导航到prebuilt domains链接,并添加Calendar域。
  • 训练LUIS应用。
  • 使用交互式测试检测应用在识别意图和实体方面的性能如何?在设计和性能方面和我们之前开发的应用相比如何?

预建域非常有用,但LUIS需要充足的训练才能产生一个性能优越的模型。

3.7 短语列表

到目前为止,我们一直通过学习和实践多种方法来增强应用。有时我们发现训练好的模型性能并不满足我们的要求,可能话语中的实体识别准确率并没达到我们的要求。可能我们构建的LUIS应用程序是专门用来处理一些内部术语的,它们与LUIS应用当前使用的语言有很大的差异;也可能我们无法做到用实体的所有可能值来训练LUIS,因为我们希望实体具有一定的灵活性。
在这种情况下,提升LUIS性能的方法是使用短语列表。短语列表是训练LUIS应用时使用的“暗示”,而不是“写死”的规则。短语列表支持我们向LUIS提供一系列具有相关性的词汇和短语,这样一组词语就是一种“暗示”,LUIS就会用相似的方式识别所有具有相关性的词语。在实体值未被正确识别的情况下,我们可以将所有已知的可能值作为短语列表输入并将列表标记为可交换(exchangeable),这就提示了LUIS?:在一个实体的上下文中,这些值都会以相同的方式被处理。如果我们试图用可能不熟悉的单词来提高LUIS的词汇量,那么短语列表不会被标记为不可交换(nonexchangeable)。
现在假设我们想提高这个Calendar模型识别私有实体的性能。毕竟,表示我们想要私有会议的方式有很多。最开始,我们可以添加一个包含各种表示私有含义的词语的短语列表。图3-36展示的是LUIS中添加短语列表的页面,具体操作是先在BUILD页面通过选择Phrase Lists跳转到该页面,然后点击Create new phrase list。
每个短语列表都需要一个命名和一些值,我们在Value区域逐一地把短语列表包含的值输入进去,点击回车时便完成了输入。Related Values包含的是Value区域的同义词,它们由LUIS自动加载。接着我们点击下方的checkbox将短语列表的值选择为可交换(interchangeable)。

image.png

在训练之前,我们首先不使用短语列表,然后试着输入一些包含私有会议信息的话语。如果我们输入类似“Meet in private”“Meet in secret”或者“Create a hidden meeting”这样的话语,那么LUIS并不能识别实体。但是,如果我们使用短语列表并进行训练,此时便会发现LUIS可以将这些实体从话语中识别出来。
练习3-8
训练功能
在本练习中,我们将通过添加功能来提升LUIS应用的性能。

  • 向LUIS应用中添加Visibility(Calendar条目的可见性,表明是公开还是私有)层次实体。
  • 添加短语列表以提升LUIS对私有Visibility实体的识别性能。
  • 将LUIS应用发布到Staging slot。
  • 使用curl工具查看返回的JSON结果。
  • 测试“将短语列表设置为interchangeable”对LUIS应用识别实体性能产生的影响。

短语列表是LUIS中一项十分强大的功能,能帮助LUIS应用在识别不同实体时获得更好的性能。
练习3-9
添加Invitee实体
到目前为止,我们的Calendar中还没有识别会议参加者实体的功能,在本练习中我们将解决这个问题。

  • 添加一个新实体,并命名为Invitee。
  • 复查之前的每一个话语输入,将Invitee实体标注出来。
  • 如果需要更多的训练,则再加入一些包含Invitee实体的输入。
  • 将Invitee实体添加到CalendarEntry复合实体中。
  • 训练LUIS应用,并确保所有的意图、实体的识别效果依然能达到要求。
  • 将LUIS应用发布到Staging slot。
  • 使用curl工具查看返回的JSON结果。

如果经过本次练习之后,我们开发的LUIS应用识别实体和意图的性能很好,那么读者已经可以熟练使用LUIS了。

3.8 主动学习

我们花了数周时间训练模型,然后进行了好多轮测试,接着将应用程序部署到生产环境中,最后开启Bot服务。下一步呢?我们如何才能知道我们训练和发布的LUIS应用是不是它能达到的最好性能呢?我们怎样才能知道是否有一些用户输入了意料之外的话语,让机器人无法理解并导致了一次非常差的用户体验呢?bug报告自然有效,但那样的话我们必须得到出现bug时的反馈。我们能否在问题发生时就立马发现呢?此时我们可以利用LUIS的主动学习功能来达到这个目的。
回顾一下,监督学习是从有标签的数据中进行学习,非监督学习是从没有标签的数据中进行学习,半监督学习则是介于监督学习和非监督学习之间。主动学习是一种半监督学习,学习算法要求监督者为新的数据打标签。对于一个LUIS处理不了的输入,它可能会要求熟悉该应用的开发者为这个新的用户输入进行数据标注。这提高了模型的性能,并且随着时间的推移,通过使用真实的用户输入作为样本数据来进行训练,也会使我们开发的LUIS应用更加智能。
开发者可以通过BUILD页面中的“Review endpoint utterances”链接来使用LUIS的主动学习功能(如图3-37所示)。在训练完LUIS应用之后,我们继续使用端点(endpoint)来测试不同话语输入的结果。LUIS将其主动依赖于针对端点的输入,而不是交互式测试功能。

image.png

3.9 仪表板概览

到目前为止,我们已经训练完LUIS应用了,并且进行了测试,此时LUIS仪表板提供的数据同样值得注意。仪表板让我们能对LUIS应用的整体状态有一个快速了解,包括它的使用情况、我们已经训练的数据量等。
在仪表板中,最上方的信息是我们上一次训练和发布LUIS应用的信息,如图3-38所示。另外,我们还能得到目前使用的意图和实体的数量信息、创建的列表实体数量以及到目前为止标记的话语数量。

image.png

仪表板展示的第二部分内容是用户通过不同API来使用LUIS应用的情况,如图3-39所示。我们可以监测各个端点的点击数,并且时间范围可以从上一周持续到上一年。这部分数据只有在将LUIS应用发布到Production slot时才能获取。

image.png

仪表板中最后一部分内容是意图和实体的统计情况,如图3-40所示。图中展示的是,每个意图所使用的训练话语占据所有训练话语的百分比分布。我们明显可以看到有4个意图使用的话语明显比其他意图使用的话语更多。实体和意图一样,也可以通过仪表板进行展示。注意,不平衡的分布并不表示需要针对某一意图或者实体进行更多的训练。

image.png

3.10 LUIS应用管理与版本更新

到目前为止,我们的开发流程主要是:添加话语样本、训练LUIS应用、发布LUIS应用。在开发阶段,我们反复重复这一流程;但是,一旦将LUIS应用发布到生产环境中,我们便应该谨慎处理这些应用,添加一个新的意图或者实体可能给LUIS应用中的其他部分带来无法预料的影响。因此,最好的方法是在生产环境之外开发已经存在的LUIS应用,这样我们可以独立于生产环境之外对新意图或实体进行测试。
我们在前面的章节中介绍了Staging slot和Production slot,并且也已尝试过不必将应用发布到production端点而对应用中新增的功能进行测试。LUIS应用管理的一个基本规则是在Staging slot中存放LUIS应用的开发版本和测试版本,在Production slot中存放LUIS应用的生产版本。一旦开发中的版本经过测试已经适合上线,那么发布到生产环境时,我们便可将它从Staging slot移动到Production slot。但如果我们发现生产版本中的LUIS应用存在一些错误该怎么办?或者我们想将Production slot中的LUIS应用进行卷回操作该怎么办?此时,我们需要使用LUIS的版本管理功能。
LUIS支持开发者在任意时间创建应用的版本号。到目前为止,我们一直工作在版本0.1中。在适合将版本0.1发布到生产环境时,我们将其发布并克隆,得到一个新版本0.2。我们再将版本0.2设置为活跃状态(Active),此时我们就工作在版本0.2中了。如果在开发过程中不小心将还未成熟的版本0.2发布到了生产环境中,那么我们可以轻松地卷回到版本0.1,并继续发布版本0.1。一旦版本0.2已经适合发布到生产环境,我们便可将其部署发布到Production slot,然后克隆版本0.2,又产生新的版本0.3,同时我们将新版本0.3设置为Active,并且也可以在需要的时候将LUIS应用的发布版卷回到版本0.2。如此循环,完成版本更新。整个工作流程如图3-41所示。

image.png

我们可以通过设置(Settings)页面访问LUIS应用的版本信息。图3-42和图3-43展示的是将版本0.1克隆到版本0.2之后该页面显示的一些信息。

image.png

注意在关闭版本0.1之后,它在Staging slot中依旧存在,只是版本0.2才是Active版本。LUIS不支持分支操作,如果多个开发者想更改同一个版本,那么它们不能各自克隆一个版本,然后在修改之后再合并分支。要想实现类似于Git的分支操作,则需要通过点击页面上的Export Version按钮,下载LUIS App JSON,如图3-42所示,然后利用源代码管理工具进行类似于branch和merge命令的分支操作,最后点击Import new version按钮从JSON文件中上传一个最新的版本。

image.png

在该页面中,开发者也可以将项目的共同开发人员加入进来,这样开发者所在的组织的同事可以协助开发者对LUIS应用进行开发、训练和测试。在完成本书的写作时,该功能还没有加入一些特殊权限的管理,因此除了添加或移除别的共同开发者之外,所有加入的开发者都能对LUIS应用做任何操作(如图3-44所示)。

image.png

3.11 拼写检查

拼写检查是集成在LUIS中的一个非常好的功能,它能自动修复用户输入中的拼写错误。用户的输入往往非常多样和杂乱,因此拼写错误非常常见。
拼写检查通过在Bing的拼写检查器服务上运行用户查询,获取可能更改的查询并修复拼写错误,并在LUIS上运行经过修复的查询。该功能可以通过加入查询参数spellCheck和bing-spell-check-subscription-key实现,其中subscription key(订阅密钥)可以在Azure Portal中获取。我们将在第5章介绍Azure Portal,并在第10章使用LUIS的拼写检查API。
拼写检查非常有用,但开发者也需要注意,如果所开发的LUIS应用中包含具体领域的词语、术语的实体,或者某些并不严格遵循英语规范的词语,那么LUIS处理的查询中可能被替换过词语,因此无法识别对应的实体。比如,LUIS可能将一个词语分解成多个词汇进行处理,这显然不是我们想看到的。又比如,假设某个LUIS应用处理的是金融领域的内容,那么LUIS应用可能会改变该领域中的一些术语,比如将VEA(Vanguard ETF)改成VA进行处理。而在美国,VA一般指的是弗吉尼亚州。很显然,VEA和VA的含义差别非常大,因此建议谨慎合理地使用拼写检查功能。
拼写检查在LUIS的返回结果中就能看到,也就是返回结果中的alteredQuery域和query域是一起传入到LUIS模型中的。一个典型的curl请求和JSON响应如下所示:
image.png

3.12 导入/导出LUIS应用

LUIS应用可以以JSON文件的形式导出,同样也可以将应用再导入到LUIS中。JSON文件中既包括自定义的意图、实体以及应用使用的预建实体,也包括短语列表。此外,被导出的LUIS应用中还包括大量的话语样本以及在上面标注的意图标签、每个实体在话语中的起始位置。在LUIS的My Apps页面点击Export App按钮,或者在Settings页面点击Export Version,即可完成LUIS应用的导出,如图3-41所示。
虽然导出的应用程序的格式特定于LUIS,但我们可以用其他工具编写代码来解析LUIS应用中的数据。从代码管理的角度来看,最好导出LUIS应用程序并将JSON文件放到源代码管理中,因为发布操作是不可逆转的。开发团队如果遵循这种策略:在Production slot中发布应用就表明创建了新的应用程序版本,那么就不会有上述问题。
我们在工作中收到的最多的问题就是“为什么不能将一个应用导入到一个已经存在的应用中?”。原因在于,这种需要智能合并的方式比较难以实现,特别是存在不同意图或完全不同内涵的同名意图的情况下。由于每个应用程序都有不同的语义,因此这种合并将是一项非常艰难的任务。我们建议使用Git来管理和合并应用程序JSON代码,或使用LUIS Authoring API创建自定义代码进行合并。

3.13 使用LUIS Authoring API

在谈到LUIS及其兼容性时,开发者时常会考虑这些功能能否通过API完成,答案是肯定的。LUIS的Authoring API(创作API)支持开发者通过使用API完成我们之前在UI(LUIS应用的网页页面)上所进行的所有任务。Authoring API主要包括以下内容:

  • App:添加、管理、移除和发布LUIS应用。
  • Example:向某个版本的LUIS应用中上传一些话语样本。
  • Feature:在某个版本的LUIS应用中添加、管理或者移除短语或模式。
  • Model:添加、管理或移除自定义的意图分类和实体抽取;添加/移除预建实体;添加/移除预建域。
  • Permission:添加、管理和移除应用中的用户。
  • Train:排列用于训练的应用版本,并获取LUIS应用的训练状态。
  • User:管理LUIS应用的订阅密钥以及外部的key。
  • Version:添加和移除版本;将key与版本关联;导出、导入和克隆某个版本的LUIS应用。

LUIS的API非常丰富,支持应用训练、自定义的主动学习,还支持CI/CD类型的场景。API Reference Docs(API参考文档)非常适合用于了解和学习LUIS Authoring API。

3.14 解决遇到的问题

我们一直专注于LUIS本身,以及通过将自定义意图分类器和自定义实体抽取器与预建实体和预建域相结合来创建应用程序的过程。在这个过程中,我们发现机器学习模型有时性能并不好,这是因为我们一定会遇到一些奇怪的应用场景,在这些场景中意图识别和实体抽取会很困难。我们在这里列举一些问题:

  • 最容易遇见的问题之一是训练完模型之后没有发布。在测试LUIS应用时一定要确保已将LUIS应用发布到了Staging slot,并且确保在调用API时根据需要传递staging标志位。
  • 如果某个意图被错误地识别,则应为该意图提供更多的话语样本。如果问题持续存在,则应转而分析意图本身是各自独立的意图还是可以合并的意图。此外,我们还要确保使用一些和我们所开发的LUIS应用无关的话语来训练None意图。测试数据非常适用于此目的。
  • 如果应用在识别实体方面有困难,则应该检查我们创建的实体。有些实体通常是意图中同一位置的单字修饰符,就像我们的Visibility(可见性)实体那样。另一方面,还有些更微妙的实体可以在话语中的任何位置(通常是某些词语的前缀或者后缀)。总体而言,实体识别遇到的问题主要可以通过以下几种方法来解决:

    • 添加更多的话语样本,并且保证话语样本之间具有表达上的变化和差异。
    • 查看实体是否应该设置成列表实体,可以通过两个原则来确定:实体是不是一个查询列表?LUIS应用在识别此类型的实体时是否需要具有一定的适应性,以适应该实体的多种变化?
    • 考虑使用短语列表来显示LUIS实体的外观。
  • 如果LUIS对某两个实体识别较差,那么检查一下这两个实体是否只是由于上下文语境的原因而使得它们在表达上有一点差异。如果是这样,则将它们设置为层次实体比较好。
  • 如果用户经常使用一些由多个实体组成的高级概念,则最好使用复合实体。

创建一个LUIS应用不仅需要开发技术,更需要调优,开发者可能需要花费大量时间在一些实体上进行优化。同时,请用统计学的观点来看待这个过程,LUIS应用必须先使用大量的话语样本进行训练,继而才能开始理解话语输入。最后,无论是使用LUIS还是任何其他NLU系统,我们都应明白这一点:作为人,我们可能将智能和语言理解看得理所当然;但对一个应用程序而言,能够快速地训练出像LUIS这样的语言理解应用已经很令人惊讶了。

3.15 结束语

经过本章的学习之后,我们已经具备了使用LUIS工具创建NLU模型的开发经验。概括地说,本章主要是通过使用预建实体、自定义意图和自定义实体来创建LUIS应用。我们对各种预构建的实体进行了探索和使用,并且尝试使用了LUIS提供的预建域。我们学习了训练LUIS应用、测试LUIS应用、将LUIS应用发布到不同类型的slot以及使用curl测试LUIS应用中的API端点。我们还使用短语列表以及LUIS的主动学习功能来进一步提升模型的性能。本章最后部分,我们还学习了版本更新、合作开发、在LUIS应用中集成拼写检查、将LUIS应用导入和导出、使用Authoring API、通用问题定位(common troubleshooting)技术。
另外,需要重申的是,我们在本章学习的一切概念和技术在LUIS之外的其他NLU平台上同样适用。无论是在机器人还是在语音助理中,训练意图和实体以及优化模型都是非常强大的技巧。下一步,我们该思考如何创建机器人,并且仍将使用LUIS,因为我们所开发的机器人中使用的工具就是LUIS。

相关文章
|
3月前
|
JavaScript 前端开发 Unix
Node.js Shell 脚本开发指南(中)
Node.js Shell 脚本开发指南(中)
61 0
|
3月前
|
JavaScript 前端开发 Shell
Node.js Shell 脚本开发指南(上)
Node.js Shell 脚本开发指南(上)
90 0
|
2月前
|
Web App开发 JavaScript NoSQL
深入浅出:构建基于Node.js的RESTful API
在当今快速发展的互联网时代,RESTful API已成为前后端分离架构中不可或缺的一部分。本文旨在为初学者和中级开发人员提供一个清晰、简洁的指南,详细介绍如何使用Node.js构建一个高效、可维护的RESTful API。通过结合实际案例,本文将从API设计理念出发,深入讲解如何利用Express框架及MongoDB数据库实现API的增删改查功能,同时探讨如何通过JWT进行安全认证,确保数据传输的安全性。此外,文章还将简要介绍如何使用Swagger生成API文档,使得API的测试和维护更加便捷。无论你是希望提升现有项目的API设计,还是想从零开始构建一个新项目,本文都将为你提供一条清晰的道路
N..
|
1月前
|
存储 JavaScript 前端开发
JavaScript语言的基本结构
JavaScript语言的基本结构
N..
15 1
|
1月前
|
Web App开发 JavaScript 前端开发
使用Node.js和Express构建RESTful API
使用Node.js和Express构建RESTful API
22 0
|
1月前
|
JSON JavaScript 前端开发
javascript语言ES5版本详解(一)
javascript语言ES5版本详解(一)
|
1月前
|
Windows
node搭建本地https和wss服务
node搭建本地https和wss服务
26 0
|
1月前
|
前端开发 JavaScript 开发者
编程笔记 html5&css&js 064 JavaScript 语言规则
编程笔记 html5&css&js 064 JavaScript 语言规则
|
2月前
|
Web App开发 JavaScript 前端开发
构建现代Web应用:Vue.js与Node.js的完美结合
在当今快速发展的Web技术领域,选择合适的技术栈对于开发高效、响应迅速的现代Web应用至关重要。本文深入探讨了Vue.js和Node.js结合使用的优势,以及如何利用这两种技术构建一个完整的前后端分离的Web应用。不同于传统的摘要,我们将通过一个实际的项目示例,展示从搭建项目架构到实现具体功能的整个过程,着重介绍了Vue.js在构建用户友好的界面方面的能力,以及Node.js在处理服务器端逻辑和数据库交互中的高效性。通过本文,读者不仅能够理解Vue.js与Node.js各自的特点,还能学习到如何将这两种技术融合应用,以提升Web应用的开发效率和用户体验。
|
2月前
|
NoSQL JavaScript 前端开发
深入浅出:使用Node.js和MongoDB构建RESTful API
在当今的软件开发领域,构建高效、可扩展的Web服务已成为开发者的重要任务之一。本文将引导读者通过现代JavaScript环境——Node.js,搭配流行的NoSQL数据库MongoDB,一步步构建一个RESTful API。不同于常规的摘要,我们将采用故事化的方式,通过一个虚构的应用“BookFinder”的开发旅程,带领读者理解API设计、数据库交互及安全性考量等关键知识点。无论你是前端工程师希望深化后端知识,还是后端新手寻求实践机会,本文都将提供你所需的指南和启示。