AWS Comprehendの感情分析を使ってみた

はじめまして、新入社員のTHです。

AWSは未経験ですが、ブログを書かせていただくことになりました。

以後よろしくお願いいたします!

 

この記事でやること

Amazon Comprehendを使用して、文章の感情分析をやってみます。
特別な知識は不要で、Web上で体験してみるだけなら、すぐに実践可能です。
最後に簡単な分析パイプラインを作ってみます。

Amazon Comprehendとは

機械学習を使用してテキストから洞察を見つける自然言語処理 (NLP) サービスです。
キーフレーズ(会話のポイント)を抽出したり、感情分析を行ったりできます。
Amazon Comprehendの特徴:
https://aws.amazon.com/jp/comprehend/features/

使ってみた感想

感情分析でPositive, Negativeに分類できるので、Negativeに分類されたデータから改善点に関する知見を抽出できそうだと思いました。
もっと知りたいと思えるサービスでした。

料金について

Amazon公式ページをご参照ください。
Amazon Comprehendの料金:
https://aws.amazon.com/jp/comprehend/pricing/

 

やってみよう

AWS Comprehendを実際にコンソール上で動かしてみます。

Amazon Comprehendをコンソールで開く

AWSにログインしたら、Comprehendで検索します。
Launch Amazon Comprehendをクリックします。


Input text欄にある、Built-inを選択して、Input textに分析したい文字列を入力していきましょう。
Built-in: AWSが用意したモデルを使用します
Custom: 自分で用意したモデルを利用します


分析結果 エンティティ

「日本の寿司は世界一!」で分析してみました。
先ほどのInput textの下にある、Insightsに結果が表示されます。


エンティティの抽出結果を見てみます。どうやら「寿司」は抽出できていないみたいですね。
現在、食べ物はエンティティ抽出の対象外みたいです。詳しくは下記をご覧ください。
エンティティの検出: https://docs.aws.amazon.com/ja_jp/comprehend/latest/dg/how-entities.html

分析結果 感情スコア

次に感情分析の結果を見てみましょう。
私としては、ポジティブに書いたつもりです。
Positiveスコアが0.69と最も高く、期待通りですね。


では上記を否定形にするとどうでしょうか。
Negativeスコアが最も高くなりそうですが…
Negativeスコアが0.65と期待通りとなりましたね。


パイプラインの作成

コンソール上で手動で行いましたが、自動で感情値を取得できるようにします。
流れは下記のとおりです。
1. JSONファイルをS3(input用のバケット)にアップロード
2. Lambda関数が起動して、感情値を取得
3. JSONファイルに感情値を追加(追加後、Positiveスコアで降順にします)
4. JSONファイルをS3(output用のバケット)にアップロード

S3の作成

S3で検索して、適当な名前でinput用とoutput用のバケットを用意してください。
バケット名は全世界でユニークです。私と同じバケット名はつけられません。


Lambda関数の作成

以下4ステップでLambda関数を用意していきます。

①設計図「s3-get-object-python」から関数を作成

Lambdaで検索して、関数を作成します。
「設計図の使用」を選択して、設計図に「s3-get-object-python」を選択します。
S3トリガーには、用意したバケットを指定してください(input用を選択するようご注意ください)


②アクセス権限の編集(S3とComprehendの権限を追加します)

Lambda関数の作成が終了したら、アクセス権限を追加していきます。
1. 設定タブをクリック
2. サイドメニューよりアクセス権限をクリック
3. ロール名をクリックして、ロールの編集画面に遷移

4. 遷移先で下記ポリシーを選択してアタッチ
・AmazonS3FullAccess
・ComprehendFullAccess


③Lambdaのタイムアウト設定を編集

デフォルト設定だとタイムアウトする可能性があるので、編集しておきます。
1. 設定タブをクリック
2. サイドメニューより一般設定を選択
3. 編集ボタンをクリックして、編集画面に遷移
4. タイムアウトの時間を10秒に変更して保存


④実行コードを用意して、Deploy

import json
import urllib.parse
import boto3

s3 = boto3.client('s3')
comprehend = boto3.client('comprehend')

def upload_file(bucket, filename, content):
    s3_object = boto3.resource('s3').Object(bucket, filename)
    s3_object.put(Body=(content))

def get_sentiment_score(target):
    sentiment = comprehend.detect_sentiment(Text=target, LanguageCode='ja')
    return sentiment.get('SentimentScore')

def lambda_handler(event, context):
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')

    try:
        # トリガーとなったS3イベントのファイル内容を取得
        response = s3.get_object(Bucket=bucket, Key=key)
        body = json.load(response['Body'])
        
        # 感情分析の結果を追加
        for i in range(len(body)):
            text = body[i]['text']
            body[i].update(get_sentiment_score(text))
        
        # 感情分析の結果をアップロード
        content = sorted(body, key=lambda x:x['Positive'], reverse=True) # 降順
        content = json.dumps(content, ensure_ascii=False)
        # 出力先のbucketは各自、書き換えてください
        # 必ず出力用のバケットを指定してください(Input用にアップロードすると、無限ループに陥ります)
        upload_file("出力用のバケット", key, content)
        
        return response['ContentType']
        
    except Exception as e:
        print(e)
        print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket))
        raise e

実際に動かしてみる

今回使用するデータは、社員の入社時の自己紹介文です。
用意したバケットJSONファイルをアップロードしてみましょう。

JSONファイル

[
{"text":"まだまだ若輩者なので、ご指導のほど、よろしくお願いいたします。"},
{"text":"これからクロスパワーの一員としてがんばっていきたいと思います。よろしくお願いします。"},
{"text":"色々とご迷惑をおかけするかもしれませんが、日々精進してまいりますので、どうぞよろしくお願いいたします。"},
{"text":"よろしくおねがいします!"},
{"text":"コツコツ頑張ります"},
{"text":"がんばりますので、よろしくお願いいたします。"},
{"text":"ねこと一緒にがんばります。ゲームのマルチプレイのお誘い待ってます!"},
{"text":"仕事になれるまでは、聞く事がたくさんあると思いますが、よろしくおねがいします。"},
{"text":"気軽に話しかけて下さい。"},
{"text":"よろしくお願いします。"},
{"text":"一日でも早く会社に貢献できるようがんばりますので、ご指導よろしくお願いいたします。"},
{"text":"がんばります。よろしくお願いします。"},
{"text":"1日でも早く仕事になじめるようにがんばります。"},
{"text":"初めての事ばかりですが、頑張ります。"},
{"text":"これからよろしくお願い致します。"},
{"text":"仕事を早く覚えるよう精進して参りますので、よろしくお願いいたします。"},
{"text":"未経験でございますが、努力してまいりますので、宜しくお願いします。"},
{"text":"早々にお役に立てるよう頑張ります!"},
{"text":"至らぬ点が多々あると思いますが、よろしくお願い致します。"},
{"text":"色々な事に挑戦したいです。よろしくお願いします。"},
{"text":"よろしくお願いします。"},
{"text":"真面目に楽しく社会人生活を楽しみます!"},
{"text":"一生懸命頑張ります。"},
{"text":"未経験の身ではありますが一生懸命がんばるので、よろしくお願いします!"},
{"text":"宜しくお願いします。"},
{"text":"人として、SEとして成長できるようにがんばります。"},
{"text":"インフラ配属となりました。皆様よろしくお願いします。"},
{"text":"よろしくお願いします。"},
{"text":"これからがんばります。"},
{"text":"何かと抜けていて至らぬ点が多々ありますが、よろしくお願いします。"},
{"text":"早く活躍できるよう努めてまいります。"},
{"text":"お役に立てるよう努めますのでよろしくお願いします。"},
{"text":"早く仕事を覚えて戦力になれるように頑張ります。"},
{"text":"未熟者ですが精一杯がんばります。"},
{"text":"クロスパワーの文化に早く溶け込めるようがんばります。"},
{"text":"頑張ります。"},
{"text":"一生懸命頑張りますので宜しくお願いします!"},
{"text":"宜しくお願いします!"},
{"text":"頑張りますので、宜しくお願いします!"},
{"text":"挑戦をあきらめません。"},
{"text":"すごく入りたかった会社に入社出来て、とてもうれしいです。がんばりますのでよろしくお願いします。肩がこったらいつでも声かけてください^^"},
{"text":"仕事も楽しくをモットーに仕事をしています。休日は大体スノボ、釣りをしているのでお誘いください!"},
{"text":"よろしくお願いします。"}
]


input用のバケットにアップロードしたら、output用のバケットに移動します。
新規ファイルが作成されているはずです。ダウンロードして感情値が追加されているか確認します。
※ Lambda関数の処理が終了していない場合、しばらく待ちます。

ダウンロードしたら結果を見てみましょう。

出力結果

Positiveスコアで結果を降順にしています。
個人的にはどれもポジティブな自己紹介に感じます!
Positiveスコアが低いからといって、Negativeスコアが高いわけではありませんね。
上位にあるものは、個人的な感覚ともマッチしています。

[
{"text": "真面目に楽しく社会人生活を楽しみます!", "Positive": 0.9980524778366089, "Negative": 3.910830128006637e-05, "Neutral": 0.0018935317639261484, "Mixed": 1.483429969084682e-05},
{"text": "すごく入りたかった会社に入社出来て、とてもうれしいです。がんばりますのでよろしくお願いします。肩がこったらいつでも声かけてください^^", "Positive": 0.9973375201225281, "Negative": 7.24554993212223e-05, "Neutral": 0.0025308961048722267, "Mixed": 5.910595064051449e-05},
{"text": "これからよろしくお願い致します。", "Positive": 0.9960934519767761, "Negative": 0.0003147170937154442, "Neutral": 0.00358265801332891, "Mixed": 9.196657629217952e-06},
{"text": "一生懸命頑張りますので宜しくお願いします!", "Positive": 0.9959995746612549, "Negative": 0.00014208932407200336, "Neutral": 0.0038421181961894035, "Mixed": 1.6192951079574414e-05},
{"text": "よろしくおねがいします!", "Positive": 0.9949235320091248, "Negative": 0.000494243809953332, "Neutral": 0.0045768702402710915, "Mixed": 5.342153599485755e-06},
{"text": "頑張りますので、宜しくお願いします!", "Positive": 0.993073046207428, "Negative": 0.00019358166900929064, "Neutral": 0.006719977594912052, "Mixed": 1.3403773664322216e-05},
{"text": "宜しくお願いします!", "Positive": 0.990885317325592, "Negative": 0.0002177592396037653, "Neutral": 0.008888466283679008, "Mixed": 8.483581041218713e-06},
{"text": "仕事も楽しくをモットーに仕事をしています。休日は大体スノボ、釣りをしているのでお誘いください!", "Positive": 0.986234188079834, "Negative": 4.6934408601373434e-05, "Neutral": 0.013689090497791767, "Mixed": 2.988532287417911e-05},
{"text": "早々にお役に立てるよう頑張ります!", "Positive": 0.9791128635406494, "Negative": 0.0005151255172677338, "Neutral": 0.02034587785601616, "Mixed": 2.62462690443499e-05},
{"text": "一日でも早く会社に貢献できるようがんばりますので、ご指導よろしくお願いいたします。", "Positive": 0.9733215570449829, "Negative": 0.0005047339363954961, "Neutral": 0.026163749396800995, "Mixed": 9.974874046747573e-06},
{"text": "挑戦をあきらめません。", "Positive": 0.9646511077880859, "Negative": 0.0016795678529888391, "Neutral": 0.03364716097712517, "Mixed": 2.2128395357867703e-05},
{"text": "お役に立てるよう努めますのでよろしくお願いします。", "Positive": 0.9617434144020081, "Negative": 0.0003617567417677492, "Neutral": 0.03788091987371445, "Mixed": 1.3896367818233557e-05},
{"text": "宜しくお願いします。", "Positive": 0.9595017433166504, "Negative": 0.0003451470984145999, "Neutral": 0.040138497948646545, "Mixed": 1.4592422303394414e-05},
{"text": "まだまだ若輩者なので、ご指導のほど、よろしくお願いいたします。", "Positive": 0.9572271108627319, "Negative": 0.0005388210993260145, "Neutral": 0.04219797998666763, "Mixed": 3.614382148953155e-05},
{"text": "がんばります。よろしくお願いします。", "Positive": 0.9285699129104614, "Negative": 0.004425073508173227, "Neutral": 0.06698764115571976, "Mixed": 1.7277885490329936e-05},
{"text": "よろしくお願いします。", "Positive": 0.9280008673667908, "Negative": 0.002992258407175541, "Neutral": 0.0689290463924408, "Mixed": 7.792825635988265e-05},
{"text": "よろしくお願いします。", "Positive": 0.9280008673667908, "Negative": 0.002992258407175541, "Neutral": 0.0689290463924408, "Mixed": 7.792825635988265e-05},
{"text": "よろしくお願いします。", "Positive": 0.9280008673667908, "Negative": 0.002992258407175541, "Neutral": 0.0689290463924408, "Mixed": 7.792825635988265e-05},
{"text": "よろしくお願いします。", "Positive": 0.9280008673667908, "Negative": 0.002992258407175541, "Neutral": 0.0689290463924408, "Mixed": 7.792825635988265e-05},
{"text": "がんばりますので、よろしくお願いいたします。", "Positive": 0.9153178334236145, "Negative": 0.003962054383009672, "Neutral": 0.08070055395364761, "Mixed": 1.9533901649992913e-05},
{"text": "これからクロスパワーの一員としてがんばっていきたいと思います。よろしくお願いします。", "Positive": 0.8996465802192688, "Negative": 0.002966221421957016, "Neutral": 0.0973653569817543, "Mixed": 2.1831532649230212e-05},
{"text": "仕事になれるまでは、聞く事がたくさんあると思いますが、よろしくおねがいします。", "Positive": 0.8903701305389404, "Negative": 0.0012445531319826841, "Neutral": 0.08261790126562119, "Mixed": 0.025767317041754723},
{"text": "色々な事に挑戦したいです。よろしくお願いします。", "Positive": 0.8853881359100342, "Negative": 0.0008263525669462979, "Neutral": 0.11376026272773743, "Mixed": 2.5274257495766506e-05},
{"text": "色々とご迷惑をおかけするかもしれませんが、日々精進してまいりますので、どうぞよろしくお願いいたします。", "Positive": 0.8642394542694092, "Negative": 0.0010782324243336916, "Neutral": 0.134404718875885, "Mixed": 0.00027757862699218094},
{"text": "インフラ配属となりました。皆様よろしくお願いします。", "Positive": 0.8211432099342346, "Negative": 0.007623747456818819, "Neutral": 0.17120759189128876, "Mixed": 2.5437413569306955e-05},
{"text": "頑張ります。", "Positive": 0.7718032598495483, "Negative": 0.00844748318195343, "Neutral": 0.219117671251297, "Mixed": 0.000631549977697432},
{"text": "早く活躍できるよう努めてまいります。", "Positive": 0.7453831434249878, "Negative": 0.00042650187970139086, "Neutral": 0.25416290760040283, "Mixed": 2.7507385311764665e-05},
{"text": "一生懸命頑張ります。", "Positive": 0.7420463562011719, "Negative": 0.006911689881235361, "Neutral": 0.25069305300712585, "Mixed": 0.0003488249785732478},
{"text": "未経験の身ではありますが一生懸命がんばるので、よろしくお願いします!", "Positive": 0.7360607981681824, "Negative": 0.06259702146053314, "Neutral": 0.19928327202796936, "Mixed": 0.0020589393097907305},
{"text": "これからがんばります。", "Positive": 0.6852964162826538, "Negative": 0.07998844981193542, "Neutral": 0.23464520275592804, "Mixed": 6.99266602168791e-05},
{"text": "初めての事ばかりですが、頑張ります。", "Positive": 0.6545454263687134, "Negative": 0.0029141956474632025, "Neutral": 0.3324032723903656, "Mixed": 0.010137174278497696},
{"text": "未経験でございますが、努力してまいりますので、宜しくお願いします。", "Positive": 0.5879949927330017, "Negative": 0.0018560898024588823, "Neutral": 0.4100433588027954, "Mixed": 0.00010557023051660508},
{"text": "仕事を早く覚えるよう精進して参りますので、よろしくお願いいたします。", "Positive": 0.5061310529708862, "Negative": 0.0011824388056993484, "Neutral": 0.492643803358078, "Mixed": 4.2715069866972044e-05},
{"text": "1日でも早く仕事になじめるようにがんばります。", "Positive": 0.46091514825820923, "Negative": 0.18871335685253143, "Neutral": 0.350151926279068, "Mixed": 0.00021956842101644725},
{"text": "早く仕事を覚えて戦力になれるように頑張ります。", "Positive": 0.4140060842037201, "Negative": 0.010371980257332325, "Neutral": 0.5749410390853882, "Mixed": 0.0006809122860431671},
{"text": "気軽に話しかけて下さい。", "Positive": 0.4125920534133911, "Negative": 0.00223811948671937, "Neutral": 0.5851460695266724, "Mixed": 2.381978447374422e-05},
{"text": "コツコツ頑張ります", "Positive": 0.34744033217430115, "Negative": 0.01964150182902813, "Neutral": 0.6325687170028687, "Mixed": 0.0003494280681479722},
{"text": "ねこと一緒にがんばります。ゲームのマルチプレイのお誘い待ってます!", "Positive": 0.32923588156700134, "Negative": 0.04746706038713455, "Neutral": 0.6232355237007141, "Mixed": 6.151555862743407e-05},
{"text": "至らぬ点が多々あると思いますが、よろしくお願い致します。", "Positive": 0.2274141013622284, "Negative": 0.17051787674427032, "Neutral": 0.5776476263999939, "Mixed": 0.024420343339443207},
{"text": "人として、SEとして成長できるようにがんばります。", "Positive": 0.08758891373872757, "Negative": 0.05022376403212547, "Neutral": 0.8620947599411011, "Mixed": 9.247381967725232e-05},
{"text": "クロスパワーの文化に早く溶け込めるようがんばります。", "Positive": 0.051380474120378494, "Negative": 0.5052165389060974, "Neutral": 0.44321373105049133, "Mixed": 0.00018930257647298276},
{"text": "未熟者ですが精一杯がんばります。", "Positive": 0.04379380866885185, "Negative": 0.06648029386997223, "Neutral": 0.0646212100982666, "Mixed": 0.8251046538352966},
{"text": "何かと抜けていて至らぬ点が多々ありますが、よろしくお願いします。", "Positive": 0.009399283677339554, "Negative": 0.05406167358160019, "Neutral": 0.03602847456932068, "Mixed": 0.9005105495452881}
]

感想

簡単にAmazon Comprehendを紹介しました。
手軽に文章を解析できる、とてもいい機能ですね!
今回は自己紹介文の感情値を取得して、Positiveスコアでソートしてみました。

他にも、下記のような応用ができるのではないでしょうか。
・Negativeスコアの高いグループから改善点に関する知見を得る(ログ、アンケートなど)
・Positive, Negativeに分類し、各グループから改善点に関する知見を得る(ログ、アンケートなど)
・物語のピークを感情値の時系列で見つける
・物語のあらすじの感情値から、感情値でランキングにしてみる
・音楽の歌詞から、感情値でランキングにしてみる

Amazon公式のユースケースは下記に記載があります。
Amazon Comprehend:https://aws.amazon.com/jp/comprehend/

以上です、ぜひみなさんも使ってみてください!