Amazon ComprehendとOpenSearchで感情分析結果をグラフ化してみた

    Amazon ComprehendとOpenSearchで感情分析結果をグラフ化してみた

    目次

      はじめに

      クラウドインテグレーション部の池口です。

      本記事ではAmazon ComprehendとAmazon OpenSearch Serviceを使用して、感情分析結果をグラフ化する方法について記載します。
      前回のブログで、S3バケット内のテキストファイルに対して実行する方法を紹介していますので、
      まずはAmazon Comprehendで飲食店の口コミを感情分析をご確認いただけるとイメージが付きやすいと思います。

      Amazon Comprehend とは

      Amazon Comprehend は、AWSが提供する自然言語処理(NLP)サービスで、テキストデータから意味や関係性を自動的に抽出・解析することを可能にするツールです。
      このサービスは、機械学習モデルを使用して、ユーザーが提供するテキストの中から「感情分析」「エンティティ認識」「トピックモデリング」等を行うことが可能です。

      詳細はAmazon Comprehend の特徴をご確認ください。

      感情分析 とは

      感情分析(Sentiment Analysis)は、テキストデータからその感情的な傾向を自動的に識別する機能です。
      顧客フィードバックやレビュー等を分析し、ビジネスインサイトを得ることができます。

      詳細は感情分析とは何ですか?をご確認ください。

      Amazon OpenSearch Service とは

      Amazon OpenSearch Serviceは、AWSクラウドでの OpenSearch クラスターのデプロイ、運用、スケーリングを容易にするマネージドサービスです。

      OpenSearch は、ログ分析、リアルタイムアプリケーションモニタリング、クリックストリーム分析などのユースケース向けの、完全にオープンソースの検索および分析エンジンです。

      詳細はAmazon OpenSearch Service とはをご確認ください。

      検証

      今回の検証では前回同様、架空の飲食店「アジアンレストランAQ」の口コミをAmazon Comprehend(以降Comprehend)にて感情分析を行います。
      加えて、感情分析結果をAmazon OpenSearch Service(以降OpenSearch)にてグラフ化することを目的とします。

      シチュエーション

      計4件の口コミの内3件は良い評価、1件は悪い評価の口コミとして、
      以下の4つの文章をそれぞれテキストファイルでS3にアップロードします。
      ※テキストは前回と同様です。

      ■レビュー1
      先日「アジアンレストランAQ」に初めて訪れました。
      メニューはどれも本格的で、特にトムヤムクンとパッタイが絶品でした!
      ピリッと辛いけれど、深みのあるスープに感動。
      スタッフも親切で、おすすめメニューを丁寧に教えてくれました。
      アジアの雰囲気に浸りながら食事ができて、大満足です。また絶対に来たいです。

      ■レビュー2
      アジアンレストランAQは、インドネシア、タイ、ベトナムなど、様々な国の料理が楽しめるのが魅力です。
      家族で行きましたが、辛さの調整もしてくれて子供も喜んで食べていました。
      店内のインテリアもアジアンテイストでおしゃれで、まるで旅行に来た気分に。
      デザートのマンゴースティッキーライスもとても美味しかったです!

      ■レビュー3
      友達とランチで利用しました。
      セットメニューがとても充実していて、値段以上の満足感です。
      グリーンカレーを注文したのですが、程よい辛さとココナッツミルクの甘みが絶妙でした。
      スタッフのサービスも気持ちよく、料理が出てくるスピードも速くてびっくり。
      また次回は夜のディナータイムに行って、他のメニューも試してみたいです。

      ■レビュー4
      友人の勧めで「アジアンレストランAQ」に行きましたが、正直がっかりしました。
      料理は全体的に塩辛すぎて、本来のアジア料理の繊細な風味が感じられませんでした。
      特にグリーンカレーは辛さが強すぎて、味のバランスが崩れているように感じました。
      また、週末で混雑していたのは理解できますが、料理が出てくるまでかなり時間がかかり、スタッフも忙しそうでサービスも行き届いていませんでした。
      期待していただけに残念です。

      S3内のinputフォルダにテキストファイルがアップロードされるとLambda関数が呼び出されます。
      その後、Comprehendを使用して感情分析を行い、OpenSearchに分析結果が連携されます。


      構成図

      検証の構成図および処理フローは以下になります。
      ① S3へテキストファイルをアップロード
      ② S3へのアップロードがトリガーとなり、Lambdaが起動
      ③ アップロードされたファイルをComprehendで感情分析
      ④ 感情分析結果をLambdaで整形
      ⑤ 整形した分析結果をOpenSearchに送信202411_visualizing-sentiment-analysis_01

      サービス設定

      S3

      バケット名は「ikeguchi-test-bucket-01」を使用します。
      inputフォルダを作成し、テキストファイルがアップロードされた場合にLambdaに通知するイベント通知を設定します。
      ※記載がない項目はデフォルトです。

        • イベント名:sentiment-analysis-s3-to-lambda(任意)
        • プレフィックス:input/
        • イベントタイプ:PUT
        • 送信先:Lambda 関数

      Amazon OpenSearch Service

      続いて今回の検証で行ったOpenSearchの設定を紹介します。
      ※記載がない項目はデフォルトです。

        • ドメイン名:sentiment-analysis-test(任意)

        • ドメインの作成方法:標準作成

        • テンプレート:開発/テスト

        • デプロイオプション:スタンバイが無効のドメイン

        • アベイラビリティゾーン:1-AZ

        • バージョン:2.15(最新)

        • インスタイスタイプ:t3.small.search

        • ノード数:1

        • ストレージタイプ:EBS

        • EBS ボリュームタイプ:汎用(SSD) - gp3

        • ノードあたりの EBS ストレージサイズ:10

        • ネットワーク:パブリックアクセス

        • IP address type:IPv4のみ

        • きめ細かなアクセスコントロールを有効:有効

          • マスターユーザータイプ:マスターユーザーの作成
            ※任意のユーザー名/パスワードを設定します。
        • Artificial Intelligence (AI) and Machine Learning (ML) :無効

        • ドメインアクセスポリシー:ドメインレベルのアクセスポリシーの設定

      {
      "Version": "2012-10-17",
      "Statement": [
      {
      "Effect": "Allow",
      "Principal": {
      "AWS": "arn:aws:iam::<アカウントID>:role/ikeguchi-sentiment-analysis-test-role"
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:ap-northeast-1:<アカウントID>:domain/sentiment-analysis-test/*"
      },
      {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "es:ESHttp*",
      "Resource": "arn:aws:es:ap-northeast-1:<アカウントID>:domain/sentiment-analysis-test/*",
      "Condition": {
      "IpAddress": {
      "aws:SourceIp": "<ソースIPアドレス>/32"
      }
      }
      }
      ]
      }

      上記のアクセスポリシーの概要は以下になります。

        • Lambdaに付与するIAMロールに対してOpenSearchの全操作を許可
        • OpenSearchにhttp通信が可能なソースIPアドレスを制限
          ※ソースIPアドレスは、後述のOpenSearch Dashboardにログインする際の接続元IPアドレスを指定してください。

      OpenSearchのデプロイには30分程掛かるため、特にエラーが無ければ気長に待ちましょう。

      Lambda

      続いてLambdaを設定します。
      S3のinputフォルダにアップロードされたファイルに対して、
      Comprehendで感情分析を行い、結果をOpenSearchに送信します。

      また、実行完了までに5秒程の時間を要するため、
      Lambdaのデフォルトのタイムアウト値「3秒」だとタイムアウトになってしまいました。
      今回の検証ではタイムアウト値を「1分」に変更しています。

      コードの内容は以下になります。
      OpenSearchのドメインエンドポイント名はLambdaの環境変数で指定しています。

      import json
      import boto3
      import os
      import requests
      from requests_aws4auth import AWS4Auth

      # クライアントの初期化
      s3_client = boto3.client('s3')
      comprehend_client = boto3.client('comprehend')

      def lambda_handler(event, context):
      # 環境変数から OpenSearch のエンドポイントを取得
      opensearch_endpoint = os.environ.get(
      'OPENSEARCH_ENDPOINT'
      )
      index_name = "comprehend-results"

      # AWSの認証情報とリージョンを設定
      session = boto3.Session()
      credentials = session.get_credentials()
      region = 'ap-northeast-1'
      auth = AWS4Auth(
      credentials.access_key,
      credentials.secret_key,
      region,
      'es',
      session_token=credentials.token
      )

      # S3イベントからバケット名とオブジェクトキーを取得
      bucket_name = event['Records'][0]['s3']['bucket']['name']
      prefix = 'input/' # フォルダ名
      object_key = event['Records'][0]['s3']['object']['key']

      # inputフォルダ内の特定のファイルのみ処理する
      if not object_key.startswith(prefix):
      print("File is not in the input folder. Skipping.")
      return

      # ファイルの内容を S3 から取得
      try:
      file_content = s3_client.get_object(Bucket=bucket_name, Key=object_key)['Body'].read().decode('utf-8')
      except Exception as e:
      print(f"Error fetching file from S3: {e}")
      return

      # Comprehend で感情分析を実行
      try:
      response = comprehend_client.detect_sentiment(Text=file_content, LanguageCode='ja')
      sentiment = response['Sentiment']
      except Exception as e:
      print(f"Error with Comprehend: {e}")
      return

      # OpenSearch に送信するドキュメントの作成
      document = {
      "file_name": object_key,
      "text": file_content,
      "sentiment": sentiment
      }

      # OpenSearch にデータを送信
      try:
      headers = {'Content-Type': 'application/json'}
      response = requests.post(
      f"{opensearch_endpoint}/{index_name}/_doc",
      headers=headers,
      data=json.dumps(document),
      auth=auth,
      timeout=10 # タイムアウト設定
      )

      if response.status_code == 201:
      print(f"Document indexed successfully for file: {object_key}")
      else:
      print(f"Error indexing document: {response.status_code}, {response.text}")

      except requests.exceptions.RequestException as e:
      print(f"Error sending data to OpenSearch: {e}")

      return {
      'statusCode': 200,
      'body': json.dumps('File processed successfully!')
      }

      また、IAMロールで以下の権限を付与します。

        • S3のinputフォルダに対してのList/Get
        • OpenSearchに対してのPost/Put/Get
        • CloudWatch Logsに対してロググループ作成/ログストリーム作成/ログ送信
        • Comprehendに対して感情分析を実行

      ※CloudWatch Logsに対する権限はトラブルシューティングに役立つため推奨ですが、必須ではありません。

      今回の検証でロールに設定したポリシーは以下になります。

      {
      "Version": "2012-10-17",
      "Statement": [
      {
      "Effect": "Allow",
      "Action": [
      "s3:GetObject",
      "s3:ListBucket"
      ],
      "Resource": [
      "arn:aws:s3:::ikeguchi-test-bucket-01",
      "arn:aws:s3:::ikeguchi-test-bucket-01/input/*"
      ]
      },
      {
      "Effect": "Allow",
      "Action": [
      "es:ESHttpPost",
      "es:ESHttpPut",
      "es:ESHttpGet"
      ],
      "Resource": "arn:aws:es:ap-northeast-1:<アカウントID>:domain/sentiment-analysis-test/*"
      },
      {
      "Effect": "Allow",
      "Action": [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents"
      ],
      "Resource": "*"
      },
      {
      "Effect": "Allow",
      "Action": [
      "comprehend:DetectSentiment"
      ],
      "Resource": "*"
      }
      ]
      }

      OpenSearch Dashboard

      最後にOpenSearch DashboardでOpenSearchのロールとIAMロールをマッピングの設定を行います。
      AWSコンソールにてOpenSearchのデプロイが完了後、ブラウザでOpenSearch Dashboardにアクセスし、作成したマスターユーザーでログインします。
      以下のログイン画面が表示されなければ、OpenSearchのアクセスポリシーが誤っている可能性が高いです。202411_visualizing-sentiment-analysis_02

      ログイン後、ロールの画面に遷移します。

        • 画面左上の[≡] -> [Security] -> [Role] -> 右上の[Create Role]をクリックします。202411_visualizing-sentiment-analysis_03

      設定内容は以下になります。
      ※記載がない項目はデフォルトです。
      ※各項目必要に応じて権限追加や制限を設けてください。

        • Name:sentiment-index-role(任意)
        • Cluster permissions:cluster_composite_ops、cluster_monitor
        • Index:*
        • Index permissions:indices_all
        • Tenant:global_tenant

      各項目を設定後[Crete]をクリックします。202411_visualizing-sentiment-analysis_04

      続いて、作成したロールのタブより[Mapped users] -> [Manage mapping]をクリックします。202411_visualizing-sentiment-analysis_05
      設定内容は以下になります。

        • Backend roles:<Lambdaに設定したIAMロールのARN>
          ※Usersは空欄のままで問題ありません。

      以上で各サービスの設定が完了です。

       

      実行確認

      S3のinputフォルダに感情分析を行いたいテキストファイルをアップロードします。202411_visualizing-sentiment-analysis_06
      OpenSearch Dashboardにログインし[≡] -> [Index Management] -> [Indexes] -> [Indexes]をクリックし、
      Lambdaのコード内で定義した「comprehend-results」が表示されていることを確認します。
      ※「.(ドット)」から始まるIndexはデフォルトで作成されます。202411_visualizing-sentiment-analysis_07

      「comprehend-results」の以下の内容が確認できれば正常にデータが取り込まれていると判断できます。

      • 「Total size」が0kbではないこと
      • 「Total Documents」が4であること
        アップロードしたテキストファイルのサイズ/数によって上記の値は異なります。

      202411_visualizing-sentiment-analysis_08

       

      グラフ化

      続いて、取り込んだデータをグラフ化します。今回は棒グラフを作成します。
      [≡] -> [Visualize] -> [Create new visualization]をクリックします。202411_visualizing-sentiment-analysis_09

      一覧の中から[Vertical Bar]をクリックします。202411_visualizing-sentiment-analysis_10

      ソースとして先程取り込んだ「comprehend-results」をクリックします。202411_visualizing-sentiment-analysis_11

      グラフが作成されます。202411_visualizing-sentiment-analysis_12

      今回は選択したソースから「sentiment」の件数をグラフ化します。
      sentimentとは何か分からない/忘れた方向けに前回の記事のおさらいです。

      項目 説明
      sentiment Amazon Comprehendによる感情分析の結果
      ※分析したテキストが「POSITIVE(ポジティブ)」「NEGATIVE(ネガティブ)」「NEUTRAL(中立)」、あるいは「MIXED(混合)」のどれかに分類されます。

      Comprehendによる感情分析の結果、今回アップロードした4つテキストファイルの感情の件数は以下となります。

      • POSITIVE:3件
      • NEGATIVE:1件
      • NEUTRAL:0件
      • MIXED:0件

      感情分析結果の詳細は前回の記事をご確認ください。

      目的のグラフにするためにカスタマイズを行います。
      縦軸に件数、横軸に感情の種類を表示します。
      グラフ右側の「Data」タブで以下の設定を行います。
      ※記載がない項目はデフォルトです。

      • Metrics
        • Y-axis
          • Aggrigation:Count
      • Buckets
        • X-axis
          • Aggrigation:Terms
            • Field:sentiment_keyword
            • Order:Descending
              • Size:4

      上記設定後、画面右下の[Update]をクリックします。202411_visualizing-sentiment-analysis_13

      グラフが更新され、POSITIVEが3件、NEGATIVEが1件というグラフが表示されました。202411_visualizing-sentiment-analysis_14NEUTRALとMIXEDは今回の感情分析結果に含まれていないため、現時点ではグラフには表示されません。

      このようにして、S3にアップロードされたテキストファイルがComprehendで感情分析され、
      OpenSearchでグラフ化できることが確認できました。

      まとめ

      最近よく見聞きする生成AIの他にも、複数のAIの種類が存在します。
      今回は分析AIとしてAmazon Comprehend+OpenSearchを使用して感情分析結果のグラフ化を行いました。
      どちらも業務では使用したことはありませんでしたが、普段の業務とは異なる分野の検証だったため新鮮な気持ちで検証/執筆することができました。

      今後、AIや自然言語処理技術はさらに進化し、市場も拡大されると個人的に予想しています。
      次回のブログでは、他のAIサービスを検証して見ようと思いますので、次回もお楽しみに!

      アジアクエスト株式会社では一緒に働いていただける方を募集しています。
      興味のある方は以下のURLを御覧ください。