やさしいチャットボットのつくりかた -3.Cognitive Serviceでテキスト分析をしてみよう！

#どういう記事？
Azure素人の自分がMicrosoft AzureのBot FrameworkとCognitive Serviceと触れ合いながら、難易度も人への接し方も**“やさしい”**チャットボットを作ってみた際の備忘録の第3部です。どんなチャットボットを作るのかを含め、これまでの手順は[第1部](https://qiita.com/miokawa/private/6f1a3e480580e1e29abc)、[第2部](https://qiita.com/miokawa/private/c71f9f4b62ce2b1c2864)にございます。
PowerPointで記録していたものをQiita化してみたものになるので、そんな感じの画像が頻出します。
Qiita初心者なので、書き方が下手でも広い心で許してほしいです。そして全体的にゆるいです。

# アジェンダ
以下の5部構成になります。この記事は第3部にあたります。

1. [Bot Frameworkでチャットボットを作ってみよう！](https://qiita.com/miokawa/private/6f1a3e480580e1e29abc)
2. [ボットをAzure（クラウド）にデプロイしてみよう！](https://qiita.com/miokawa/private/c71f9f4b62ce2b1c2864)
3. [Cognitive Serviceでテキスト分析をしてみよう！](https://qiita.com/miokawa/private/cdb243401fe3c8d9d583)**←イマココ**
4. [Twitterからつぶやきをとってきてみよう！](https://qiita.com/miokawa/private/eb9f16220a36f65d7098)
5. [LINE経由でボットと話してみよう！](https://qiita.com/miokawa/private/c6f0b550f8fa41a23228)

なお、公式ドキュメントに沿って作りましたので、それらのリンクを張り付けつつ、辿った道筋通りに書いていきます。
その筋の方からすると遠回りだったり野暮ったかったりすると思いますが、ご容赦ください。

# 2. Cognitive Serviceでテキスト分析をしてみよう！
これまで、[第1部](https://qiita.com/miokawa/private/6f1a3e480580e1e29abc)で作ったソースコードを、[第2部](https://qiita.com/miokawa/private/c71f9f4b62ce2b1c2864)でAzure（クラウド）上へデプロイしましたので、今回はユーザーから受け取ったコメントを[Azure Cognitive Service](![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/d3b1948f-f50d-6e3a-9f15-8a4a21856cc1.png)
)の[Text Analytics](https://azure.microsoft.com/ja-jp/services/cognitive-services/text-analytics/)を使って、ネガティブorポジティブの判定をしてみたいと思います（図中の赤枠部分）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/7c37f070-56f2-2374-0342-4156536a7a03.png)




## 2.1 参考ドキュメント
##### クイック スタート:Text Analytics クライアント ライブラリを使用する
今回の作業は公式ドキュメントの[クイック スタート:Text Analytics クライアント ライブラリを使用する](https://docs.microsoft.com/ja-jp/azure/cognitive-services/text-analytics/quickstarts/text-analytics-sdk?tabs=version-2&pivots=programming-language-csharp#install-the-nuget-sdk-package)を参考にしています。
なお、最新のプレビュー バージョンは 3.0-previewとのことですが、今回は安定版である2.1を使ってみました。

## 2.2 前提条件
まずは上記リンク内の「前提条件」の部分に従って、以下を準備します。

- Microsoft Azure のサブスクリプション
- Visual Studio IDE
- Text Analytics リソースの作成（キーとエンドポイントを取得）

#### Microsoft Azure のサブスクリプションの準備
[第2部](https://qiita.com/miokawa/private/c71f9f4b62ce2b1c2864)で準備済みかと思いますので、それを使います。

#### Visual Studio IDE
[第1部](https://qiita.com/miokawa/private/6f1a3e480580e1e29abc)で使ったVisual Studio 2019を使います。

#### Text Analytics リソースの作成（キーとエンドポイントを取得）
[Azure portal を使用して Cognitive Services リソースを作成する](https://docs.microsoft.com/ja-jp/azure/cognitive-services/cognitive-services-apis-create-account?tabs=multiservice%2Cwindows)のクイックスタートに沿って、Text Analyticsリソースを作成します。こちらは割とつまずくところもなく、公式ドキュメントそのままで作成可能なので、そちらを参照いただいても大丈夫かと思います。

##### 新しい Azure Cognitive Services リソースを作成する
リンク先の**新しい Azure Cognitive Services リソースを作成する**のところで、**単一サービスリソース**のタブから、**Text Analytics**を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/f0e3a4c8-63ce-a67b-9966-58a2a23175ed.png)

すると、AzurePortalへ自動で飛ばされ、TextAnalyticsリソース作成の画面に移りますので、それぞれ値を設定していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/11c2a779-afc4-0d4e-2a87-543732deddee.png)

+ **名前**<br>
TextAnalyticsリソースの名前です。なんでもよいです。今回は例として、YasashiiBotTextAnalyticsとしています。
+ **サブスクリプション**<br>
TextAnalyticsリソースをどのAzureサブスクリプションで作成するかということです。適切なものを選択してください。
+ **場所**<br>
TextAnalyticsリソースをどのリージョンに配置するかということです。今回は`東日本`を選択します。
+ **価格レベル**<br>
価格レベルを選択します。今回はテストなので、一番下のレベル`F0`を選択します。
+ **リソースグループ**<br>
TextAnalyticsリソースをどのリソースグループに作成するかということです。今回は[第2部](https://qiita.com/miokawa/private/c71f9f4b62ce2b1c2864)で作成したリソースグループを選択しました。

全ての項目で設定ができたら、左下の**作成**ボタンをクリックします。

##### リソースのキーを取得する
少し待つと、デプロイが完了しました、という画面になりますので、**リソースに移動**ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/cdfd75b3-07c0-ce50-e42f-a7125abfb8db.png)

APIキーとエンドポイントが表示されます。後程使いますので、コピーしておいてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/5cae189c-1495-4d9b-0f97-0a1cd88f8161.png)


## 2.3 Text Analyticsの設定

<font color="red">**ここから先、コードを書いていきますが、筆者はコーディング力は皆無（とりあえず動くものを作るだけ）なので、清く正しく美しいコードを書けるようになりたい方は、参考にしすぎないようにしてください。ごめんなさい。**</font>

公式ドキュメントだと、新しいアプリを作成するところからですが、[第1部](https://qiita.com/miokawa/private/6f1a3e480580e1e29abc)で作成済みなので、そちらを使っていきます。プロジェクトを開いて、**ソリューションエクスプローラー**から**ソリューション名**を右クリックし、**NuGetパッケージの管理**を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/dc16a3fd-0ebc-60d0-055b-6022d9f60823.png)

NuGetパッケージマネージャーが開いたら、`Microsoft.Azure.CognitiveServices.Language.TextAnalytics`と検索し、パッケージをインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/dda7f39d-c931-e606-3255-c91504528ab9.png)


公式ドキュメントではprogram.csへの追加の手順が記載されていますが、今回はEchoBot.csに全てまとめてしまおうと思うので、EchoBot.csの上の方（usingほげほげが書いてあるところ）に以下の通り追加します。これは、公式ドキュメントに書いてあるものと、EchoBot.csに元々書いてあったものの差分です。

```cs:EchoBot.cs
using System;
using System.Net.Http;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics.Models;
using Microsoft.Rest;
```

追加するとこんな感じになります。わかりやすいように、今回TextAnalytics連携のために追加したコードの上には、`//for TextAalytics`と書いてあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/0a4e4a11-4cec-05c6-6e1b-984cd0656871.png)

次に、`public class EchoBot`に、先ほど作成したTextAnalyticsのAPIキーとエンドポイント（[リソースのキーを取得する](#リソースのキーを取得する)で表示されたもの）を以下の通り追加します。

```cs:EchoBot.cs
private static readonly string key = "<replace-with-your-text-analytics-key-here>";
private static readonly string endpoint = "<replace-with-your-text-analytics-endpoint-here>";
```
追加すると、こんな感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/95a273fa-b51e-66ab-3b87-98631d1a5505.png)

さて、いよいよ、TextAnalyticsとの連携部分を作っていきます。公式ドキュメントでは新しく.NET Core コンソールアプリを作った例になっているため、Mainメソッド（アプリを実行したらまず開始される部分）を書き換えるように書いてありますが、今回の場合はユーザーが話しかけてきたら発動するようにしたいので、以下の2行を`OnMessageActivityAsync`のタスク内に追加します。

```cs:EchoBot.cs
var client = authenticateClient();
sentimentAnalysisExample(client);
```

各メソッドの処理内容としては以下の通りです。

+ **authenticateClient()**
[新しい Azure Cognitive Services リソースを作成する](#新しい Azure Cognitive Services リソースを作成する)で作成したTextAnalyticsリソースを、このボットアプリから利用するための認証をする。

+ **sentimentAnalysisExample(client)**
authenticateClient()で認証されたTextAnalyticsリソースを使って、任意のメッセージの感情分析をして、スコアを得る。

追加するとこんな感じになります。この状態だと記載したメソッドの本体が存在しないため、赤い波線が出てエラーになってしまっていますが、一旦無視してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/2128d850-fe94-c194-53c3-01e1e161f80c.png)

## 2.4 クライアントを認証する
このボットアプリから、先ほど作ったTextAnalyticsリソースを使えるように認証をするため、authenticateClient()の中身を作っていきます。以下のクラスを追加します。クラスなので、`public class EchoBot`と同じレベル（クラス名の左側の深さを合わせる）で記載します。

```cs:EchoBot.cs
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);
    }
}
```

追加するとこんな感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/cd2b775c-432f-004b-f09b-c59d88502058.png)


次に、先ほどエラーになっていた`authenticateClient`を呼び出せるようにしていきます。`public class EchoBot`に戻って、以下を追加します。既に`public class EchoBot`内にある、他のタスクと同じレベル（深さ）に記載します。

```cs:EchoBot.cs
static TextAnalyticsClient authenticateClient()
{
    ApiKeyServiceClientCredentials credentials = new ApiKeyServiceClientCredentials(key);
    TextAnalyticsClient client = new TextAnalyticsClient(credentials)
    {
        Endpoint = endpoint
    };
    return client;
}
```

追加するとこんな感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/55a990f0-e90a-2ad6-eccb-466a681d5b87.png)


## 2.5 センチメント分析
次に、もう一つエラーになっていた、SentimentAnalysisExample() というメソッドを作成します。ここで、感情分析をするテキストの指定と、その結果の受け取りをします。以下のコードを`public class EchoBot`に追加します。こちらも、既に存在している他のタスクと同じレベル（深さ）に記載します。

```cs:EchoBot.cs
static void sentimentAnalysisExample(ITextAnalyticsClient client)
{
    var result = client.Sentiment("I had the best day of my life.", "en");
    Debug.WriteLine($"Sentiment Score: {result.Score:0.00}");
}
```
ここでは、サンプルとして、「I had the best day of my life.」という英語の文章の感情分析をしてみています。

追加するとこんな感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/6c6ff01e-4f0f-edc9-f828-d5a0e624de91.png)

ここで、**Debug.WhiteLine**の部分にエラーが出ていることに気づくかと思います。これは、拡張機能が不足している（上の方でusingしているものが、Debug.WhiteLineを使うのには足りていない）ために起きています。Debug部分にカーソルを合わせて、**考えられる修正内容を表示する**を選択し、`using System.Diagnostics`の追加の提案を受け入れてください。すると自動的にソースコード上部に`using System.Diagnostics`が追加され、エラーも消えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/6f5f51f2-a0b6-ed9f-5b83-843ab964be14.png)

さて、この状態で、一応ボットアプリ→TextAnalyticsリソースの利用はできるようになっていますので、一旦**▶IIS Express**からローカルで実行し、Bot Framework Emulatorでボットに話しかけてみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/442096ef-1308-48c0-b239-fba9428cfd13.png)
こんな感じで、出力画面にSentiment Scoreが出てきたら成功です。現時点では、ボットに話しかけたメッセージの分析ではなく、ソースコードに直接書いた「I had the best day of my life.」の分析結果ではありますが、きちんと値が返ってくれば、ボットアプリからTextAnalyticsリソースを利用し、分析結果を得ることができていることの確認ができます。

## 2.6 ユーザーのメッセージをTextAnalyticsで分析する
次に、ユーザーのメッセージをTextAnalyticsで分析するように、ソースコードを変更します。ユーザーのメッセージが入っているのは`OnMessageActivityAsync`内の`turnContext.Activity.Text`の部分ですので、これをSentimentAnalysisExampleメソッドに渡すように、コードを変更します。具体的には、sentimentAnalysisExampleの引数に、`turnContext.Activity.Text`を追加します。

```cs:EchoBot.cs
sentimentAnalysisExample(client,turnContext.Activity.Text);//引数turnContext.Activity.Textを追加
```

このような感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/32fd433b-84bb-9404-6320-dcf4ab1232b3.png)

次に、受け取るsentimentAnalysisExample側も変更が必要ですので、以下の通り変更します。

```cs:EchoBot.cs
static void sentimentAnalysisExample(ITextAnalyticsClient client, string message)//引数messageを追加
{
   var result = client.Sentiment(message, "ja");//引数messageを分析対象にし、言語は日本語に設定
   Debug.WriteLine($"User Message: {message}");//引数messageの内容を出力
   Debug.WriteLine($"Sentiment Score: {result.Score:0.00}");
}
```

このような感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/b4fbb3ec-9fe2-ea99-da16-81ee490afc79.png)

ここまで変更したら、またアプリを実行して、ボットに話しかけてみましょう。今度は日本語で、意味のある言葉を投げかけてみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596292/178d389f-48ee-a3ec-9211-c1c9c24b4ee2.png)

このように、話しかけた内容と、分析結果のスコアが返ってきていれば成功です！

## 2.7 現時点でのEchoBot.cs
現時点でのEchoBot.csのソースコードはこのような状態になります。

```cs:EchoBot.cs
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.6.2

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;

//for TextAnalytics
using System;
using System.Net.Http;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics.Models;
using Microsoft.Rest;
using System.Diagnostics;

namespace YasashiiBot.Bots
{
    public class EchoBot : ActivityHandler
    {
        //for TextAnalytics
        private static readonly string key = "<replace-with-your-text-analytics-key-here>";
        private static readonly string endpoint = "<replace-with-your-text-analytics-endpoint-here>";

        protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
        {
            var replyText = $"Echo: {turnContext.Activity.Text}";
            await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);

            //for TextAnalytics
            var client = authenticateClient();
            sentimentAnalysisExample(client,turnContext.Activity.Text);//引数turnContext.Activity.Textを追加
        }

        protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
        {
            var welcomeText = "Hello and welcome!";
            foreach (var member in membersAdded)
            {
                if (member.Id != turnContext.Activity.Recipient.Id)
                {
                    await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken);
                }
            }
        }
        
        //for TextAnalytics
        static TextAnalyticsClient authenticateClient()
        {
            ApiKeyServiceClientCredentials credentials = new ApiKeyServiceClientCredentials(key);
            TextAnalyticsClient client = new TextAnalyticsClient(credentials)
            {
                Endpoint = endpoint
            };
            return client;
        }
        
        //for TextAnalytics
        static void sentimentAnalysisExample(ITextAnalyticsClient client, string message)//引数messageを追加
        {
            var result = client.Sentiment(message, "ja");//引数messageを分析対象にし、言語は日本語に設定
            Debug.WriteLine($"User Message: {message}");//引数messageの内容を出力
            Debug.WriteLine($"Sentiment Score: {result.Score:0.00}");
        }
    }

    //for 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);
        }
    }
}
```


# 以降の工程について
以降の工程はこのようになっています。
次回はTwitterと連携して、感情分析の結果がネガティブ判定だった場合、偉人さんのありがたいお言葉を返す部分を作ってみたいと思います。

1. [Bot Frameworkでチャットボットを作ってみよう！](https://qiita.com/miokawa/private/6f1a3e480580e1e29abc)
2. [ボットをAzure（クラウド）にデプロイしてみよう！](https://qiita.com/miokawa/private/c71f9f4b62ce2b1c2864)
3. [Cognitive Serviceでテキスト分析をしてみよう！](https://qiita.com/miokawa/private/cdb243401fe3c8d9d583)**←イマココ**
4. [Twitterからつぶやきをとってきてみよう！](https://qiita.com/miokawa/private/eb9f16220a36f65d7098)
5. [LINE経由でボットと話してみよう！](https://qiita.com/miokawa/private/c6f0b550f8fa41a23228)