はじめに
ヴェネクト株式会社のディレクター 小峰です。
今回の記事では、”エンティティ分析”と”エンティティ感情分析”を扱います。インプットした文章で言及されているトピックを抽出し、その「重要度」と「感情表現の方向性」と「感情表現の強さ」を取得できます。
エンティティ分析を通して文章中のトピック(何が述べられているか、主題)を取得することで、文章で言及されている内容を取得できます。その結果を利用して弊社内では、大量のレビューに対し「レビュー文章でどのようなトピックが言及されているか」を機械的に集計しています。そのほかにも、文章のカテゴリ分け/ タグ付けに活用したり、言語入力型のインターフェイスで入力内容の識別に利用することができます。
一言でまとめると、「文章中の主題」を集計するための処理になります。
GCP|エンティティ分析
https://cloud.google.com/natural-language/docs/analyzing-entities?hl=ja
プログラム実行環境
Pythonの実行環境
今回はPythonを利用します。Versionは3.7を採用します。
先に必要なPackageを書き出すと下記の通りになります。
# 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変数に変換し、分析を実行します。
インプットに向けた加工
エンティティ分析はレビュー文1つずつ実行します。そのため、Excelからデータを読み取り、レビューの一覧を格納したData Frame変数を、レビュー1文づつに分解します。成果物を扱いやすくするために、Dict変数を採用します。Keyにレビュー記入者の名前とレビューが記載された日付を設定し、各レビューをユニークに識別できるようにし、Valueにレビュー文を導入します。
下記のScriptを実行し、インプットするDict変数を得ます。
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
サンプルコード
サンプルコード例
説明の前にサンプルコードを記載します。
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変数を定義し出力結果のLIstに格納させる
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を採用します。
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が返されます。
{
"entities": [
{
object (Entity)
}
],
"language": string
}
ここで重要なのはentitiesなので詳しく中身を見ます。
{
"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は中立的な表現です。
-
magnitude
まとめると、”Name”、 "salience”、”magnitude”、”score”の4者を取得すればOKです。
そのため、下記の処理でJsonから必要な要素を取得できます。
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変数を活用すれば、各レビューで言及されるトピックを機械的に取得できます。
次に各Entityの特性を分析するためのData Frame変数を作成します。
Entity毎にSalienceとSntimentを取得し、レビュー内でどのように言及されているか、機械的に集計した結果の一覧表を得ます。より分析の完成度を高めるために、レビュー内に記載されている年齢や肌質も取得しています。
上記2者を組み合わせて活用することで、ビジネス上の目的に対し回答を得ることが出来ます。
どのように活用できるか
弊社内では商品毎に言及されているトピックを集計し、 「各商品の利用者はどのような点に関心があるか/どのような点を評価しているか」 を商品間で比較することで、マーケティング上で有益な考察を得るために活用しています。
大量のレビューを読むのはマンパワーの負担も大きく、読む人の主観にも左右されやすいですが、自然言語解析を活用することで、効率的かつ客観的な分析を行うことができます。
それ以外にも文章中で言及されているトピックを取得できるため、そのトピックを利用して 文章をカテゴリ分け/ タグ付けする ことに活用も出来ます。ニュース記事や投稿内容のトピックを判断し、機械的な処理に結びつけることができます。
他にもトピックの抽出とそのポジティブ/ネガティブの判別もできるため、 言語入力型のインターフェイスのリクエストの識別 にも活用できるし、顧客対応を目指し、ネガティブな投稿の監視にも役立てられると思います。
次回について
全4回の記事を投稿しておりますので、下記リンクから参照ください