概述
科技的不斷發展帶動着人們生活質量不斷的提升,其中一方面就體現在日常家庭生活中,智能設備層出不窮,給人們的生活帶來了很大的便利。
以電視為例,幾十年前的電視還是按鈕式的,每次換台還要跑到電視跟前;后來使用遙控器控制成為了主流,人們可以舒服的窩在沙發里看電視;再后來隨着互聯網及移動通信技術的發展,電視、機頂盒、空調等,都可以在手機上進行控制,再也不用幾個遙控器之間來回倒騰了,還不耽誤刷微博;近年來隨着人工智能的發展,語音識別、合成技術日趨成熟,家電已經可以理解語音指令,能夠按照人類的交流方式進行人機交互了,人們只需要動嘴說說話就可以完成各種操控。
一種智能家居的典型場景如下圖所示,以智能音箱為核心,所有設備通過WiFi與智能音箱連接並可以接受智能音箱的控制指令。
人們可以和音箱打招呼,音箱能夠自然的回答問好;也可以詢問天氣,音箱能夠自動根據當前位置搜索並回答未來一天的天氣;還可以直接對着音箱說一聲"把燈關掉",音箱應該能夠控制關掉燈,或者主動詢問"要關哪個燈",在得到明確指令后再執行指定的操作。
本文將借助微軟認知服務中的多個服務實現一個簡單的智能家居應用,來模擬一個語音控制開關燈的場景,期望能給予大家一些啟示,並期望大家可以利用更多的微軟認知服務擴展出更多炫酷實用的功能。
流程
這一節我們看一下語音控制開關的流程是怎樣的。
從場景上來看很簡單,人直接說"開燈",燈就可以打開,當然說"請把燈打開",燈也應該能夠打開。
但是對於傳統的程序員來說,實現起來主要的難點在於如何讓程序理解人類說的話,尤其是用戶一般都是用很自然的語言去說,用戶的習慣不一樣,語法結構也不一樣。
為了解決這個難點,這里我們將借助微軟認知服務里的語音轉文本服務和語言理解服務來賦予程序理解人類說話的能力。
要做到這些,主要分三個步驟:
第一步,借助語音轉文本服務,將用戶的語音輸入識別成對應的文字,比如"開燈"、"請把燈打開"等等。
第二步,借助語言理解服務來將自然語言轉為程序可以理解的意圖。不管用戶是說"開燈"還是"請把燈打開",語言理解服務都可以識別出用戶的意圖是打開(TurnOn)。
第三步,按照識別出的用戶的意圖去控制燈打開或關閉。
語音轉文本服務
語音轉文本服務提供將音頻流轉錄為文本的能力,微軟的語音轉文本服務采用了和微軟小娜相同的技術。應用程序借助此服務可以輕松地將聲音轉錄為文字,之后可以直接顯示或做更進一步的使用。
語音服務提供SDK和REST API兩種使用方式。使用SDK可以將服務更方便的集成到應用程序中,並且可以提供額外的功能,如實時的中間轉錄結果、靜默一段時間自動停止、轉錄超長的音頻等。目前提供的SDK有.Net、C/C++、Java版本,如果使用其它編程語言,可以考慮更通用的REST API方式,但是不能提供SDK中所有的功能。
在線體驗
語音轉文本服務的在線體驗地址是https://azure.microsoft.com/zh-cn/services/cognitive-services/speech-to-text/,可以先通過此頁面對該服務有個初步的認識。
如果設備有麥克風,可以點擊開始錄音,然后對着麥克風說話,查看語音轉文本的效果;如果沒有麥克風,也可以點擊下方的兩個示例體驗一下。
可以看到網頁在錄音的過程中,同時顯示語音轉文本的中間結果,並按照最新播放的內容不斷糾正舊文本、顯示新文本,最終給出了一個最佳的文本結果。
申請試用
后面要在程序中調用語音轉文本服務,必須要有服務密鑰才可以。
試用密鑰的有效期是 30 天,每月 5000 個事務,每分鍾 20 個。每個賬號只能申請一次試用。
申請步驟:
- 打開申請試用頁面:https://azure.microsoft.com/zh-cn/try/cognitive-services/?api=speech-services
- 找到語音服務,點擊右側的獲取API密鑰
- 在彈出頁面點擊來賓7天試用下面的開始使用(不用管這里顯示的7天,語音服務現在還是預覽版,有30天的試用期,申請完成后顯示30天)
- 在服務條款頁面勾選同意,選擇國家/地區為中國,下一步
- 選擇要使用的賬號,筆者這里選擇Microsoft
- 登錄后可以看到密鑰申請成功,如下圖所示
這里要注意圖中重點標出的部分,一個是終結點中westus,這個是當前密鑰可使用的區域,試用密鑰都是westus;另一個是下面的密鑰1和密鑰2。區域和密鑰稍后在后面程序代碼中要用到,大家可以單獨記下來或者保持該網頁不要關閉,方便后續使用。
在Azure中申請使用
上面30天的試用密鑰過期后如果想繼續免費使用該服務,還可以到Azure門戶中申請密鑰,前提是首先要有Azure賬戶。
如果還沒有Azure賬戶,可以免費注冊一個。打開免費注冊頁面,https://azure.microsoft.com/zh-cn/free/ai/,點擊免費開始,然后按提示一步步補充完整注冊信息。注冊過程中需要驗證手機號及信用卡,而且會看到1美元的預付款,不過不用擔心,這1美元只是用來驗證信用卡是否可用,會在幾天后返還。
有了Azure賬戶后,打開Azure門戶網站,https://portal.azure.com/,點擊創建資源,搜索找到Speech(預覽),按提示一步步創建,就可以得到對應的密鑰。
語言理解服務
語言理解服務,Language Understanding Intelligence Service,簡稱LUIS。后面文中使用LUIS來代替語言理解服務。
LUIS提供在線的API服務,可以將用戶輸入的自然語言描述的文本,轉換成為計算機能夠直接使用的結構化的信息,這樣應用程序就可以借助LUIS理解人類自然語言的輸入。
在線體驗
LUIS的在線體驗場景是https://azure.microsoft.com/zh-cn/services/cognitive-services/language-understanding-intelligent-service/。
打開網頁后,可以看到如下圖所示的示例。可以選擇一條指令來觀察燈光的變化,也可以通過輸入自定的指令文本來控制燈光,可以試試自己習慣的語法,看能否正確的控制燈光變化。
基本術語
LUIS應用程序
在使用LUIS的過程中,我們最初會接觸到app這個詞,這里的app是指LUIS應用程序。一個LUIS應用程序其實對應的就是一個語言理解模型。通常情況下,一個LUIS應用程序(即一個模型),是用來解決一個特定域(主題)內的語言理解問題的。
舉個例子,對於旅行相關的主題,如預訂機票、酒店等,可以創建一個LUIS應用程序,而對於購物相關的主題,如搜索商品、下單,可以再創建另外一個LUIS應用程序。
意圖(Intent)
意圖(Intent),表示用戶想要執行的任務或操作。比如詢問天氣、預訂機票等,都是意圖,在控制開關燈的例子中,開燈和關燈就是意圖。
實體(Entity)
實體(Entity),想當於上面意圖中的參數。比如對象、時間、地點等,都可以標記為實體。舉個例子,幫我預定明天北京飛往西雅圖的飛機,這里用到了三個實體:明天、北京、西雅圖,分別表示了時間、始發地、目的地三個參數。
另外,實體在不同的意圖之間是可以共享的。舉個例子,明天天氣怎么樣,這個語句的意圖是詢問天氣,而明天是其中的一個實體,表示時間參數,和上一個例子中的實體是一樣的。
語句(Utterance)
語句(Utterance),是LUIS應用需要處理的用戶的輸入。在訓練時提供的語句應該是盡可能包含不同的說話方式或不同的語法的,這樣訓練出來的結果會更好一些。
定制語言理解服務
LUIS提供一些預先構建好的域、實體及意圖,覆蓋了比較全的場景。一種常見的做法是添加預構建的內容,然后迭代完成自定義的模型。
我們這里的演示的場景比較簡單,直接動手從頭定制一個語音控制開關燈需要用到的LUIS應用程序。
登錄LUIS
打開LUIS網站https://www.luis.ai,並登錄對應的微軟賬號。
如果是第一次登錄,網站還會請求訪問對應賬號的一些信息,點Yes繼續。網站首次加載較慢,需要耐心等待,必要的時候可以刷新重新再次加載。
如果頁面跳轉到了歡迎頁面https://www.luis.ai/welcome,可以翻到頁面最下方,點擊Create LUIS app,然后在下一個頁面補充缺失的信息,將Country設為China,並勾選I agree條款,然后點擊Continue。
直到看到My Apps頁面,才算登錄完成,如下圖所示
創建LUIS應用
點擊Create new app,創建一個新的LUIS應用。注意,Culture要選擇Chinese,Name隨意,這里使用LightControl。然后點擊Done完成創建。
添加意圖
創建完LUIS應用程序后會直接進入Intents頁面,也可以通過點擊左側的Intents進入。然后點Create new intent創建一個新的意圖。
在彈出窗中輸入意圖的名稱,這里使用TurnOn表示開燈的意圖,完成后點擊Done。注意這里意圖的名稱在后面程序中會用到,拼寫及大小寫要保持前后一致。
完成后會進入TurnOn意圖的設置頁面,可以在輸入框中輸入不同的語句,然后回車就可添加到語句列表中。這里添加了4條語句:打開、開燈、請開燈、把燈打開。
點擊左側Intents回到意圖頁面,然后重復上面的步驟,添加TurnOff意圖表示關燈,並添加4條語句:關閉、關燈、請關燈、關閉燈泡。
添加實體
意圖是必需的,而實體不是必需的。可以根據具體的場景來決定是否添加實體,比如有需要控制客廳燈和卧室燈兩個燈,應該定義個實體來標記房間是客廳還是卧室,而不是使用多個意圖分別控制不同的燈。
LUIS支持多種類型的實體來應對各種復雜的情況,包括簡單實體、列表實體、正則表達式、復合實體等,這里不詳細展開,有興趣的可以參考https://docs.microsoft.com/zh-cn/azure/cognitive-services/luis/luis-concept-entity-types里的介紹。
在我們下面的例子中只控制一個燈,這里就不添加實體了。
訓練
添加完意圖和實體后就可以訓練了,可以看到右上角有個帶紅點的Train按鈕,點擊就可以直接訓練。訓練速度非常快,幾秒種之后看到紅點變成綠點就表示訓練完成了。
測試
點擊右上角的Test,會滑出Test側邊欄,在輸入框中輸入測試語句並回車,可以看到對應的意圖及得分。這里使用的測試語句是請幫我把燈打開,可以看到識別到的意圖是TurnOn,得分是0.86,意圖正確且比較接近1,結果還不錯。
發布
點擊頂部的PUBLISH進入發布頁面,在頁面中點擊Publish按鈕就直接發布了。發布成功后可以看到右側的版本號及發布時間。
將頁面翻到底部可以找到服務發布后的區域、密鑰和終結點。
要注意這里的終結點是以q=結尾的,需要在終結點的最后拼上要測試的語句,比如請幫我把燈打開,得到一個完整的鏈接,然后再從瀏覽器里打開,就可以看到識別后的結果。
識別結果是以JSON格式返回的,其中topScoringIntent是最匹配的意圖,我們后面要用到。
迭代
LUIS應用在發布后可以繼續改進數據,添加新語句或意圖或實體,然后再訓練,再發布。在這樣的周期中反復迭代,最終達到最佳的效果。
在上一步發布的終結點最后拼上一個新的語句"北京天氣怎么樣",然后在瀏覽器進行測試,可以發現最終topScoringIntent是TurnOff,這顯然是不對的,而且得分只有0.14,和其它幾個意圖的得分差不多。
我們需要對模型改進一下,再進行一次迭代。回到LUIS的意圖頁面,我們可以看到這里有一個None意圖,這是創建完LUIS應用程序后自帶的一個意圖,可以直接打開該意圖的頁面,在其中添加一條語句無關語句,比如"北京天氣怎么樣"。
還一種做法是通過審查終結點上得到的語句來改進模型。點擊左側的Review endpoint utterances,進入審查頁面,可以看到我們剛才在終結點上查詢的語句,我們可以把"北京天氣怎么樣"右側的Aligned Intent改為None,然后點擊右側的對勾,此時,該語句就會被自動加到了None意圖中。
然后再次訓練、發布,再使用之前的終結點查詢"北京天氣怎么樣",可以看到結果正確,topScoringIntent是None意圖。
Tips:最佳實踐是None意圖中語句的數量應占總語句數量的10%~20%。
查看應用程序ID
點擊SETTINGS進入應用程序設置頁面就可以看到Application ID,某些情況下會用到。
密鑰管理
如果要在程序中調用LUIS服務,必須要有密鑰。LUIS 中有兩種密鑰:創作密鑰和終結點密鑰。
創作密鑰是在首次登錄LUIS時自動分配的,終結點密鑰是在Azure門戶申請的。終結點密鑰不是必需的,可以在下面的示例中繼續使用創作密鑰。但是創作密鑰的免費配額要比終結點密鑰低得多,所以還是建議在LUIS應用程序發布后,申請一個終結點密鑰並分配給LUIS應用程序供外部調用。
創作密鑰
創作密鑰是在登錄LUIS時自動創建的免費密鑰,創建的多個LUIS應用程序使用同一個密鑰,且每月只能調用1千次。
創作密鑰的值可以在User settings中找到,點擊右上賬戶名出現下拉菜單,再點擊Settings,就可以看到創作密鑰:
也可以在發布頁面的終結點處看到創作密鑰:
終結點密鑰
終結點密鑰是在Azure門戶中申請的密鑰,申請的免費密鑰的配額是月1萬次調用,每秒最多5次調用,遠大於創作密鑰的配額。
終結點密鑰的申請方法和前面介紹的在Azure門戶中申請語音服務密鑰的方法類似,在創建資源時搜索LUIS即可找到Language Understanding服務,按步驟創建個免費密鑰即可。
這里我們換一種申請方式,在Visual Studio中借助Tools for AI來申請密鑰。
- 確保已安裝Visual Studio 2017並安裝了Tools for AI擴展。
如果沒有安裝,請參考AI應用開發實戰 - 從零開始配置環境中Windows下開發環境搭建的第1節安裝VS和第5節安裝Tools for AI插件。
- 確保Visual Studio中登錄了對應的微軟賬號。
如果沒有登錄,可以在Visual Studio的右上角看到登錄按鈕,點擊后按步驟登錄。
- 確保可以看到Azure訂閱
在視圖菜單中,選擇Cloud Explorer,如下圖點擊賬戶管理,確保可以刷出你的訂閱,如果沒有刷出,請嘗試重啟Visual Studio並稍后再試。
- 創建認知服務
在視圖菜單中,選擇服務器資源管理器,找到AI工具,在Azure認知服務上點右鍵,創建新認知服務。
選擇已有的訂閱,新申請的賬戶通常會有個名為免費試用的訂閱。選擇一個已有的資源組,如果沒有資源組,需要在Azure上先創建一個。API類型選擇LUIS。服務名可以隨便起,這里使用LUIS。位置可以先東亞,East Asia。定價申請免費的,直接選F0。點擊確定來創建。
創建成功后可以在Azure認知服務下看到名為LUIS的服務,右鍵選擇管理密鑰,就可以看到申請的密鑰1和密鑰2。新申請的密鑰需要幾分鍾的部署時,之后才可以使用。
分配密鑰
新申請的終結點密鑰需要分配到LUIS應用程序中才可以使用對應的終結點進行查詢。
回到LUIS的發布頁面,翻到最下面的Resources and Keys,切換到Asia Regions,點擊Add Key按鈕。
在彈出的Assign a key to your app對話框中,依次選擇剛才使用的訂閱和創建的密鑰,點擊Add Key按鈕。
可以看到Resoures and Keys中Asia Regions下出現了終結點密鑰和使用該密鑰的終結點地址。
構建智能家居應用
程序的源代碼在https://github.com/MS-UAP/LightControl,將源代碼下載到本地后,用Visual Studio 2017打開解決方案文件src\LightControl.sln。
界面設計
在解決方案資源管理器中找到Form1.cs,雙擊打開對應的設計界面,如下圖所示。
左側是一個圖片控件,可以顯示燈打開時和關閉時的圖片,來模擬真實的開關燈操作。在解決方案資源管理器中可以看到LightOn.png和LightOff.png兩張圖片,用來顯示燈處在不同的狀態下。
右側是一個文本框,用來顯示一些日志,比如語音轉文本過程中的中間結果、最終結果以及識別出的意圖等信息。通過日志,我們可以看到語音服務和LUIS是否已正常連接並正常工作,如果出現異常,也會在這里顯示異常信息,方便對問題進行排查。
在Form窗體上點右鍵,查看代碼,打開該窗體的代碼頁面。
首先,在構造函數中,控件初始化完成后,讓圖片控件顯示一張關閉着的燈的圖片:
public Form1() { InitializeComponent(); pictureBox1.Load("LightOff.png"); }
然后,封裝要用到的一些界面操作,例如,在右側文本框中追加日志輸出,模擬打開燈,關閉燈等:
private void Log(string message, params string[] parameters) { MakesureRunInUI(() => { if (parameters != null && parameters.Length > 0) { message = string.Format(message + "\r\n", parameters); } else { message += "\r\n"; } textBox1.AppendText(message); }); } private void OpenLight() { MakesureRunInUI(() => { pictureBox1.Load("LightOn.png"); }); } private void CloseLight() { MakesureRunInUI(() => { pictureBox1.Load("LightOff.png"); }); } private void MakesureRunInUI(Action action) { if (InvokeRequired) { MethodInvoker method = new MethodInvoker(action); Invoke(action, null); } else { action(); } }
集成語音服務SDK和語言理解服務SDK
這個代碼示例中,我們用語音服務SDK來處理音頻轉文本,用語言理解服務SDK來提取文本中的意圖。
首先添加SDK的引用。切換到解決方案資源管理器,在LightControl下的引用上點右鍵,選擇管理NuGet程序包。
在打開的NuGet包管理器中,依次搜索並安裝下面3個引用:
- Microsoft.CognitiveServices.Speech
- Newtonsoft.Json
- Microsoft.Azure.CognitiveServices.Language.LUIS.Runtime
然后回到Form1.cs的代碼編輯頁面,引用命名空間
using Microsoft.CognitiveServices.Speech; using Microsoft.Azure.CognitiveServices.Language.LUIS.Runtime;
然后配置兩個服務用到的密鑰、區域及終結點
本文前面申請到的語音服務的30天試用密鑰是6d04e77c6c6f4a02a9cf942f6419ffaf,區域是westus。前面定制的LUIS應用程序的ID是130e348f-d131-41d1-96b2-a29d42cc1d96,密鑰這里示例先用創作者密鑰58c57e08c8d540a4aa2196588eb69f8a,終結點字符串比較長,但是LUIS SDK中只需配置到域名即可,不需要后面的路徑,所以這里的終結點是https://westus.api.cognitive.microsoft.com
// 設置語音服務密鑰及區域 const string speechKey = "6d04e77c6c6f4a02a9cf942f6419ffaf"; const string speechRegion = "westus"; // 設置語言理解服務終結點、密鑰、應用程序ID const string luisEndpoint = "https://westus.api.cognitive.microsoft.com"; const string luisKey = "58c57e08c8d540a4aa2196588eb69f8a"; const string luisAppId = "130e348f-d131-41d1-96b2-a29d42cc1d96";
然后初始化SDK
添加成員變量語音識別器和意圖預測器,並在Form1_Load函數中初始化,掛載對應的事件處理函數。
Tips:Form1_Load函數需通過在Form窗體設計界面直接雙擊窗體的標題欄來添加。
// 語音識別器 SpeechRecognizer recognizer; // 意圖預測器 Prediction intentPrediction; private void Form1_Load(object sender, EventArgs e) { try { SpeechFactory speechFactory = SpeechFactory.FromSubscription(speechKey, speechRegion); // 設置識別中文 recognizer = speechFactory.CreateSpeechRecognizer("zh-CN"); // 掛載識別中的事件 // 收到中間結果 recognizer.IntermediateResultReceived += Recognizer_IntermediateResultReceived; // 收到最終結果 recognizer.FinalResultReceived += Recognizer_FinalResultReceived; // 發生錯誤 recognizer.RecognitionErrorRaised += Recognizer_RecognitionErrorRaised; // 啟動語音識別器,開始持續監聽音頻輸入 recognizer.StartContinuousRecognitionAsync(); // 設置意圖預測器 LUISRuntimeClient client = new LUISRuntimeClient(new ApiKeyServiceClientCredentials(luisKey)); client.Endpoint = luisEndpoint; intentPrediction = new Prediction(client); } catch (Exception ex) { Log(ex.Message); } }
然后補充完整幾個事件處理函數
語音轉文本時會不斷的接收到中間結果,這里把中間結果輸出到日志窗口中
// 識別過程中的中間結果 private void Recognizer_IntermediateResultReceived(object sender, SpeechRecognitionResultEventArgs e) { if (!string.IsNullOrEmpty(e.Result.Text)) { Log("中間結果: " + e.Result.Text); } }
識別出現錯誤的時候,也把錯誤信息輸出到日志窗口
// 出錯時的處理 private void Recognizer_RecognitionErrorRaised(object sender, RecognitionErrorEventArgs e) { Log("識別錯誤: " + e.FailureReason); }
靜默幾秒后,SDK會認為語音結束,此時返回語音轉文本的最終結果。這里拿到結果后,在日志窗口中顯示最終結果,並進一步處理文本結果
// 獲得音頻分析后的文本內容 private void Recognizer_FinalResultReceived(object sender, SpeechRecognitionResultEventArgs e) { if (!string.IsNullOrEmpty(e.Result.Text)) { Log("最終結果: " + e.Result.Text); ProcessSttResultAsync(e.Result.Text); } }
添加處理文本的函數,這里從文本中獲取意圖,然后根據意圖的值,來執行開燈或關燈操作
private async void ProcessSttResultAsync(string text) { // 調用語言理解服務取得用戶意圖 string intent = await GetIntentAsync(text); // 按照意圖控制燈 if (!string.IsNullOrEmpty(intent)) { if (intent.Equals("TurnOn", StringComparison.OrdinalIgnoreCase)) { OpenLight(); } else if (intent.Equals("TurnOff", StringComparison.OrdinalIgnoreCase)) { CloseLight(); } } }
然后,添加對LUIS SDK的調用,可以從文本中獲取意圖
private async Task<string> GetIntentAsync(string text) { try { var result = await intentPrediction.ResolveAsync(luisAppId, text); Log("意圖: " + result.TopScoringIntent.Intent + "\r\n得分: " + result.TopScoringIntent.Score + "\r\n"); return result.TopScoringIntent.Intent; } catch (Exception ex) { Log(ex.Message); return null; } }
編譯運行,並對着麥克風說出指令,就可以看到對應的效果了。同時我們可以看到語音轉文本的中間結果在不斷變化,說明服務端會根據后續接收到的音頻不斷進行調整,最終返回一個最佳的結果。
擴展和習題
本文通過介紹語音轉文本服務及語言理解服務,並將兩個服務集成在一個程序中完成了個模擬的智能家居應用。
回頭看一下我們的場景非常簡單,這里提出一些改進作為習題供大家練習:
- 現在只能控制一個燈,可以考慮控制更多的燈,客廳燈,卧室燈,等等,可以考慮在LUIS中增加實體來實現這個目標。
- 如果每次開燈或關燈時,智能家居都可以用人類的語音反饋給人"燈已打開"、"已經把燈關上了",這樣的話可以得到更好的體驗。微軟認知服務也提供了文本轉語音的服務,在不久的將來還會支持開發者定義自己的語音字體,可以定制自己喜歡的聲音,使得用戶的體驗更好。
- 實現了多個燈的控制及語音反饋以后,還可以考慮讓智能家居應用支持多輪對話。比如,當人說開燈的時候,智能家居可以詢問"要打開哪里的燈",並按照后續補充的指令打開對應的燈。
當然還可以舉出更多的場景使得智能家居更完美,大家可以充分種用微軟提供的認知服務,考慮並設計自己的智能家居應用。