智能定时器与语言理解:基于 LUIS 构建意图与实体解析的完整实战(IoT-For-Beginners)
【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners
本指南基于 IoT-For-Beginners 开源课程"消费者(Consumer)"路径第 2 课(对应整体课程第 22 课),完整讲解如何为上一课已实现的语音转文字智能定时器注入"理解能力":借助微软 LUIS 语言理解服务,让设备不仅能听到"set a 3 minute timer",还能听懂"set a timer for 3 minutes"这类千变万化的自然表达。读完本文,你将掌握语言理解模型的核心概念(意图与实体)、在 LUIS 门户中创建与训练模型的全流程、通过 HTTP 触发的 Azure Functions 服务端代码调用模型,以及如何把解析结果(秒数)开放给 IoT 设备调用的完整链路。
图片来源:本课官方 sketchnote(作者 Nitya Narasimhan),点击可查看大图。
为什么要"理解"语言,而不是"匹配"句子
在上一课 1-speech-recognition 中,我们完成了从语音到文本的转换。但"听懂文字"是另一回事:如果代码只期待固定句式set a 3 minute timer,那么用户说set a timer for 3 minutes时,程序就会茫然无措——尽管人类一眼就能看出两者表达的是同一个意思。
语言理解(language understanding,也称自然语言理解,natural-language understanding)正是自然语言处理(NLP)这一人工智能领域中的"阅读理解"分支:它负责从文字中抽取关键细节,而不是逐字匹配模板。当你对 Alexa 或 Siri 说"播放 Taylor Swift 的最新专辑"时,背后就是语言理解服务在起作用,它需要从这句话中推导出:
- 动作是"播放音乐";
- 歌手是 Taylor Swift;
- 播放对象是包含多首曲目、按顺序播放的整张专辑;
- Taylor Swift 有多张专辑,需要按时间排序取最新发布的那张。
💁 需要强调的是:计算机的"理解"远未达到人类沟通的水平。这里说的语言理解,本质是"取一些词,抽取关键信息",而不是真正意义上的语义领悟。
语言理解模型属于 AI 模型,通常先用海量语言数据训练,再通过**迁移学习(transfer learning)**针对具体任务微调——这与你在本课程制造篇中用少量图片训练 Custom Vision 模型的思路如出一辙:拿一个预训练模型,再用你希望它理解的文本去训练它。
创建 LUIS 语言理解模型
LUIS(Language Understanding)是微软 Cognitive Services 中的语言理解服务,本课用它来构建定时器领域的语言模型。
第一步:创建 Authoring 资源
使用 Azure CLI 在smart-timer资源组中创建编写(authoring)资源:
az cognitiveservices account create --name smart-timer-luis-authoring \ --resource-group smart-timer \ --kind LUIS.Authoring \ --sku F0 \ --yes \ --location <location><location>替换为你创建资源组时使用的区域;--sku F0表示免费层,足够开发使用。
⚠️ LUIS 并非在所有区域都可用。如果出现以下错误,请换一个区域重试:
InvalidApiSetId: The account type 'LUIS.Authoring' is either invalid or unavailable in given region.
第二步:在 LUIS 门户创建应用
- 打开 luis.ai,用与 Azure 相同的账号登录;
- 按对话框指引选择订阅,然后选中刚创建的
smart-timer-luis-authoring资源; - 在Conversation apps列表中点击New app,命名应用为
smart-timer,Culture(文化/语言区域)按你的语言设置; - 门户会显示预测资源(prediction resource)字段。虽然可以单独建一个预测资源,但免费编写资源每月已含 1,000 次预测,足以支撑开发调试,此处留空即可;
- 阅读创建应用后弹出的新手引导,了解训练语言理解模型的步骤后关闭它。
意图(Intents)与实体(Entities):语言理解的基石
语言理解围绕两个核心概念:
- 意图(Intent):这句话要做什么,例如播放音乐、设置定时器、取消定时器、下单点餐;
- 实体(Entity):意图作用的对象,例如专辑、定时时长、食物种类。
模型解释的每个句子至少包含一个意图,可选地包含一个或多个实体。下表是本课给出的典型示例:
| 句子 | 意图 | 实体 |
|---|---|---|
| "Play the latest album by Taylor Swift" | play music | the latest album by Taylor Swift |
| "Set a 3 minute timer" | set a timer | 3 minutes |
| "Cancel my timer" | cancel a timer | 无 |
| "Order 3 large pineapple pizzas and a caesar salad" | order food | 3 large pineapple pizzas,caesar salad |
训练 LUIS 时先定义实体,再定义意图。实体可以是固定的词条列表,也可以从文本中学习。例如菜单场景可以给出食物清单及每个词的各种变体(同义词),如把egg plant和aubergine都归为aubergine的变体。LUIS 还内置了一批可直接使用的预置实体(pre-built entities),如数字 number、位置 location 等。
对于定时器场景,可以用预置的number实体表示时长数值,再自建一个表示单位(分钟/秒)的实体;每个单位需要覆盖单复数变体,如 minute 与 minutes。
意图由模型根据你提供的**示例语句(utterances)**学习。比如set timer意图可以给出:
set a 1 second timerset a timer for 1 minute and 12 secondsset a timer for 3 minutesset a 9 minute 30 second timer
然后告诉 LUIS 这些句子中哪些片段对应哪些实体。例如set a timer for 1 minute and 12 seconds的意图是set timer,它包含 2 个实体、每个实体各有 2 个值:
| 片段 | 数值 time | 单位 unit |
|---|---|---|
| 1 minute | 1 | minute |
| 12 seconds | 12 | second |
💁 与任何 AI 模型一样,训练数据越多、越准确,模型效果越好。因此需要提供覆盖"同一件事的不同问法"的多样化示例句子。
实操:为模型添加实体
定时器需要 2 个实体:一个是时间单位(分钟或秒),一个是分钟/秒的数值。
- 在 LUIS 门户的Entities标签页,点击Add prebuilt entity,从列表中选择number预置实体;
- 点击Create新建时间单位实体:命名为
time unit,类型选择List(列表);在Normalized values中添加minute和second,并在synonyms中补充单复数形式,每输入一个同义词按return确认:
| Normalized value | Synonyms |
|---|---|
| minute | minute, minutes |
| second | second, seconds |
实操:为模型添加意图
在Intents标签页点击Create,创建名为
set timer的意图;输入多种设置定时器的写法,混合使用分钟、秒以及分钟+秒的组合,示例可以是:
set a 1 second timerset a 4 minute timerset a four minute six second timerset a 9 minute 30 second timerset a timer for 1 minute and 12 secondsset a timer for 3 minutesset a timer for 3 minutes and 1 secondset a timer for three minutes and one secondset a timer for 1 minute and 1 secondset a timer for 30 secondsset a timer for 1 second
注意将数字的单词写法与数字写法混合(如
four与4),让模型同时学会处理两种形式;每输入一个示例,LUIS 就会开始识别实体,并把识别到的实体加下划线标注。
实操:训练与测试模型
- 实体和意图配置完成后,点击顶部菜单的Train按钮,模型会在几秒内完成训练(训练期间按钮置灰,训练完成后恢复);
- 点击Test按钮测试:输入如
set a timer for 5 minutes and 4 seconds并回车,文本下方会显示检测到的top intent(概率最高的意图),应为set timer,后面跟着该意图的概率; - 点击Inspect查看结果明细:可以看到得分最高的意图及其百分比概率,以及识别出的实体列表;
- 测试完成后关闭Test面板。
实操:发布模型
要从代码中调用模型,必须先发布。LUIS 支持发布到Staging(暂存)环境用于测试,或Production(生产)环境用于正式发布;本课使用 Staging 即可。
点击 LUIS 门户顶部Publish按钮;
确认选择Staging slot,点击Done,应用发布后会收到通知;
用 curl 验证发布结果。构建 curl 命令需要三个值——endpoint(终结点)、App ID(应用 ID)和 API key(密钥),三者都可在顶部MANAGE标签页获取:
- 从Settings部分复制 App ID;
- 从Azure Resources部分选择Authoring Resource,复制Primary Key和Endpoint URL;
在命令行执行:
curl "<endpoint url>/luis/prediction/v3.0/apps/<app id>/slots/staging/predict" \ --request GET \ --get \ --data "subscription-key=<primary key>" \ --data "verbose=false" \ --data "show-all-intents=true" \ --data-urlencode "query=<sentence>"<endpoint url>替换为Azure Resources部分的 Endpoint URL;<app id>替换为Settings部分的 App ID;<primary key>替换为Azure Resources部分的 Primary Key;<sentence>替换为要测试的句子;
返回的 JSON 文档包含查询语句、top intent 以及按类型分类的实体列表。以查询
set a timer for 45 minutes and 12 seconds为例:{ "query": "set a timer for 45 minutes and 12 seconds", "prediction": { "topIntent": "set timer", "intents": { "set timer": { "score": 0.97031575 }, "None": { "score": 0.02205793 } }, "entities": { "number": [ 45, 12 ], "time-unit": [ [ "minute" ], [ "second" ] ] } } }这份响应的解读:
set timer是 top intent,概率高达 97%;- 检测到两个number实体:
45和12; - 检测到两个time-unit实体:
minute和second。
在代码中使用语言理解模型
为什么选择 HTTP 触发器 + 服务端无服务器代码
之前几课中,我们通过 IoT Hub 与云服务通信:发送遥测、监听命令。这种方式非常异步——遥测发出后代码不等待响应,如果云服务宕机也无法感知。而智能定时器需要即时反馈(告诉用户定时器已设置,或提示云服务不可用),因此设备应直接调用 Web 终结点,而不是依赖 IoT Hub 的异步通道。
直接在 IoT 设备上调用 LUIS 可行,但更好的做法是封装一层带 HTTP 触发器的无服务器函数(Azure Functions):函数应用监听 REST 请求并响应,设备只需调用这个 REST 终结点。这样当你想更换 LUIS 应用(例如训练出更好的模型,或增加多语言模型)时,只需更新云端代码,而无需向成千上万台 IoT 设备重新部署代码。
创建无服务器函数应用
新建名为
smart-timer-trigger的 Azure Functions 应用并在 VS Code 中打开;在 VS Code 终端执行以下命令添加 HTTP 触发器:
func new --name text-to-timer --template "HTTP trigger"这会创建名为
text-to-timer的 HTTP 触发器;运行函数应用,输出中会列出终结点:
Functions: text-to-timer: [GET,POST] http://localhost:7071/api/text-to-timer在浏览器加载
http://localhost:7071/api/text-to-timer验证:This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.
接入 LUIS Python SDK
LUIS 的 SDK 以 Pip 包形式提供,在
requirements.txt中加入依赖:azure-cognitiveservices-language-luis仓库中该应用的完整依赖见 code/functions/smart-timer-trigger/requirements.txt,其中还包含
azure-functions(注意不要引入azure-functions-worker,以免与 Functions 平台冲突):# Do not include azure-functions-worker as it may conflict with the Azure Functions platform azure-functions azure-cognitiveservices-language-luis确保 VS Code 终端已激活虚拟环境,执行安装:
pip install -r requirements.txt💁 若安装报错,可先升级 pip:
pip install --upgrade pip。在
local.settings.json中为 LUIS API Key、Endpoint URL 和 App ID 添加配置项(值从 LUIS 门户MANAGE标签页获取):"LUIS_KEY": "<primary key>", "LUIS_ENDPOINT_URL": "<endpoint url>", "LUIS_APP_ID": "<app id>"完整文件结构参考 code/functions/smart-timer-trigger/local.settings.json:
{ "IsEncrypted": false, "Values": { "FUNCTIONS_WORKER_RUNTIME": "python", "AzureWebJobsStorage": "", "LUIS_KEY": "<primary key>", "LUIS_ENDPOINT_URL": "<endpoint url>", "LUIS_APP_ID": "<app id>" } }<endpoint url>:MANAGE标签页Azure Resources部分获取的 Endpoint URL,形如https://<location>.api.cognitive.microsoft.com/;<app id>:MANAGE标签页Settings部分获取的 App ID;<primary key>:MANAGE标签页Azure Resources部分获取的 Primary Key。
在
__init__.py中加入导入语句:import json import os from azure.cognitiveservices.language.luis.runtime import LUISRuntimeClient from msrest.authentication import CognitiveServicesCredentials仓库中的完整实现见 code/functions/smart-timer-trigger/text-to-timer/init.py,下文将逐段讲解其逻辑。
编写预测与解析逻辑
清空
main方法原有内容,加入客户端初始化代码。它从环境变量(即local.settings.json中的值)加载配置,用 API key 创建 credentials 对象,再创建与 LUIS 应用交互的客户端:luis_key = os.environ['LUIS_KEY'] endpoint_url = os.environ['LUIS_ENDPOINT_URL'] app_id = os.environ['LUIS_APP_ID'] credentials = CognitiveServicesCredentials(luis_key) client = LUISRuntimeClient(endpoint=endpoint_url, credentials=credentials)本 HTTP 触发器接收的请求体为 JSON,待理解的文本放在
text属性中。从请求体取值并打印日志:req_body = req.get_json() text = req_body['text'] logging.info(f'Request - {text}')构造预测请求(一个包含待预测文本的 JSON 文档),并发送到应用发布所在的 Staging 槽:
prediction_request = { 'query' : text } prediction_response = client.prediction.get_slot_prediction(app_id, 'Staging', prediction_request)预测响应包含 top intent 和实体。如果 top intent 是
set timer,则从实体中读取定时所需时长:if prediction_response.prediction.top_intent == 'set timer': numbers = prediction_response.prediction.entities['number'] time_units = prediction_response.prediction.entities['time unit'] total_seconds = 0number实体是数字数组。例如"Set a four minute 17 second timer."会得到[4, 17];time unit实体是字符串数组的数组,每个时间单位是一个内层数组。同上示例会得到[['minute'], ['second']]。
这两类实体组合后的 JSON 形态:
{ "number": [4, 17], "time unit": [ ["minute"], ["second"] ] }实体之间没有显式关联,但可以合理假设:它们按说话顺序排列,因此用数组下标即可确定哪个数字对应哪个时间单位。例如:
- "Set a 30 second timer":1 个数字
30、1 个单位second,一一对应; - "Set a 2 minute and 30 second timer":2 个数字
2、30,2 个单位minute、second,第一个数字对第一个单位(2 分钟),第二个数字对第二个单位(30 秒)。
用如下循环按序提取值(放在
if块内):for i in range(0, len(numbers)): number = numbers[i] time_unit = time_units[i][0]对"Set a four minute 17 second timer."会循环两次:
循环次数 numbertime_unit0 4 minute 1 17 second - "Set a 30 second timer":1 个数字
在循环内按单位换算总秒数:每分钟加 60 秒,秒则直接累加:
if time_unit == 'minute': total_seconds += number * 60 else: total_seconds += number循环结束后打印定时器总时长:
logging.info(f'Timer required for {total_seconds} seconds')在
if块末尾,把秒数包装成 payload,转为 JSON 字符串并以 200 状态码返回(200 表示调用成功):payload = { 'seconds': total_seconds } return func.HttpResponse(json.dumps(payload), status_code=200)在
if块之外处理意图未被识别的情况——返回 404(not found)状态码:return func.HttpResponse(status_code=404)
触发器配置说明
仓库中 code/functions/smart-timer-trigger/text-to-timer/function.json 定义了触发器绑定:
{ "scriptFile": "__init__.py", "bindings": [ { "authLevel": "function", "type": "httpTrigger", "direction": "in", "name": "req", "methods": [ "get", "post" ] }, { "type": "http", "direction": "out", "name": "$return" } ] }其中authLevel为function(函数级密钥鉴权),methods支持 GET 与 POST。宿主配置见 host.json,其extensionBundle声明使用 Azure Functions 扩展包版本[2.*, 3.0.0)。
用 curl 验证整个链路
运行函数应用,然后用 curl 发起 POST 请求:
curl --request POST 'http://localhost:7071/api/text-to-timer' \ --header 'Content-Type: application/json' \ --include \ --data '{"text":"<text>"}'将<text>替换为请求文本,例如set a 2 minutes 27 second timer。函数应用侧会输出类似日志:
Functions: text-to-timer: [GET,POST] http://localhost:7071/api/text-to-timer For detailed output, run func with --verbose flag. [2021-06-26T19:45:14.502Z] Worker process started and initialized. [2021-06-26T19:45:19.338Z] Host lock lease acquired by instance ID '000000000000000000000000951CAE4E'. [2021-06-26T19:45:52.059Z] Executing 'Functions.text-to-timer' (Reason='This function was programmatically called via the host APIs.', Id=f68bfb90-30e4-47a5-99da-126b66218e81) [2021-06-26T19:45:53.577Z] Timer required for 147 seconds [2021-06-26T19:45:53.746Z] Executed 'Functions.text-to-timer' (Succeeded, Id=f68bfb90-30e4-47a5-99da-126b66218e81, Duration=1750ms)curl 返回的响应中,"seconds"字段即定时器秒数:
HTTP/1.1 200 OK Date: Tue, 29 Jun 2021 01:14:11 GMT Content-Type: text/plain; charset=utf-8 Server: Kestrel Transfer-Encoding: chunked {"seconds": 147}把 REST 终结点开放给 IoT 设备
IoT 设备要调用 REST 终结点,必须知道 URL。此前访问用的localhost只是本机快捷方式,要开放给设备,有两种方式:发布到云端,或获取本机 IP 供局域网访问。
⚠️ 若使用 Wio Terminal,建议在本机运行函数应用:部分库依赖会导致无法按此前方式部署函数应用。此时请本地运行函数应用并通过电脑 IP 访问;若确实要部署到云端,后续课程会给出方法。
方式一:发布到云端
按前序课程的指引把函数应用发布到云端。发布后 URL 为https://<APP_NAME>.azurewebsites.net/api/text-to-timer(<APP_NAME>为函数应用名),同时记得发布本地设置。
HTTP 触发器默认受函数应用密钥(function app key)保护。获取该密钥:
az functionapp keys list --resource-group smart-timer \ --name <APP_NAME>复制functionKeys部分default条目的值:
{ "functionKeys": { "default": "sQO1LQaeK9N1qYD6SXeb/TctCmwQEkToLJU6Dw8TthNeUH8VA45hlA==" }, "masterKey": "RSKOAIlyvvQEQt9dfpabJT018scaLpQu9p1poHIMCxx5LYrIQZyQ/g==", "systemKeys": {} }该密钥需作为查询参数拼入 URL,最终为https://<APP_NAME>.azurewebsites.net/api/text-to-timer?code=<FUNCTION_KEY>。
💁 可通过 function.json 中的
authLevel设置调整 HTTP 触发器的鉴权级别。
方式二:本机运行 + 局域网 IP 访问
获取电脑在本地网络的 IP 地址:
- Windows 10:查看"查找 IP 地址"相关指南;
- macOS:查看"如何在 Mac 上查找 IP 地址";
- Linux:查看私网 IP 地址的获取方法。
得到 IP 后,即可通过http://<IP_ADDRESS>:7071/api/text-to-timer访问,例如http://192.168.1.10:7071/api/text-to-timer。
💁 注意使用端口 7071,IP 地址后必须加
:7071。💁 该方式仅当 IoT 设备与电脑处于同一网络时才可用。
最后用 curl 验证终结点可访问。
动手挑战:扩充训练语料
"设置定时器"有无数种等价说法(例如不同语序、不同数字写法、夹杂语气词等)。请思考更多表达方式,把它们作为示例加入你的 LUIS 应用并重新训练,测试模型面对多种请求方式时的泛化能力。
课程作业:实现"取消定时器"
阅读 6-consumer/lessons/2-language-understanding/assignment.md:为 LUIS 应用新增一个cancel timer意图(无需实体,但需要若干示例语句);在无服务器代码中检测该意图是否为 top intent,若命中则记录日志并返回合适响应。参考评分标准:能添加意图并训练模型、能检测 top intent 并记录日志,即为满分表现。
回顾与延伸
- 本课完整示例代码位于 code/functions/smart-timer-trigger,其中
__init__.py是解析核心、function.json定义触发器、local.settings.json存放 LUIS 连接配置; - 想深入理解语言理解(自然语言理解)的理论背景,可查阅自然语言理解相关公开资料;
- 想进一步了解 HTTP 触发器的更多配置项,可查阅 Azure Functions HTTP 触发器文档中关于
authLevel、绑定方向的配置说明; - 下一课 3-spoken-feedback 将把本课解析出的定时器秒数转换为语音反馈,形成完整的"听懂—执行—回话"闭环。
【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考