2022年版「Cloud Natural Language」活用 第2回｜エンティティ分析の方法と成果物を説明する

# はじめに
ヴェネクト株式会社のディレクター 小峰です。
今回の記事では、”エンティティ分析”と”エンティティ感情分析”を扱います。インプットした文章で言及されているトピックを抽出し、その「重要度」と「感情表現の方向性」と「感情表現の強さ」を取得できます。
エンティティ分析を通して文章中のトピック（何が述べられているか、主題）を取得することで、文章で言及されている内容を取得できます。その結果を利用して弊社内では、大量のレビューに対し「レビュー文章でどのようなトピックが言及されているか」を機械的に集計しています。そのほかにも、文章のカテゴリ分け/ タグ付けに活用したり、言語入力型のインターフェイスで入力内容の識別に利用することができます。
一言でまとめると、「文章中の主題」を集計するための処理になります。
> GCP｜エンティティ分析
> https://cloud.google.com/natural-language/docs/analyzing-entities?hl=ja


# プログラム実行環境
## Pythonの実行環境
今回はPythonを利用します。Versionは3.7を採用します。
先に必要なPackageを書き出すと下記の通りになります。
```Python
# DataFrame変数を処理に活用するため、Importします
import pandas as pd
import numpy as np

# Excelで取得したデータを処理するため、Importします
import xlrd

# 以下はGCPに接続し、APIでやり取りをするため、各種GCP関連のPackageをImportします
from google.cloud import language_v1
import os
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from googleapiclient import discovery
from google.cloud import storage
```

## GCPの「Natural Language API」の利用
また、自然言語処理はGCPの「Natural Language API」を利用します。そのため、GCPのプロジェクトを立ち上げ、課金を有効にし、API利用の認証取得と「Natural Language API」の有効化が必要です。
実際の設定方法ですが、公式のHelpを参考にしてください。
> GCP｜すべてのクイックスタート
> https://cloud.google.com/natural-language/docs/quickstarts?hl=ja
> GCP｜クイックスタート: Natural Language API の設定
> https://cloud.google.com/natural-language/docs/setup?hl=ja


# インプットするデータ
## 取得データの解説
サンプルとして、レビューサイトに記載されている、化粧品の商品レビューを対象にします。レビュー文章だけでなく、レビュー記入者の名前とレビューが記入された日付を取得します。レビューが記載されたExcelをPythonで読み込み、PandasのData Frame変数に変換し、分析を実行します。
![スクリーンショット 2022-04-18 20.10.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/68a58487-8dfe-3dd4-9635-14f99952ee17.png)


## インプットに向けた加工
エンティティ分析はレビュー文1つずつ実行します。そのため、Excelからデータを読み取り、レビューの一覧を格納したData Frame変数を、レビュー1文づつに分解します。成果物を扱いやすくするために、Dict変数を採用します。Keyにレビュー記入者の名前とレビューが記載された日付を設定し、各レビューをユニークに識別できるようにし、Valueにレビュー文を導入します。
下記のScriptを実行し、インプットするDict変数を得ます。
```Python
Reviews = {}
for DF in ListOfReviews:
 for Index, Row in DF.iterrows():
   R = Row["内容"]
   R = R.replace('\n', '')#改行文字\nを削除する
   R = R.replace('\u3000', '')
   I = Index
   Reviews[I] = R
  
Reviews
```


# サンプルコード
## サンプルコード例
説明の前にサンプルコードを記載します。
```Python
def AccmodateEntities(Input, Dict, Threshold):
 #解析対象の言語特性を定義する
 Type = language_v1.Document.Type.PLAIN_TEXT
 Language = "ja"
 Encoding_type = language_v1.EncodingType.UTF8
 
 
 #各Reviewの分析結果を集計するためのList変数を定義する
 ListEntities_PerReviews = []
 
 #各レビューに対し処理を実行する
 for Key, Value in zip(Input.keys(), Input.values()):
   #各レビューのEntityのみを抜き出し格納するためのList変数を定義する
   ListEntities_ForDictionary = []
 
 
   #分析対象のレビューを取得し、UTF-8に変換した上で解析処理を行う
   Text = Value.encode('utf-8')
   Document = {"content":Text , "type_": Type, "language": Language}
   Response = Client.analyze_entity_sentiment(request = {'document': Document, 'encoding_type': Encoding_type})
        
  
   #各Entityの分析結果を集計するためのList変数を定義する
   ListEntities_PerEntities = []
 
  
   # 各レビューのEntityを取得し、併せてSalience, Sentimentも取得する
   for Entity in Response.entities:
     Name = format(Entity.name)
     Salience = float(format(Entity.salience))
     ET = str(format(Entity.type_.name))
     Sentiment = Entity.sentiment
     SentimentScore = format(Sentiment.score)
     SentimentMagnitude = format(Sentiment.magnitude)
 
    
     #取得したEntityを一覧化したList変数を作成する
     #重要度がThreasold以下のEntityを削除する
     if Salience < Threshold:
       None
     else:
       #Entityの判別結果のMentionが取得対象のListに合致しなければ除外する
       if ET in EntityList:
         #Entity登録の重複を避ける
         if Name in ListEntities_ForDictionary:
           None
         else:
           ListEntities_ForDictionary.append(Name)
       else:
         None
    
 
     #Entity感情分析結果をData Frameに格納する処理を行う
     EntityEmotionResult = pd.DataFrame.from_dict({
         "Date":Key[0],
         "ReviewerName":Key[1],
         "BrandName":Key[2],
         "ReputationFromReviewer":Key[3],
         "Age":Key[4],
         "SkinCondition":Key[5],
         "Review":Value,
         "EntityName":Name,
         "EntitySalience":Salience,
         "EntityCategory":ET,
         "EntitySentimentScore":SentimentScore,
         "EntitySentimentMagnitude":SentimentMagnitude
     },
     orient="index").T
 
     #各Entity毎の処理結果をListEntities_PerEntitiesに格納する
     ListEntities_PerEntities.append(EntityEmotionResult)
  
 
   #個別のEntity出力結果を縦に結合し、レビュー毎の集計結果をまとめる
   EntityReview = pd.concat(ListEntities_PerEntities)
 
   #各Review毎の処理結果をListEntities_PerReviewsに格納する
   ListEntities_PerReviews.append(EntityReview)
 
   #各レビュー毎に出現したEntityを格納するためのDictinnary変数を定義し出力結果のLIｓｔに格納させる
   Dict[Key] = ListEntities_ForDictionary
 
 #個別のReview毎の出力結果を縦に結合し、レビュー毎の集計結果をまとめる
 EntityResult = pd.concat(ListEntities_PerReviews)
 
 #最後に各列のデータ型を定義する
 EntityResult = EntityResult.astype({
     "Date":"object",
     "ReviewerName":"object",
     "BrandName":"object",
     "ReputationFromReviewer":"int",
     "Age":"object",
     "SkinCondition":"object",
     "Review":"object",
     "EntityName":"object",
     "EntitySalience":"float128",
     "EntityCategory":"object",
     "EntitySentimentScore":"float128",
     "EntitySentimentMagnitude":"float128"
 })
 
 return Dict, EntityResult

Entities  = {}
Entities, EntityResult = AccmodateEntities(Reviews, Entities, 0)
```

## 解説
上記のスクリプトを実行することで、Keyにレビュー識別用の名前と日付が入力され、Valueに取得したEntityを格納したDict変数が得られます。
Entitiesは、Keyにレビュー情報（レビュー者の名前や日付など）、Valueにエンティティを格納しています。結果、「対象のレビューのエンティティは何か」を格納しています。
EntityResultは、DataFrame変数を採用し、レビュー情報とエンティティ、そのエンティティの特性（SalienceやSentimentなど）を一覧表として格納しています。以降の集計処理で活用します。

最初に「Natural Language API」に送信する言語設定を定義します。今回は日本語（ja）を利用し、文字コードはUTF-8を採用します。
```Python
Type = language_v1.Document.Type.PLAIN_TEXT
Language = "ja"
Encoding_type = language_v1.EncodingType.UTF8
```
 
「Natural Language API」への送信は下記の処理で実行します。エンティティ感情分析を実行することで、通常のエンティティ分析で得られる成果も取得できるため、エンティティ感情分析を採用し処理を行います。
```
Text = Value.encode('utf-8')
Document = {"content":Text , "type_": Type, "language": Language}
Response = Client.analyze_entity_sentiment(request = {'document': Document, 'encoding_type': Encoding_type})
```

結果はJsonで返されます。中身を解説すると下記になります。
まず、第一階層ではentitiesとlanguageが返されます。
```Python
{
  "entities": [
    {
      object (Entity)
    }
  ],
  "language": string
}
```

ここで重要なのはentitiesなので詳しく中身を見ます。
```Python
{
  "name": string,
  "type": enum(Type),
  "metadata": {
    string: string,
    ...
  },
  "salience": number,
  "mentions": [
    {
      object(EntityMention)
    }
  ],
  "sentiment": {
    object(Sentiment)
  }
}
```

各要素について解説します。
- __Name__
    - 取得したエンティティです。
    - 入力した文章のトピックが記載されています。
- __type__
    - エンティティタイプです。（人名や地名、イベントなど、エンティティ種別が出力されます）
    - エンティティの大まかな種類がわかるため、不要なEntityのフィルタリングに利用できます。
    - 下記のリンクより、詳細をご確認ください。
        > GCP｜Entity Type
        > https://cloud.google.com/natural-language/docs/reference/rest/v1/Entity?hl=ja#Type
- __metadata__
    - エンティティに関連付けられるメタデータが記載されます。
    - 英語版であればWikipediaのリンクなどが表示されますが、現時点では日本語は非対応で満足な結果が得られません。そのため無視して構いません。
- __salience__
    - エンティティの重要度です。対象のエンティティ文章の中心に位置し、重要だと判断されれば点数が高くなります。
    - [0-1.0]で表現されます。1ほど重要度が高く、0は重要度が低いです。
- __mentions__
    - エンティティの文章中での表現です。
    - ただし、現時点では日本語は対応していないため、無視して構いません。
- __sentiment__
    - エンティティの感情表現です。
    - その内部はさらに2つの要素を含みます。
        - __magnitude__
            - 感情表現の強さです。感情的な表現がどれだけ出現し、強い表現であったかを数値化した値です。
            - 0から+∞の間の値です。感情表現が強いほど点数が高くなります。
        - __score__
            - 感情表現の方向性です。ポジティブな表現であるか、ネガティブな表現であるかを判断できます。
            - -1から+1の間の値で表現されます。-1に近いほどネガティブであり、+1に近いほどポジティブです。0は中立的な表現です。

__まとめると、”Name”、 "salience”、”magnitude”、”score”の4者を取得すればOKです。__
そのため、下記の処理でJsonから必要な要素を取得できます。
```Python
   for Entity in Response.entities:
     Name = format(Entity.name)
     Salience = float(format(Entity.salience))
     ET = str(format(Entity.type_.name))
     Sentiment = Entity.sentiment
     SentimentScore = format(Sentiment.score)
     SentimentMagnitude = format(Sentiment.magnitude)
```


> GCP｜Method: documents.analyzeEntitySentiment
> https://cloud.google.com/natural-language/docs/reference/rest/v1/documents/analyzeEntitySentiment?hl=ja
> GCP｜Entity
>https://cloud.google.com/natural-language/docs/reference/rest/v1/Entity?hl=ja
>GCP｜Sentiment
>https://cloud.google.com/natural-language/docs/reference/rest/v1/Sentiment?hl=ja


# アウトプット
弊社内では2つの形式で成果物を取得します。
まず、レビュー毎にどのようなトピックが出現しているか、格納したDict変数を取得します。
このDict変数を活用すれば、各レビューで言及されるトピックを機械的に取得できます。
![スクリーンショット 2022-04-18 20.20.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/4f7d8dae-199d-c10c-1677-e09c5425b427.png)


次に各Entityの特性を分析するためのData Frame変数を作成します。
Entity毎にSalienceとSntimentを取得し、レビュー内でどのように言及されているか、機械的に集計した結果の一覧表を得ます。より分析の完成度を高めるために、レビュー内に記載されている年齢や肌質も取得しています。
![スクリーンショット 2022-04-18 20.20.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/558291f9-d2c7-66e4-f755-8688e2a649d6.png)


上記2者を組み合わせて活用することで、ビジネス上の目的に対し回答を得ることが出来ます。

# どのように活用できるか
弊社内では商品毎に言及されているトピックを集計し、 __「各商品の利用者はどのような点に関心があるか/どのような点を評価しているか」__ を商品間で比較することで、マーケティング上で有益な考察を得るために活用しています。
大量のレビューを読むのはマンパワーの負担も大きく、読む人の主観にも左右されやすいですが、__自然言語解析を活用することで、効率的かつ客観的な分析を行うことができます。__

それ以外にも文章中で言及されているトピックを取得できるため、そのトピックを利用して __文章をカテゴリ分け/ タグ付けする__ ことに活用も出来ます。ニュース記事や投稿内容のトピックを判断し、機械的な処理に結びつけることができます。
他にもトピックの抽出とそのポジティブ/ネガティブの判別もできるため、 __言語入力型のインターフェイスのリクエストの識別__ にも活用できるし、顧客対応を目指し、ネガティブな投稿の監視にも役立てられると思います。

# 次回について
全4回の記事を投稿しておりますので、下記リンクから参照ください

- [第1回｜GCPの「Cloud Natural Language」の概要と活用方法](https://qiita.com/venect_qiita/items/1fdf5b8377f79f9a9910)
- [第3回｜感情分析の方法と成果物を説明する](https://qiita.com/venect_qiita/items/6e44848bd2cf3a25c250)
- [第4回｜構文解析の方法と成果物を説明する](https://qiita.com/venect_qiita/items/71409bcef2caac9f2843)