Cortana Skill - 整合 LUIS

語音控制是現在最流行的用法,裏面怎麽分析人說的話來完成對的任務是最困難的地方。

本篇介紹如何整合 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

介紹剛才用到的關鍵元素:

[補充]

想要同時使用多個 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: