こんにちは、サイオステクノロジー技術部 武井です。全5回シリーズのAzure Bot Service及びBot FrameworkのDeepDive(全然深くないですが)も折り返し地点になってきました。今回は、チャットボットの開発に欠かせないCognitiveサービス「QnA Maker」について、書きたいと思います。
- 概要編
- Azure Bot Service編
- 今回はこちら → QnA Maker編
- LUIS編
- Azureのことに何でも答えてくれるLINEボット作る編
本シリーズの成果物は以下のGitHubにあがっております。
https://github.com/noriyukitakei/AzureFAQBot
QnA Makerとは?
QnA Makerとは、あらかじめ「問い合わせ」と「回答」のペアを作成しておき、「問い合わせ」に近い内容の質問があった場合、それに対する「回答」を返すものです。
例えば、QnA Makerに以下のような「問い合わせ」と「回答」のペア(以降、QAデータと呼びます)を作成しておいたとします。
問い合わせ:Azureとはなんですか?
回答:イケてるパブリッククラウドです。
この場合、QnA Makerに「Azureとはなんですか?」という質問を投げると、もちろん「イケてるパブリッククラウドです。」という答えが返ってきます。QnA Makerに質問を投げる場合のインターフェースは、RestfulなAPIです。
また、日本語の微妙な揺らぎも対応してくれます。例えば、「Azureとはなんでしょうか?」という質問にもきちっと回答をしてくれます。ただし、どんな揺らぎでも完璧に回答してくれるわけではないので、ある程度調整は必要になります。
Azure Bot ServiceでのQnA Makerの位置づけ
Azure Bot Service編でもご説明しましたが、QnA MakerはAzure Bot Serviceからは完全に独立したサービスであり、Azure Bot Service以外からも利用することは可能です。インターフェースはRestfulなAPIなので汎用性が非常に高く、あらゆるサービスとの親和性は高いです。
ただ、QnA Makerのような一問一答のサービスは、ヘルプデスク業務の代替として頻繁に使われるチャットボットと非常に相性がよく、仲良くペアで登場することが多いですね。
先程もご説明したように、QnA MakerのインターフェースはRest APIなので、ご多分に漏れず、Bot Framework上のチャットボットアプリケーションからもRest APIでアクセスします。
QnA Makerのシステム構成
QnA Makerの内部的なシステム構成をご説明します。こちらを把握しておかないと、後にご説明するQnA Makerの価格やサイズの検討で迷うことになってしまいます。
まずQnA Makerは、以下の3つのコンポーネントから成り立っています。
- QnA Makerポータル
- QnA Maker Endpoint
- Azure Search
QnA Makerポータルは、問い合わせと回答のペアの作成、同義語の設定などの管理を行うもので、QnA Makerを管理するサービス管理者向けのインターフェースです。初期の問い合わせや回答のペアの投入、運用フェーズにおける問い合わせや回答のメンテナンスなどを行います。
QnA Makerポータルで作成したデータ(問い合わせと回答のペア、同義語など)は、「QnA Maker Endpoint」という、Web App上のRest APIのインターフェースを経由して、Azure Searchに格納されます。Azure Searchの説明はここでは割愛しますが、Azure Searchは全文検索エンジンです。問い合わせに対する回答の検索は、実質このAzure Searchに対して行われます。ただし、QnA Makerを利用するサービス管理者や、チャットボットアプリケーションが直接このAzure Searchに対してリクエストを送ることはなく、その役割を肩代わりしてくれるのが、QnA Maker Endpointです。
QnA Maker Endpointは、QnA Makerの仕様に特化したRest APIのインターフェースであり、本来複雑であるAzure Searchへの検索をよりわかりやすいインターフェースに変換してくれています。QnA Makerのサービスを使う場合は、このわかりやすいRest APIインタフェースを使えばよいのです。
そして、もちろんチャットボットアプリケーションから行われる、問い合わせに対する回答の取得も、このQnA Maker Endpoint経由でAzure Searchに格納されたデータを取得することで実現されます。
ここでお気づきかとは思いますが、QnA MakerのポータルはAzureのポータルとは全く別物です。個人的には統合しないでほしいなと思っております。今のままであれば、QnA Makerのメンテナンスだけお客様に任せることができます。もしAzureポータルに統合されてしまうと、OnA Makerのデータだけではなく、Azure上の他のリソース(Virtual Machineなど)も見えてしまい、その制御や運用がとても煩雑になりそうな気がします。
QnA Makerの利用フロー
本章では、QnA Makerの利用フロー(初期設定から運用まで)をご説明します。QnA Makerは、Cognitiveサービスの一つ、つまりAIです。最初は赤ちゃんで何も知りませんので、最初に色々教え込む必要がありますし、リリース後も面倒を見てあげなければいけません。このあたり、他のAIサービスと同様ですね。
1.QnA Makerの設定
QnA Makerの設定を行います。Azureポータル及びQnA Makerポータルの両方で実施します。
2.初期データの登録
QnA Makerのポータルから、問い合わせと回答のペアの初期データを登録します。初期データの登録方法は、CSVで投入する、指定したURLのWebページの構造を解析してQAデータを取り込むなど様々な方法があります。
3.アプリケーションとの連携
Bot Framework上に構築したチャットボットアプリケーションを改修して、QnA Maker(正確にいうとAzure Search)に登録された回答データをQnA Maker EndpointにRest APIを発行して取得します。しかし、C#の場合、Rest APIを書かなくても、それをラップしたSDKが提供されているので、今回はそちらを使うこととします。
4.利用ログの分析
ユーザーの利用ログを取得し、適切な回答を返すことができなかったケースを分析します。チャットボットアプリケーションのソースコードを修正し、Application Insightsにログを出力するようにします。
5.チューニング
4で取得したログの分析結果をもとにQAデータを追加・修正することで、より精度の高い回答ができるようにします。
QnA Makerの設定
まず、QnA Makerの設定から始めます。以下のURLにログインします。
右上部の「Sign In」をクリックします。
AzureのサブスクリプションにログインしているMicrosoftアカウントでログインします。
「Create a knowledge base」をクリックします。
「Create a QnA service」をクリックします。
Azureポータルに遷移します。
上図の各項目を以下の要領で入力して、最後に「作成」をクリックします。
■ 名前
QnA Makerを識別する名前をつけます。
■ サブスクリプション
ご利用のAzureのサブスクリプションを選択します。
■ 価格レベル
QnA Makerの価格レベルを選択します。以下の「Free」と「Standard」の2つから選択します。価格レベルの選択で大きく異なるのは「ドキュメント」と「トランザクション」です。
Free
無料のプランです。3つのドキュメントを登録できます。ここでいう「ドキュメント」とは、後述するデータソースと同義であり、データソースとは、初期のQAデータを登録するための源泉です。CSV、PDF、Webページなど様々なデータソースを選択することができます。CSVの場合は、カンマ区切りでQAデータを記述して、QnA Makerからアップロードすることで一度に大量のQAを登録することができます。
Freeのプランのドキュメント登録上限は3つまでなので、例えば、異なるCSVのデータを3つまで登録できます。もしくは、CSV1つ、PDF1つ、Webページ1つといった組み合わせも可能です。
3つ以上のドキュメントを登録したい場合は、どれか一つのドキュメントを削除するしかありません。ただ、ドキュメントを削除すると、そのドキュメントによってアップロードされたQAデータは削除されます。例えば、あるCSVによってアップロードしたQAデータがあるとしますと、そのCSVのドキュメントを削除すると、そのCSVによってアップロードされたQAも削除されてしまいましすので、注意が必要です。
また、Freeプランでは各ドキュメントのサイズも1MBまでに制限されています。つまり、1MB以上のCSVを登録することはできません。
他にも、「トランザクション」という概念があります。これは、QnA Makerに登録されているQAデータを管理するためのAPIの呼び出しのことです。先程ご説明した下図の(1)に当たる部分です。Freeプランでは、トランザクションの発行は1秒間に最大3回までに制限されています。ただし、このAPIはQAデータを登録するときにしか呼び出されないため、この制限がネックになることは通常の運用ではないと思われます。頻繁に呼び出されるのは、(3)ですが、ここはWeb App上で処理されるので、こういった制限はありません。
Standard
このプランは有料で、2019年8月14日時点で東日本のリージョンでは、月額10$になっています。ドキュメントは無制限に登録できることが最大の特徴です。
■ 場所
QnA Makerが配置されるデータセンターの場所を選択します。一般的には、ユーザーが利用する場所に近い場所を選択します。
■ リソースグループ
QnA Makerが配置されるリソースグループを選択します。
■ 検索価格レベル
QnA Makerのバックエンドとして利用されるAzure Searchの価格レベルを決定します。Azure Searchの価格は色々な要素により変わります。詳細は以下のURLをご参照下さい。
https://azure.microsoft.com/ja-jp/pricing/details/search/
その要素は多くあり、ここですべてを語ることはできませんが、QnA Makerに関わるところのみご説明致します。
まず、Knowledge baseの数です。Knowledge baseとはQAデータをひとまとめにしたもので、以下の図がKnowledge baseとQAデータの関係になります。Azure用、Bot Framework用それぞれのKnowledge baseがあり、APIから回答を取得する際は、それぞれのKnowledge IDを指定することで、回答を取得する対象を絞ることができます。例えば、Azureに関する問い合わせに対する回答は、AzureのKnowledge baseからのみ取得するということが可能です。
そして、このKnowldge baseの数が、Azure Searchのインデックスの数に依存します。
例えば、Azure SearchのBASICプランでは、インデックス数が15です。このプランを選択した場合、15 – 1 = 14個のKnowledge baseを作成することができます。一つ足りないのは、作成及びテスト用のKnowledge baseとして利用されるものが一つ確保されるためだそうです。
次に、Azure SearchのStorageです。一般的に多くのQAを登録すると、Azure Search上に多くのインデックスが作成されるので、このStrageの容量が多く必要になります。ただ、文章の内容によって、登録されるインデックスの数が異なるので、例えば一概に登録するCSVの容量がXXXだから、Storageの容量がYYYになるとはいい切れません。ここがQnA Maker(というかAzure Search)のサイジングの難しいところです。多めのStorageを確保しておくしかなさそうな気がしています。
■ 場所の検索
Azure Searchが配置される場所です。ユーザーが利用する場所に近いほうが一般的にはよいとされています。
■ アプリ名
QnA MakerのRest APIのエンドポイント(先程の図でQnA Maker Endpointと記載のあるところ)をホストするWeb Appのホスト名になります。Azure内で一意である必要があります。
■ Webサイトの場所
QnA MakerのRest APIのエンドポイント(先程の図でQnA Maker Endpointと記載のあるところ)をホストするWeb Appが配置される場所です。ユーザーが利用する場所に近いほうが一般的にはよいとされています。
■ App Insights
有効にしておけば、各種ログがApplication Insightsに記録できますので、必ず有効にして下さい。今回は特に、「利用ログの分析」のフェーズで必要になります。
■ App Insightsの場所
Application Insightsが配置される場所です。ユーザーが利用する場所に近いほうが一般的にはよいとされています。
先程作成したQnA Makerのサービスを選択します。一旦Refreshをクリックして、最新のデータを取得した上で、以下のセレクトボックスに従い、先程作成したQnA Makerを選択して下さい。3つ目のセレクトボックスは、先のQnA Makerのサービスを作成する際に「名前」に入力したものを選択して下さい。
データソースを選択することができます。しかし、ここでは何も指定しません。最初はQnA Makerの管理画面から一つずつポチポチ手で入力しましょう。
「Create your KB」をクリックします。
これでQnA Makerの設定は完了です。
初期データの登録
初期データを登録します。まず以下のCSVを作成します
Azureとはなんですか?,イケてるパブリッククラウドです。
※ファイル名の拡張子はtxtとしてください。csvだと登録できません。
次に、「My knowledge bases」→「azure-faq」の順にクリックして下さい。
「SETTINGS」をクリックします。
「Add file」をクリックして、先程作成したCSVを選択して、「Save and train」をクリックします。
上部メニューの「EDIT」をクリックします。すると、先程CSVで登録したQAが表示されているのが確認できます。
ここで、登録したQAデータをテストできる便利なツールを紹介します。上部メニューの「Test」をクリックします。チャットウィンドウが表示されます。
「Azureとはなんですか?」と入力してEnterをポチると、先程登録した回答が返ってくることが確認できます。ここで「Azureとはなんですか?」の下にある「Inspect」をクリックします。
「Confidence Socre」というものがあります。これは、問い合わせに対して、どれくらいの精度で高い回答を返すことができたかを示す値で、最高は100です。以下の例は、問い合わせと全く同じ文章を入力しているので、完全一致となり100となっています。
試しにちょっと文章を変えて、「Azureってなに?」とすると、スコアが1.49となりました。随分落ちましたね。
次に、これをPublishするという作業を行います。これを行うことで、Azure Search側にQAデータが登録されて、Bot Framework上に構築したチャットボットアプリケーションからQAデータが取得できるようになります。上部メニューの「PUBLISH」をクリックして、「Publish」というボタンをクリックします。
ちょっと時間がかかりますので、待ちましょう。
Publishが完了すると以下の画面が表示されます。見られるとまずいところは隠してあります。以下の3つの値をメモって下さい。後でアプリケーションの連携の際に使用します。
■ QnAKnowledgebaseId
/knowledgebases/XXXX/generateAnswerの「XXXX」の部分をメモして下さい。
■ QnAEndpointHostName
htts://〜で始まるURLすべてをメモして下さい。
■ QnAAuthKey
「EndpointKey」の後に続く文字列をすべてメモして下さい。
アプリケーションとの連携
Bot Framework上に構築したチャットボットアプリケーションから、QnA Makerに登録したQAデータを取得できるようにします。C#ならば、専用のSDKが用意されているので、それを使うこととします。
Azure Bot Service編で構築したアプリケーションを変更して、QnA MakerのQAデータを取得できるようにします。Azure Bot Service編で構築したアプリケーションのプロジェクトをVisual Studioで開きます。
そして、まず必要なパッケージをインストールするところから始めます。「ツール(T)」→「NuGetパッケージマネージャー(N)」→「ソリューションのNuGetパッケージの管理(N)」の順にクリックします。
下記のようにテキストボックスに「Microsoft.Bot.Builder.AI.QnA」と入力して、Enterをポチると、同名のパッケージが表示されるので、選択してインストールをします。
appSettings.jsonを以下のように変更します。
{ "MicrosoftAppId": "", "MicrosoftAppPassword": "", "ScmType": "None", "QnAKnowledgebaseId": "先程メモしたQnAKnowledgebaseIdの値", "QnAAuthKey": "先程メモしたQnAAuthKeyの値", "QnAEndpointHostName": "先程メモしたQnAEndpointHostNameの値" }
EchoBot.csを以下のように修正します。
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.AI.QnA; using Microsoft.Bot.Schema; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Microsoft.BotBuilderSamples.Bots { public class EchoBot : ActivityHandler { private readonly IConfiguration _configuration; private readonly ILogger _logger; // .NET Coreのいろんな設定を取得できるIConfigurationの実装を // DIコンテナから取得してます。IConfigurationの実装は.NET Coreが自動でDIコンテナに入れてくれています。 // またILoggerインターフェースの実装をDIコンテナから取得します。後ほどご紹介する // Program.csで、ILoggerインターフェースを実装したApplication Insightsのログ出力の実装をDIしますので、 // このILoggerを利用すると、Application Insightsにログが出力されます。 public EchoBot(IConfiguration configuration, ILogger logger) { _configuration = configuration; _logger = logger; } protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) { // QnAMakerのインスタンスを生成し、その引数として、QnAMakerの接続情報を表すQnAMakerEndpointの // インスタンスを指定します。そのインスタンスのプロパティとして、先程appSettings.jsonで // 指定した3つの値を入れます。 var qnaMaker = new QnAMaker(new QnAMakerEndpoint { KnowledgeBaseId = _configuration["QnAKnowledgebaseId"], EndpointKey = _configuration["QnAAuthKey"], Host = _configuration["QnAEndpointHostName"] }); // QnA MakerからQAデータを取得する際にしているオプションのインスタンスを生成します。 // 以下の指定は、スコアの高い順から10件取得し、スコアが50以上のものを取得するということを表しています。 var options = new QnAMakerOptions { Top = 10, ScoreThreshold = 0.5f }; // QnA Makerにアクセスして回答を取得します。 var response = await qnaMaker.GetAnswersAsync(turnContext, options); // 回答情報を格納したresponse変数がNULLではなく、回答が1つ以上あった場合、その回答を表示します。 // それ以外は、適切な回答が見つからなかった旨のメッセージを表示します。 if (response != null && response.Length > 0) { await turnContext.SendActivityAsync(MessageFactory.Text(response[0].Answer), cancellationToken); } else { // ユーザーに対して適切な回答を返せなかった問い合わせをApplication Insightsにロギングします。 // 検索しやすいように特定のイベントIDを指定します。 _logger.LogInformation(turnContext.Activity.Text); await turnContext.SendActivityAsync(MessageFactory.Text("No QnA Maker answers were found."), cancellationToken); } //await turnContext.SendActivityAsync(MessageFactory.Text($"Echo: {turnContext.Activity.Text}"), cancellationToken); } protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) { foreach (var member in membersAdded) { if (member.Id != turnContext.Activity.Recipient.Id) { await turnContext.SendActivityAsync(MessageFactory.Text($"Hello and welcome!"), cancellationToken); } } } } }
Program.csを以下のように修正します。
using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; namespace Microsoft.BotBuilderSamples { public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() // ILoggerインターフェースに特定の実装をDIするための手順になります。ConfigureLoggingメソッドで行います。 .ConfigureLogging(builder => { // AddApplicationInsightsメソッドを使うと、ILoggerインターフェースにApplication Insightsにロギングするための // 実装がDIされます。引数には、Application Insightsのインストゥルメーションキーを設定します。 builder.AddApplicationInsights("Application Insightsのインストゥルメーションキー"); // Infoレベル以上のログのみApplication Insightsに送信されるよう設定を行います。 builder.AddFilter<Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider> ("", LogLevel.Information); }); } }
Azure Bot Service編で紹介したのと同じ手順でBot Framework Emulaterを起動して、動作確認します。事前に登録した問い合わせに対する回答が帰ってきているのがわかります。ちなみに「Azureってなに?」に対する回答は、先程もQnA Makerでテストしたように著しくスコアが低いので、「No QnA Maker answers were found.」になります。想定どおりですね。
利用ログの分析
次に利用ログの分析をします。ユーザーの問い合わせに対して適切な質問を返せなかった場合、それを抽出して分析、そしてチューニングが必要になります。これはどんなAIサービスでも同じかと思います。
先程、適切な回答を返せなかった場合にApplication Insightsにログを出すようEchoBot.csを修正しました。「Azureってなに?」の質問については、適切な回答がなかったので、Application Insightsにログがあるはずです。検索してみましょう。
まず、Azure Bot Serviceがあるリソースグループにアクセスします。その中のリソース一覧にApplication Insightsのリソースがあるので、それをクリックします。
左部のメニューから「ログ(Analytics)」をクリックします。そしてクエリを入力するところに以下のクエリを入力します。
traces | where customDimensions.CategoryName == "Microsoft.BotBuilderSamples.Bots.EchoBot" | order by timestamp desc
「Azureってなに?」という問い合わせがログにありますね。これが適切な回答を返せなかった問い合わせになります。では次章では、この結果をもとにチューニングしてみましょう。
チューニング
チューニングには主に以下の3つの手法があります。
- 新しいQAデータの登録
- 代替フレーズの追加
- 同義語の追加
「1.新しいQAデータの登録」は、利用ログの中に全く未知な問い合わせが多数あったときに、それを新しいQAデータとして登録します。例えば、既存のQAデータには全く登録がないApplication Insightsに関する問い合わせで「Application Insightsってなに?」というのが多数あった場合に、Application Insightsについて知りたがっているユーザーが多くいると判断できる場合に、それを新しいQAデータとして登録します。
「2.代替フレーズの追加」については、既存のQAデータの中に適切な回答があるのだけど、微妙な表記の揺れによって回答が返せなかった場合に行われる対処です。例えば、先の例で言えば「Azureってなに?」の問い合わせには回答を返すことができませんでした。これは既存のQAデータに登録されている「Azureとはなんですか?」とは異なる微妙な表記の揺れ(「とはなんですか?」と「ってなに?」の違い)によって、生じたスコアの違いによるものです。「Azureってなに?」という問い合わせについては、QnA Makerのスコアは50以下でしたので、回答を返すことができませんでした。この場合は、「Azureとはなんですか?」という問い合わせの代替フレーズとして「Azureってなに?」を追加することによって解決します。
最後の「3.同義語の追加」については、似ているんだけど異なる言葉のために低スコアになってしまったときの対処です。例えば、「Microsoft Azureとはなんですか?」という問いについては、先に登録したQAデータでは低スコアになってしまいます。これは、QnA Makerでは「Azure」と「Microsoft Azure」は別の言葉として判断されたため、低スコアになってしまいます。このような場合に「Azure」の同義語として「Microsoft Azure」を登録すればこの問題は解決します。
ちょっとうんちくが長くなってしまいました。では先の分析結果に基づき早速チューニングしてみましょう。今回の対処方法は「2.代替フレーズの追加」に該当します。
QAデータの一覧画面にアクセスして、「Azureとはなんですか?」の下にある「Add alternative phrasing」をクリックします。
「Azureってなに?」と入力して、「Save and train」をクリックします。
上部メニューの「PUBLISH」をクリックして、「Publish」というボタンをクリックします。
ほら、今度はちゃんと回答を返すことができました。
次に同義語を登録するパターンもご紹介したいと思います。先にご説明した「3.同義語の追加」の場合の対応ですね。
QnA Makerがあるリソースグループにアクセスします。その中のリソース一覧に種類がCognitive Serviceで、名前が「azure-qna-maker」のリソースがあるので、それをクリックします。
左部メニューの「概要」をクリックして、「エンドポイント」の項目に書いてあるURLをメモします。
左部メニューの「キー」をクリックして、「キー1」に書いてある値をメモします。
以下の内容のHTTPリクエストを発行することにより同義語を登録できます。下記の例は、「Azure」の同義語として「Microsoft Azure」を登録する例です。
※ なぜかGUIがなく、Rest APIを発行するしか方法がありません(´・ω・`)
URL | https://[先程メモしたエンドポイント]/qnamaker/v4.0/alterations |
Method | PUT |
Header | Content-Type: application/json |
Ocp-Apim-Subscription-Key: [先程メモしたキー] | |
Body | { “wordAlterations”: [ { “alterations”: [ “Azure”, “Microsoft Azure” ] } ] } |
これで同義語も追加できます!!
まとめ
いかがでしょうか?すごいですね、QnA Maker!!もうQnA Makerなしではいけていけません。No QnA Maker, No Life!!