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
分数看起来不太好。这是一个进一步训练的机会。
训练路易斯意图
前面的示例显示了我们训练的意图的一些示例输入。你的任务是创建一个 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 是微软的云服务保护伞。我们将在第 5 章利用它来注册和部署一个机器人。要将密钥与应用相关联,我们必须使用 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
发布 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"
}
}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"
}
]
}
}
]
}添加日期时间和电子邮件实体支持
在本练习中,您将在您目前正在使用的 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 的示例要少得多。花些时间来改善这一点。在我们继续之前,为我们的新主题实体添加一些替换的措辞和例子。
培训主题实体,强化我们的 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 系统。
训练定位实体
在本练习中,您将在 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"
}
]
}
]复合实体
在本练习中,您将向 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 应用定义了许多预构建的意图和实体,但它是一个封闭的系统。你不能以任何方式根据自己的喜好对它进行定制或强化。从那以后,微软已经放弃了这个特性,转而支持预构建的域。但是,与他人公开共享您的模型以便他们可以使用自己的订阅密钥调用它的想法仍然可用,并可通过设置页面访问。
利用预先构建的域
在本练习中,您将利用预构建的日历域创建一个 LUIS 应用,该应用类似于我们在本章中构建的应用。
-
创建新的 LUIS 应用。
-
导航到预构建域部分并添加日历域。
-
训练应用。
-
使用交互式测试用户界面来检查应用的性能。它在探测意图和实体方面有多好?在设计和性能方面,它与我们创建的应用相比如何?
预构建的域对于一个域的开始可能是有用的,但是 LUIS 需要勤奋的训练来拥有一个真正表现良好的模型。
到目前为止,我们一直在探索不同的技术来创建伟大的模型。我们拥有所需的工具来确保我们能够为用户创造良好的对话体验。当我们训练 LUIS 时,有些情况下模型性能不如我们希望的那样好。实体可能不会像我们希望的那样得到认可。也许我们正在构建一个 LUIS 应用,专门处理内部术语,而这些术语并不完全是您的应用所使用的文化的一部分。也许我们还没有机会用一个实体的每一个已知的可能值来训练 LUIS entities,列表实体没有削减它,因为我们希望我们的实体保持灵活性。
在这种情况下,提高 LUIS 性能的一个方法是使用短语列表。短语列表是 LUIS 在训练我们的应用时使用的提示,而不是严格的规则。他们不是银弹,但可以非常有效。短语列表允许我们向 LUIS 呈现一类彼此相关的单词或短语。这种分组是对 LUIS 的一种暗示,即以类似的方式对待该类别中的单词。在实体值未被正确识别的情况下,我们可以将所有已知的可能值作为短语列表输入,并将该列表标记为可交换的,这向 LUIS 表明,在实体的上下文中,这些值可以以相同的方式处理。如果我们试图用 LUIS 可能不熟悉的单词来提高他的词汇量,短语列表就不会被标记为不可替换。
假设我们想要提高日历模型的私有可见性实体的性能。毕竟,有很多方式来表达我们想要一个私人会议。作为起点,我们可以添加一个短语列表,其中包含我们希望模型看到的所有不同的单词。图 3-36 显示了用于处理短语列表的 LUIS 用户界面。你可以通过选择构建页面下的短语列表项并点击创建新短语列表来到达这里。
图 3-36
我可能有点过火了。我责怪相关值函数。
短语列表需要一个名称和一些值。我们在值字段中逐个输入值。当我们按 Enter 键时,它会将它们添加到短语列表值字段中。相关值字段包含 LUIS 自动加载的同义词。然后,我们选中复选框,告诉 LUIS 这些值是可以互换的。
在培训之前,让我们在不启用短语列表的情况下尝试一些不同的私人会议用语。如果你尝试像“私下会面”、“秘密会面”或“创建一个隐藏的会议”这样的话语,路易斯不会识别这个实体。然而,如果我们用短语列表训练应用,LUIS 在这些样本和许多其他样本中识别实体没有问题。 5
训练特点
在本练习中,您将通过添加功能来改进我们的 LUIS 应用。
-
将可见性分层实体添加到 LUIS 应用中。
-
添加您自己的短语列表以提高私有可见性实体的性能。
-
将 LUIS 应用发布到暂存槽中。
-
使用 curl 检查生成的 JSON。
-
将短语列表设置为不可互换对其性能有何影响?
短语列表是帮助我们的应用更好地识别不同实体的强大功能。
添加被邀请人实体
您可能已经注意到,我们还没有谈到我们如何捕获与会者,到目前为止,我们已经忽略了这个问题。在本练习中,我们将解决这个问题。
-
添加名为“被邀请者”的新自定义实体。
-
检查到目前为止的每个样本话语,并识别话语中的被邀请者实体。
-
如果它需要额外的训练,增加更多的样本。确保包含样本,其中被邀请者是话语中的唯一实体或者是许多实体中的一个。
-
对于奖励积分,将被邀请者实体添加到 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 门户获得一个订阅密钥,我们将在第 5 章中介绍。我们还将在第 10 章中更直接地使用拼写检查 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 来实现吗?”答案是肯定的!创作 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 使用。
Footnotes [1](#Fn1_source)路易斯界限: https://docs.microsoft.com/en-us/azure/cognitive-services/luis/luis-boundaries
LUIS 端点 API 文档: https://westus.dev.cognitive.microsoft.com/docs/services/5819c76f40a6350ce09de1ac/operations/5819c77140a63516d81aee78
预制实体参考: https://docs.microsoft.com/en-us/azure/cognitive-services/luis/luis-reference-prebuilt-entities
路易斯预建域名: https://docs.microsoft.com/en-us/azure/cognitive-services/luis/luis-reference-prebuilt-domains
微软。识别器.正文: https://github.com/Microsoft/Recognizers-Text
路易斯创作 API 参考文档: https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff











































