語音控制是現在最流行的用法,裏面怎麽分析人說的話來完成對的任務是最困難的地方。
本篇介紹如何整合 LUIS (Language Understanding Intelligent Service) 去瞭解用戶說的話。
Amazon echo, Google Home, 天貓精靈 或是即將上市的 小米 AI 音箱 均是利用語音輸入完成用戶的任務。
口説的内容利用 自然語言處理 (Natural Language Processing, NLP) 技術訓練 AI ,讓它懂得用戶説出來内容,例如:一段句子裏面有多少關鍵字,句子主要的意圖 等,再讓開發人員針對不同的意圖與關鍵字執行撰寫後面的處理邏輯。
在整個 Cortana Skill 架構裡,首先透過 Cortana 幫忙把 Speech 轉成文字,再交給設定在 Bot Framework portal 的 Messaging endpoint。
Messaging endpoint 可以整合 LUIS 或是其他第三方的 NLP 系統分析文字瞭解句子的意圖。
我在 簡單建立一個 Cortana Skill 也介紹過,如下圖: 如果 Messaging endpoint 有使用到 LUIS apps,可以在 Bot Framework portal 加入用到的 LUIS apps 讓 Bot 實現最佳的語音識別。
如何建立一個 LUIS app 本篇不多做説明,可以參考 [Azure] 建立Microsoft LUIS的App服務,進行語意識別的訓練並整合Bot Framework 或是官方文件 Learn about Language Understanding Intelligent Service (LUIS)。
Bot Framework SDK 提供 Microsoft.Bot.Builder.Luis,幫助開發人員將原本的 Message endpoint 加上支援 LUIS app。
加入的步驟:
1. 建立一個 class 並且繼承 LuisDialog,修改 Message Controller 指定的預設 Dialog (當然也可改用其他 Message Controller 處理)
[BotAuthentication]
public class MessagesController : ApiController
{
public async Task Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
// 改用自定義的新類別
await Conversation.SendAsync(activity, () => new Dialogs.MyLuisDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
}
2. 在繼承 LuisDialog 的 class,加入 LuisModelAttribute 的設定 (將使用的 LUIS app id 與 SubscriptionKey 加入)
3. 在 class 中加入要處理該 Luis app 定義的 intent 的 methods (利用 LuisIntentAttribute),如下的程式片段:
[LuisModel("{ModelID}", "{SubscriptionKey}")]
[Serializable]
public class MyLuisDialog : LuisDialog<object>
{
[LuisIntent("None")]
public async Task None(IDialogContext context, LuisResult result)
{
// 回應對方說的内容無法被 LUIS 識別出來
string message = $"Sorry, I did not understand '{result.Query}'. Type 'help' if you need assistance.";
await context.PostAsync(message);
context.Wait(this.MessageReceived);
}
[LuisIntent("Question")]
public async Task Question(IDialogContext context, LuisResult result)
{
if (result.TopScoringIntent == null)
{
await None(context, result);
return;
}
// 利用 Entity Name 得到内容
EntityRecommendation accountEntity = null;
if (result.TryFindEntity("account", out accountEntity))
{
// 舉例 account 為 entity name, 如果可以拿到去必要的邏輯
}
context.Wait(this.MessageReceived);
}
}
這樣就完成了最簡單整合 LUIS,詳細的官方範例可以參考 Microsoft/BotBuilder-Samples。
介紹剛才用到的關鍵元素:
- LuisDialog
LuisDialog.cs 整合 LUIS 最重要的元件,搭配 LuisModelAttribute 設定預設的 LUIS app,或是加入多個 ILuisSErvice 可以同時處理多個 LuisResult。
- LuisModelAttribute
定義 LUIS model 資訊,LuisDialog.cs 在建構子利用 MakeServicesFromAttributes()來抓出該 dialog 有標記多少個 LuisModel 。
- LuisIntentAttribute
可以用來標記 dialog method 屬於那個 LUIS intent。被標記 Luis 會在 LuisDialog.cs 中被先截取出來,根據 LUIS app 回復的結構找到適合的 intent 來轉給標記的 method。
- LuisRequest
包含所有要發送到 LUIS app 的相關屬性,可在 LuisService.cs 看到它如何被建立。
- LuisService
實作 ILuisSErvice interface 負責與 Luis.ai 設定的 LUIS app 互動。透過 LuisService.cs 可以發現裏面利用設定的 ModelID 與 SubscriptionKey 組合成 Uri 搭配 HttpClient 來得到 LuisResult。
- LuisResult
屬於 Microsoft.Bot.Builder.Luis.Models 下的類別,代表執行的結果,裏面有 EntityRecommendation,IntentRecommendation 等可以幫助判斷這次語言的識別是否符合預期找到特定的 Intent 與 Entity。
[補充]
想要同時使用多個 LUIS model 的話,要怎麽做?
根據官方的説明 How can I use more than one LUIS model? 是可以支援的。
根據 LuisDialog.cs 可以看到:
public ILuisService[] MakeServicesFromAttributes()
{
// 從標記 LuisModel 抓取看有多少 LuisService
var type = this.GetType();
var luisModels = type.GetCustomAttributes(inherit: true);
return luisModels.Select(m => new LuisService(m)).Cast().ToArray();
}
public LuisDialog(params ILuisService[] services)
{
// 如果建構子沒有指定給 ILuisService 就會利用 MakeServicesFromAttributes 來得到 LuisService
if (services.Length == 0)
{
services = MakeServicesFromAttributes();
}
SetField.NotNull(out this.services, nameof(services), services);
}
因此,您可以選擇使用其中一種方式來加入多個 Luis model, 如下:
[BotAuthentication] public class MessagesController : ApiController { public async Task Post([FromBody]Activity activity) { if (activity.Type == ActivityTypes.Message) { await Conversation.SendAsync(activity, () => new Dialogs.MyLuisDialog( new LuisService(new LuisModelAttribute("{ModelID1}", "{SubscriptionKey1}")), new LuisService(new LuisModelAttribute("{ModelID2}", "{SubscriptionKey2}")) )); } } } // 或是 [LuisModel("{ModelID1}", "{SubscriptionKey1}")] [LuisModel("{ModelID2}", "{SubscriptionKey2}")] [Serializable] public class MyLuisDialog : LuisDialog<object> { }
一個 dialog 整合了多個 Luis model,你會遇到一個問題: 怎麽都只會進去某個特定 Luis model 的 intents。
問題就在於 BestResultFrom() 如下程式碼:
protected virtual LuisServiceResult BestResultFrom(IEnumerable results)
{
// 直接從 results 中抓出 BestIntent 分數最高的
return results.MaxBy(i => i.BestIntent.Score ?? 0d);
}
這樣的寫法在一些 Luis model 的判斷永遠都會是 intent 為 None 或是空白 這些預設的為最高分。
因此,我們需要調整一下,如下:
protected override LuisServiceResult BestResultFrom(IEnumerable results)
{
// 如果只有一個 result 就直接用預設的
if (results.Count() < 1)
{
return base.BestResultFrom(results);
}
else
{
// 多個 results ,檢查裏面的 intents 都是什麽類型
var emptyResult = results.Where(x => x.Result.TopScoringIntent.Intent == "None" || string.IsNullOrEmpty(x.Result.TopScoringIntent.Intent)).ToList();
if (emptyResult.Count == results.Count())
{
// 代表多個 results 都無法辨識這次的 query text
return base.BestResultFrom(results);
}
else
{
// 如果不是,則去掉 None 或是 空白的 intent
var bestResult = results.Where(x => x.Result.TopScoringIntent.Intent != "None" || !string.IsNullOrEmpty(x.Result.TopScoringIntent.Intent)).FirstOrDefault();
return bestResult;
}
}
}
這樣就可以正確找到預期的 intent 了。
另外,要 注意 同一個 message endpoint 支援多個 LUIS model 需要考量幾個用途:
- Luis model 之間是否有關聯性? 因爲多個 Luis model 同時使用就需要從裏面找出最適合的 intent
- 整合太多的 Luis Model 會造成交易變慢,建議先定義出基本的識別結構,再轉給特定結構的 dialog (在 dialog 整合特定的 Luis model) 處理
======
Cortana Skill 目前只支援 English,所以上面的介紹都是用英文爲主。 像我們講中文的怎麽辦?
LUIS 有支援中文或是選擇其他語言,只要先做好 Speech to Text,拿到 Text 之後都有辦法識別。
因爲有很多内容與 Bot Framework 相關,如果閲讀上有問題建議看一下 Bot Framework 的介紹。
希望對大家有幫助,謝謝。
References:
- Dialogs in the Bot Builder SDK for .NET
- Troubleshooting general problems
- Bot Framework
- Create messages
- Microsoft/BotBuilder-Samples
- Microsoft/AzureBot
- Custom BotFramework Intent Service Implementation
- LuisService.cs
- BotBuilder/CSharp/Library/Microsoft.Bot.Builder/Luis/LuisService.cs
- Multiple LUIS models from config