kosen-career’s diary

高専キャリア公式

【FIXER x 高専キャリア】上級クエスト指南書 - AIを使ってBotに人間味を加えてみた

目次

はじめに

上級クエストの題材は 初級・中級クエストを終了した状態を想定としています。まだの方はこちらのまとめから取り組みましょう!

kosen-career.hatenablog.com

上級クエストの目標

本記事は上級クエストの動画に沿って、解説していきますが、メインはコーディングの解説になります。 Azureの設定やデプロイ方法等については、動画を参考にしつつ、こちらの記事をご覧ください。

kosen-career.hatenablog.com

上級クエストではAIを使い、Botに「考える」という能力を付与することです。

f:id:nomunomu0504:20210125194853p:plain

中級クエストでは、ネガティブワードを定義して利用していました。

// ネガティブワードの定義
public string[] negativeWords = {
    "しんどい", "つらい", "たいへん"
};

しかし、全てのネガティブワードを定義するのは難しいです。また、ここにポジティブワードを定義するとなると、処理が大変になります。

そこで、AzureのCognitive Services(Text Analytics)を使って、送られてきたメッセージを分析してもらい、文章全体がネガティブなのかポジティブなのかを判定してもらいます。

この動作をするBotを上級クエストで作っていきたいと思います!

1. Cognitive Serviceのアカウントを作成する

Azureのポータルを開いて「リソースの追加」から「Text Analytics」を選択します。

portal.azure.com

各項目に必要事項を設定し、リソース生成が終わったら、「キーとエンドポイント」を選択して、「キー1」と「エンドポイント」をメモ帳などにコピーしておきます。

f:id:nomunomu0504:20210126021119p:plain

「キー2」は「キー1」が利用できなくなったときに利用するため、今回は使用しません。

2. BotでText Analyticsを使えるようにする

2.1. Text Analyticsをインストール

BotでText Analyticsを利用するには「パッケージ」というText Analyticsを使うための呪文が入った宝箱を手に入れる必要があります。

f:id:nomunomu0504:20210125223548p:plain

Visual StudioでBotのソースを開きます。開いたら、ソリューションにあるEchoBotと書いてある部分を右クリックして「NuGetパッケージの管理」を押します。

f:id:nomunomu0504:20210125223758p:plain

検索に「CognitiveServices.Language.TextAnalytics」と入力します。画像と同じものを選択し、パッケージの追加をします。このとき「ライセンスの同意」の画面が表示されるかもしれません。表示されたら「同意する」を押して進みます。

f:id:nomunomu0504:20210125223954p:plain

追加が終わると、NuGetフォルダーにTextAnalyticsが追加されていると思います。

f:id:nomunomu0504:20210125224318p:plain

追加できたら、TextAnalyticsを使う準備をします。下のコードはTextAnalyticsのパッケージに含まれる呪文のうち、何を使うか。という宣言をしています。この宣言をしないと、一体「何の」「どの呪文」を使うかが分からないので、Botは呪文を使うことができません。

// TextAnalyticsを使うための呪文
using System;
using System.Net.Http;
using Microsoft.Rest;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics.Models;

こんな感じで追加します。元から書かれている using 〜; は消さないようにしてくださいね。今まで動いていたBotが動かなくなります。

f:id:nomunomu0504:20210125224816p:plain

2.2. キーとエンドポイントURL、呼出しの設定

次に「1.」で作成したCognitiveServiceのアカウントをTextAnalyticsで利用できるように設定していきます。

f:id:nomunomu0504:20210125225026p:plain

この設定を行うことで、自分が作ったBotのみが、自分が作成したTextAnalyticsにアクセスできるようになります。

(※ 今回はキー、エンドポイントURLをソースコードに直書きしますが、ソースコードを外部に公開するときには、Azure Key Vault というサービスを使い、安全に公開できるようにする必要があります。)

f:id:nomunomu0504:20210125225037p:plain

下のコードをソースに貼り付けます。貼り付けたら、「1.」でメモした「キー」と「エンドポイントURL」に置き換えてあげます。

// CognitiveServicesのキー
private static readonly string key = "<メモしたkeyと置き換える>";
// CognitiveServicesのエンドポイントURL
private static readonly string endpoint = "<メモしたエンドポイントURLと置き換える>";

f:id:nomunomu0504:20210126005632p:plain

初級・中級クエストで書いたコードはコメントアウトもしくは消しても大丈夫です👀

次に、Botを呼び出すための元を作っていきます。Botを呼び出すために以下の3つの作業を行います。

2.2.1. authenticateClient メソッドの呼出部分を追記
2.2.2. ApiKeyServiceClientCredentials クラスのコピー
2.2.3. authenticateClient メソッドの作成

f:id:nomunomu0504:20210126010536p:plain

2.2.1. authenticateClient メソッドの呼出部分
// authenticateClientを呼び出す部分
var client = authenticateClient();

貼り付けただけでは、エラーになりますが「3.」でメソッドの本体を作成することで解決します。

f:id:nomunomu0504:20210126012825p:plain

2.2.2. ApiKeyServiceClientCredentials クラスのコピー

こちらのコードをコピーします。これはAzureのTextAnalyticsへアクセスする際に必要な情報を載せてくれるクラスになります。 この処理を経由しないと正常にアクセスできないので、注意です。

class ApiKeyServiceClientCredentials : ServiceClientCredentials
{
    private readonly string apiKey;

    public ApiKeyServiceClientCredentials(string apiKey)
    {
        this.apiKey = apiKey;
    }

    public override Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (request == null)
        {
            throw new ArgumentNullException("request");
        }
        request.Headers.Add("Ocp-Apim-Subscription-Key", this.apiKey);
        return base.ProcessHttpRequestAsync(request, cancellationToken);
    }
}

記述場所は EchoBot クラスと同じ階層になります。 画像を見ると、コードが少なくなっているように見えますが、折りたたんでいるだけなので、変わってないです!

f:id:nomunomu0504:20210126015632p:plain

2.2.3. authenticateClient メソッドの作成

最後に、BotからTextAnalyticsを呼び出すための元を作成します。これを呼び出すことでCognitiveServiceを呼び出すためのクライアントが作成されます。

これを追加することで「1.」で追加したメソッドのエラーが解消されているはずです。

static TextAnalyticsClient authenticateClient()
{
    var credentials = new ApiKeyServiceClientCredentials(key);
    var client = new TextAnalyticsClient(credentials)
    {
        Endpoint = endpoint
    };
    return client;
}

f:id:nomunomu0504:20210127202950p:plain

3. 実際にTextAnalyticsを使ってみる

f:id:nomunomu0504:20210126020854p:plain

ここまでこれば、あとは実際にAzure上のTextAnalyticsを呼び出しに行く部分を作成します。今回は「センチメント分析(Sentiment)」を使って見たいと思います。

まずは、センチメント分析を呼び出すメソッド「sentimentAnalysisExample」を作成します。後からアプリを実行してもらいログを確認するのですが、とても見にくいので動画とは違うコードが含まれています。これは少しでも見やすくするためなので、あってもなくても問題ないですが、あったほうが見やすいです👀

// Azureのセンチメント分析を実行するメソッド
static void sentimentAnalysisExample(ITextAnalyticsClient client)
{
    var result = client.Sentiment("I had the best day of my life.",  "en");
    Debug.WriteLine("----------------------------------------------");
    Debug.WriteLine($"Sentiment Score: {result.Score:0.00}");
    Debug.WriteLine("----------------------------------------------");
}

f:id:nomunomu0504:20210127203950p:plain

ただ、これをそのまま貼り付けても「Debug」という部分でエラーが発生すると思います。このエラーは「Debug」という呪文が使えるようになっていないため、使えません。というエラーになります。そのため自分で以下の using を追記するか、エラーの解決から自動で入力してもらいエラーを解消しましょう。

using System.Diagnostics;

f:id:nomunomu0504:20210127204011p:plain

ちゃんとエラーが消えて、赤波がなくなっていればOKです!

f:id:nomunomu0504:20210127204024p:plain

そして、この sentimentAnalysisExample メソッドを呼び出す部分を追加します。追加する場所は authenticateClient メソッドを呼び出している下に追加してみます。

// センチメント分析の実行
sentimentAnalysisExample(client);

f:id:nomunomu0504:20210127204408p:plain

ここまで出来たら、ローカルで実行してみます。ローカルで実行して適当にBotに喋りかけてみます。喋りかけたらVisual Studioに戻り、アプリケーションの出力結果を見てみます。大体がVisual Studioを画面下部の方にあると思います。

f:id:nomunomu0504:20210127205048p:plain

その中で「Sentiment Score: 0.87」とあります。これが表示されていれば、無事Azureのセンチメント分析を実行することができています。

4. 送られてきたメッセージを分析させよう

f:id:nomunomu0504:20210127205545p:plain

次は実際にユーザーから送られてきたメッセージをセンチメント分析してみます。ここですることは大きく3つです。

4.1. sentimentAnalysisExampleメソッドを修正してメッセージを渡せるようにする。
4.2. メッセージをセンチメント分析させて結果をログに表示させてみる。
4.3. 分析の結果に応じてBotの反応を変えてみる。

4.1. sentimentAnalysisExampleメソッドを修正する

今の sentimentAnalysisExampleメソッドは client という変数しか受け取ることができません。下のコードの ITextAnalyticsClient client という部分が1つの引数となっていて、これが呼び出している側の client と紐付いています。

// Azureのセンチメント分析を実行するメソッド
static void sentimentAnalysisExample(ITextAnalyticsClient client)

// 呼び出しているところ
// センチメント分析の実行
sentimentAnalysisExample(client);

なので、sentimentAnalysisExampleメソッドの引数に、メッセージを受け取れるように引数をもう1つ追加します。

// 修正前
static void sentimentAnalysisExample(ITextAnalyticsClient client)

// 修正後
static void sentimentAnalysisExample(ITextAnalyticsClient client, string message)

さらに、3. 分析の結果に応じてBotの反応を変えてみる。 で分析した結果を利用してBotの反応を変える処理を実装します。その準備として、このメソッドが結果を返せるように修正をします。現時点でこの修正をするとエラーになると思いますが、次の修正で解決されます。

// 修正前
static void sentimentAnalysisExample(ITextAnalyticsClient client, string message)

// 修正後
static double sentimentAnalysisExample(ITextAnalyticsClient client, string message)

そして、メッセージが受け取れるようになったところで、受け取ったメッセージでセンチメント分析を行うようにします。今回の場合はメッセージは日本語という設定なので enja とします。

// 修正前
var result = client.Sentiment("I had the best day of my life.", "en");
Debug.WriteLine("----------------------------------------------");
Debug.WriteLine($"Sentiment Score: {result.Score:0.00}");
Debug.WriteLine("----------------------------------------------");

// 修正後
var result = client.Sentiment(message, "ja");
Debug.WriteLine("----------------------------------------------");
Debug.WriteLine($"User Message: {message}");
Debug.WriteLine($"Sentiment Score: {result.Score:0.00}");
Debug.WriteLine("----------------------------------------------");
return (double)result.Score;

4.2. メッセージをセンチメント分析させて結果をログに表示させてみる。

sentimentAnalysisExampleメソッドの修正が終わったら、そのメソッドを呼び出しているところでもメッセージを渡すように変更する必要があります。ユーザーから送られてきたメッセージは turnContext.Activity.Text で取得することができます。 また、4.1. の修正でセンチメント分析の結果を返すようにしたので、その結果も受け取るようにしましょう。

// 修正前
// センチメント分析の実行
sentimentAnalysisExample(client);

// 修正後
// センチメント分析の実行
var score = sentimentAnalysisExample(client, turnContext.Activity.Text);

ここまでの修正で、コードはこのようになっていると思います。初級・中級のコードは省いて掲載しています。

・OnMessageActivityAsync

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    // authenticateClientを呼び出す部分
    var client = authenticateClient();
    // センチメント分析の実行
    var score = sentimentAnalysisExample(client, turnContext.Activity.Text);

    var replyText = $"オウム: {turnContext.Activity.Text}";
    await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
}

・sentimentAnalysisExample

// Azureのセンチメント分析を実行するメソッド
static double sentimentAnalysisExample(ITextAnalyticsClient client, string message)
{
    var result = client.Sentiment(message, "ja");
    Debug.WriteLine("----------------------------------------------");
    Debug.WriteLine($"User Message: {message}");
    Debug.WriteLine($"Sentiment Score: {result.Score:0.00}");
    Debug.WriteLine("----------------------------------------------");

    return (double)result.Score;
}

ここまで出来たら、ローカルでBotを起動して実際にメッセージをセンチメント分析にかけてみます。今回は動画と同様「肩こりがしんどい」というメッセージを送信してみます。

f:id:nomunomu0504:20210127220014p:plain

そして、送信したら再度Visual Studioのアプリケーション出力部分を確認してみます。すると、送信したメッセージとそのメッセージを分析した結果(数値)が表示されていると思います。

f:id:nomunomu0504:20210127220219p:plain

これで、受け取ったメッセージで分析できていることが分かります。いろいろなメッセージを送ってみるのもいいですね!

4.3. 分析の結果に応じてBotの反応を変えてみる。

さて、上級クエストの終わりが近づいて来ました。最後はこの分析した結果(数値)を用いて、Botが送るメッセージに変化を持たせたいと思います!!

4.2. で分析の結果を取得できるように変更をしました。この結果(数値)を用いてBotからの返信を変えてみます。

この結果(数値)は以下のような対応になっています。

f:id:nomunomu0504:20210127221444p:plain

なので、今回は 「0以上0.5以下」 ならばネガティブと見なして「いたわる言葉」を「0.5より大きく1以下」ならばポジティブと見なして「喜びの言葉」を返すようにしたいと思います。

判定の方法はこんな感じ

// センチメント分析の結果を判定
if (0.5 <= score)
{
    // ポジティブ
}
else
{
    // ネガティブ
}

これを OnMessageActivityAsync に組み込みます。前回の修正と合わせて実装すると以下のように修正できます。

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    // 返信する言葉を格納する変数を定義
    var replyText = "";

    // authenticateClientを呼び出す部分
    var client = authenticateClient();

    // センチメント分析の実行して結果を取得
    var score = sentimentAnalysisExample(client, turnContext.Activity.Text);

    // センチメント分析の結果を判定
    if (0.5 <= score)
    {
        // ポジティブ
        replyText = "やったね!すばらしい!";
    }
    else
    {
        // ネガティブ
        replyText = "おつかれ!負けないで!";
    }

    // 返信を送る
    await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
}

f:id:nomunomu0504:20210127223018p:plain

修正したら、ローカルで実際に動かしてみます。送る文章は動画と同じで「肩こりがしんどい」と「昨日友達とたくさんゲームをしました。楽しかったです。」とします。

f:id:nomunomu0504:20210127223214p:plain

ちゃんと送られてきています。そしてVisual Studioのログも確認して、本当に数値通りに送られてきているか確認してみましょう。

f:id:nomunomu0504:20210127223301p:plain

f:id:nomunomu0504:20210127223313p:plain

それぞれで、ネガティブ、ポジティブ判定として送られてきていることが分かります。これで上級クエストクリアです🎉🎉🎉

他にも様々なAIがあるので、他のAIと組み合わせて見ると面白いと思うので、ぜひ挑戦してみてください!!