[Azure] 建立Microsoft LUIS的App服務,進行語意識別的訓練並整合Bot Framework

Microsoft LUIS (Language Understanding Intelligent Service) 是微軟不久前推出的一項語意識別的服務
透過一段時間的訓練,就可以有效的進行口語化內容的識別,搭配前端程式的撰寫,就可以作出依據發問者的需求作出答覆的應用

本篇文章會說明如何建立LUIS的服務,並進行訓練,最後會加上整合Bot Framework的前端機器人服務作出自動答覆的應用

建立LUIS本身服務的方式不難,但是困難的地方在於如何進行LUIS的訓練,讓伺服器能夠瞭解提出的語意內容

要申請建立LUIS服務的方式,依照下面步驟的執行就可以順利完成了

先連到LUIS的網址https://www.luis.ai/,並點選中間的[Sign in or create an account],若是有Microsoft Account的話,就可以直接進行登入的動作

登入成功之後,在上方的[My Application]頁面中,請點選[New App] => [New Application]

建立一個新的LUIS應用程式,需要填入應用程式的名稱以及語系,在這裡先選擇中文
建立完成後會進入到這個App的設定以及訓練畫面,在這裡看到的第一眼應該會覺得不知道該從何下手

下手的第一步,先從[Entities]開始,[Entities]代表了在一句話中的每一個實體項目,如動詞、名詞、疑問等等的,我先舉一個最簡單的例子,先在[Entities]裡,建立一個[問候]的項目

接著建立一個[人名][疑問][Entities],目前得到的結果就是像下圖這樣

定義好語句的實體項目後,接著就要到[Intents]這裡去建立語句的意圖,在[Intents]裡建立一個[只是打招呼]的項目,這個[Intents]的項目建立時,會要求先輸入一個範例的語句,以及在這個意圖的語句中,需要包含哪些實體項目就以打招呼的語句來看,需要的[Entities]就是[問候]了,所以我們將[問候]這個[Entities]加到參數中並按下儲存

按下儲存後,會出現範例語句是屬於哪一種意圖的設定,由於一句話中可能會包含多個[Entities],所以在設定一句話的實體時,可以用滑鼠反白後,點選這個語句所屬的實體是哪一個項目,設定完按下[Submit]就可以了

下面是另一個語句的訓練,點選[New utterances]後,輸入要訓練的語句[hi,你好]之後,可以將某段字串反白,並設定該字串的實體項目

按下[Submit]之後,點選左下方的[Train],這樣LUIS才會真正的去進行語意訓練的動作

作完訓練後,可以點選左上方的[Publish],將訓練的結果發佈成一個WebAPI,讓其他的應用程式進行呼叫

發佈完,可以直接在Query的地方輸入要語意識別的內容

LUIS會從WebAPI上回傳識別的結果,並以JSON回傳從回傳的結果可以看到,LUIS判斷有該語句有六成是問候語,如此一來,前端接收LUIS判斷的結果後,就可以作出相對應的回答了

接下來這個例子用到的範圍就比較廣一點,先在[Entities]裡建立[日期][航空公司][服務]三個項目

然後在[Intents]裡加上[詢問]的語意定義,並加上[疑問][航空公司][服務][日期]四個[Entites],然後給予一個例句

然後在這個例句中,分別定義包含的[Entities],然後設定這句話的Intents為[詢問]
[請問]:疑問
[今天]:日期
[華航]:航空公司
[航班]:服務

當然我們可以作多一點的例句進行訓練動作

訓練完並發佈後,我們在Publish的畫面上輸入一下比較不一樣的語句,看LUIS是否能正確的判斷出結果

得到的結果,LUIS判斷出這是一個問句,並且也把相關的[Entities]抓出來回傳JSON資料了

LUIS的訓練說明先到這邊,接下來要整合Bot Framework的機器人,讓機器人可以將使用者輸入的文字傳入至LUIS後,取得識別的結果再進行判斷

先在LUIS的App畫面上點選左上方的[App Settings],並把[App Id][Subscription Key]記下來,等一下會用到

打開Visual Studio,開啟Bot Framework機器人的專案,並在Models的資料夾中建立一個CognitiveModels.cs的類別庫,加上下面的程式碼

public class LUISResult
{
    public string query { get; set; }
    public List<Intent> intents { get; set; }
    public List<Entity> entities { get; set; }

    public class Intent
    {
        public string intent { get; set; }
        public float score { get; set; }
        public List<Action> actions { get; set; }
    }

    public class Action
    {
        public bool triggered { get; set; }
        public string name { get; set; }
        public List<Parameter> parameters { get; set; }
    }

    public class Parameter
    {
        public string name { get; set; }
        public bool required { get; set; }
        public List<Value> value { get; set; }
    }

    public class Value
    {
        public string entity { get; set; }
        public string type { get; set; }
        public float score { get; set; }
    }

    public class Entity
    {
        public string entity { get; set; }
        public string type { get; set; }
        public int startIndex { get; set; }
        public int endIndex { get; set; }
        public float score { get; set; }
    }
}

這個Model主要是用來接收從LUIS上回傳的識別結果,並從JSON轉換為物件類別用

在Web.Config檔案中加上前面步驟中記下來的兩個設定,分別是LUIS上的[App Id][Subscription Key]

<add key="LUISAPIKey" value="[LUIS上的Subscription Key]"/>
<add key="LUISAppId" value="[LUIS上的App Id]"/>

接著,在Controllers\MessagesControlles.cs這個主要用來接收與回傳Bot機器人訊息的控制器中,將原本Post的程式碼,置換成下面的程式碼

public async Task<Message> Post([FromBody]Activity activity)
{
    // Activity 是Bot Framework 3.0的寫法,若是改版後這種寫法無法使用
    // 請在文章下方留言告訴我,以進行內容的更新與修改

    if (activity.Type == ActivityTypes.Message)
    {
        string strLuisKey = ConfigurationManager.AppSettings["LUISAPIKey"].ToString();
        string strLuisAppId = ConfigurationManager.AppSettings["LUISAppId"].ToString();
        string strMessage = HttpUtility.UrlEncode(activity.Text);
        string strLuisUrl = $"https://api.projectoxford.ai/luis/v1/application?id={strLuisAppId}&subscription-key={strLuisKey}&q={strMessage}";

        // 收到文字訊息後,往LUIS送
        WebRequest request = WebRequest.Create(strLuisUrl);
        HttpWebResponse response = (HttpWebResponse)request.GetResponse();
        Stream dataStream = response.GetResponseStream();
        StreamReader reader = new StreamReader(dataStream);
        string json = reader.ReadToEnd();
        CognitiveModels.LUISResult objLUISRes = JsonConvert.DeserializeObject<CognitiveModels.LUISResult>(json);

        string strReply = "無法識別的內容";

        if (objLUISRes.intents.Count > 0)
        {
            string strIntent = objLUISRes.intents[0].intent;
            if (strIntent == "詢問")
            {
                string strDate = objLUISRes.entities.Find((x => x.type == "日期")).entity;
                string strAir = objLUISRes.entities.Find((x => x.type == "航空公司")).entity;
                string strService = objLUISRes.entities.Find((x => x.type == "服務")).entity;

                strReply = $"您要詢問的航空公司:{strAir},日期:{strDate},相關服務是:{strService}。我馬上幫您找出資訊";
                strReply += ".....這裡加上後續資料的呈現.....";
            }
                    
            if (strIntent == "只是打招呼")
            {
                strReply = "您好,有什麼能幫得上忙的呢?";
            }
            
            if (strIntent == "None")
            {
                strReply = "您在說什麼,我聽不懂~~~(轉圈圈";
            }
        }

        Activity reply = activity.CreateReply(strReply);
        await connector.Conversations.ReplyToActivityAsync(reply);
    }
    else
    {
        return HandleSystemMessage(activity);
    }
}

這段程式碼主要是從LUIS上取得語意辨識的結果後,判斷[Intent]的與用來決定語句語義的[Entities]項目,然後回傳結果內容至前端的機器人上,最後得到的畫面如下回傳至前端Bot Framework的訊息,確定是從LUIS上判斷得到的結果,前端的機器人也可以順利的取得回答的文字內容了

透過LUIS與Bot Framework的整合,可以很快的建構出一個簡單的線上即時問答與服務的對話機器人,對於企業在提供線上服務、與客服對話以及整體的人機運用架構上會有相關大的改變與突破。

建立這樣的功能與服務很快,若是LUIS訓練不夠的話,很有可能就會有這樣的情況發生了

文章範例程式
https://github.com/madukapai/maduka-Robot