【Python】Watsonを使ってツイートから性格推定をした

最近、業務でフォーラムに行く機会があり、そこでIBMの方とお話させてもらった。
そこで知ったのだが、IBMが所有しているAIのWatsonは各種言語向けのAPIを公開しているらしい。
しかも**基本無料**！
(使用量によって料金がかかる課金システム)
<img width="1092" alt="スクリーンショット 2019-07-21 22.38.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186515/1e663a69-d196-090d-3764-d9f2c951a725.png">
上記画像のように、Watsonの様々な機能を利用することができる。

今回は興味をもった性格推定(Personality Insights)をPythonで実装してみた。
TwitterのツイートデータをWatsonに食べさせて、性格を推定するといった流れだ。

[personality-insights-demo（GitHub）](https://github.com/m1kanch0r/personality-insight-demo)

# 環境構築
* Python 3.6.8
* IBM-Watson 3.1.2
* Tweepy 3.8.0

パッケージはpipで入る。
ローカルを汚したくなかったので、Cloud9で実装した。


# Watsonでの性格推定
Watsonはテキストデータから、書き手の性格を推定できるようだ。
面白いと思ったのは、テキストデータの意味を理解して推定するのではなく、文章の書き方の癖から推定するという点。
直感的なイメージだが、数字が多い人は理論派っぽいみたいな感じだろうか...。

[最強のチームはAIが作る!? Watsonの性格分析ツール開発秘話](https://www.ibm.com/think/jp-ja/business/personality-Insights/)


# 今回実装した流れ
実装の流れはシンプル
1. 指定したユーザのツイートを取得する
2. ノイズになりそうな部分(後述)を除外する
3. Watson APIに用意したデータを食べさせる
4. jsonで結果を出力する

本当はテストデータに著名人のツイートを使おうと思っていた。
みんな知っているし、なんとなくどんな性格かも予想ができるし。

ただ、IBMは他人のツイートの分析結果をアップロードすることを禁止している。(許可を取ればいいらしい)

仕方なしに今回は自分のツイートデータを入力する。
自分のことを知らない人がみても、推定できているのか否か全くわからないだろうけど...。


# 入力データ(ツイート取得)
ツイッターからデータを取得する前に、IBMのサンプルデータを確認する。
IBMが公開しているサンプルでも、ツイートを食べさせているのだが、気になった点がある。
下記のjsonファイルはサンプルデータの抜粋。

```json:tweets.json
{
  "contentItems": [
    {
      "content": "Wow, I liked @TheRock before, now I really SEE how special he is. The daughter story was IT for me. So great! #MasterClass",
      "contenttype": "text/plain",
      "created": 1447639154000,
      "id": "666073008692314113",
      "language": "en"
    },
    {
      "content": ".@TheRock how did you Know to listen to your gut and Not go back to football? #Masterclass",
      "contenttype": "text/plain",
      "created": 1447638226000,
      "id": "666069114889179136",
      "language": "en"
    },
    {
      "content": "RT @patt_t: @TheRock @Oprah @RichOnOWN @OWNTV this interview makes me like you as a fellow human even more for being so real.",
      "contenttype": "text/plain",
      "created": 1447637030000,
      "id": "666064097562247168",
      "language": "en"
    }
  ]
}
```
contentがツイート本文となっているわけだが、見てわかるように、リツイートやリプライ先のユーザIDが入ってしまっているのがわかる。

実際Twitter APIを叩いてツイートを取得すると、
- リツイート
- リプライ先のユーザID
- 本文中のURL
など、ノイズになりそうなものがちらほらある。

今回は取得したツイートから上記3つを間引いて、入力データを作成する。

ツイートを取得する部分はこんな感じ。

```Python:twitter_module.py
import tweepy
import re

def export_tweet_as_text(account):

    # TwitterAPIに関するAPIKeyを入力
    CONSUMER_KEY = 'xxxx'
    CONSUMER_SECRET = 'xxxx'
    ACCESS_TOKEN = 'xxxx'
    ACCESS_TOKEN_SECRET = 'xxxx'
    
    # APIインスタンス生成
    auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
    auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
    api = tweepy.API(auth)
    
    # 保存用テキストファイルを生成
    with open('../resources/tweets.txt', 'w') as f:
        
        # ツイートを取得
        tweet_count = 0
        pages=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]
        for page in pages:
            tweets=api.user_timeline(account, count=200, page=page)
            for tweet in tweets:
                tweet_text = tweet.text
                
                # RTを除外
                retweet_flag = False
                if tweet_text.find('RT') != -1:
                    retweet_flag = True
                if retweet_flag:
                    continue
                
                # ツイートから，URLとリプライ先のユーザIDを削除
                tweet_text = re.sub(r"(https?|ftp)(:\/\/[-_\.!~*\'()a-zA-Z0-9;\/?:\@&=\+\$,%#]+)", "", tweet_text)
                tweet_text = re.sub(r"@[a-zA-Z0-9_]+ ","",tweet_text)
                
                # ファイルに書き込み
                f.write(tweet_text + '\n')
```

Twitter APIやTweepyの使い方はこのあたりが参考になります。

[Twitter APIの申請手順まとめ【2019年最新版】](https://your-3d.com/apply-twitter-developer/)
[TweepyでTwitterの特定のユーザから3200ツイートの取得](https://non-face.hatenablog.com/entry/2018/07/15/215614)
[API Reference Tweepy](https://kurozumi.github.io/tweepy/)


# Watsonで性格推定
入力データができたら、あとはWatsonに食べさせるだけ！

ソースはこんな感じ。

```Python:watson_module.py
import json
from ibm_watson import PersonalityInsightsV3

def analyze＿personality(text_path):
    
    # APIインスタンスを生成
    service = PersonalityInsightsV3(
        version='2017-10-13',
        url='https://gateway-tok.watsonplatform.net/personality-insights/api',
        iam_apikey='XXXX')
    
    # 性格を分析
    with open('../resources/tweets.txt', 'r') as profile_text:
        profile = service.profile(
            profile_text.read(),
            'application/json',
            content_language='ja',
            accept_language='ja').get_result()
        
        # ファイルに書き込み
        with open('../result/result.json', 'w') as f:
            json.dump(profile, f, ensure_ascii=False, indent=2)
```

IBMのサンプルソースに一部変更を加えた形。
- 入力、出力の言語に日本語を指定
- 出力されたASCIIコードを変換
など、細々と変更。

Watson APIは日本語であまりいい説明がなかったので、リファレンスを参考にするのがいいと思う。
[API Reference Watson](https://cloud.ibm.com/apidocs/personality-insights)
[公式サンプルソース(GitHub)](https://github.com/watson-developer-cloud/python-sdk)

後は2つのメソッドをいい感じに動かせば、結果が出力されます。


# 出力結果
今回の出力結果はこんな感じ。長いです。

```json:tweets.json
{
  "word_count": 1477,
  "processed_language": "ja",
  "personality": [
    {
      "trait_id": "big5_openness",
      "name": "知的好奇心",
      "category": "personality",
      "percentile": 0.3269748764392166,
      "significant": true,
      "children": [
        {
          "trait_id": "facet_adventurousness",
          "name": "大胆性",
          "category": "personality",
          "percentile": 0.851632683688357,
          "significant": true
        },
        {
          "trait_id": "facet_artistic_interests",
          "name": "芸術的関心度",
          "category": "personality",
          "percentile": 0.08248046823602373,
          "significant": true
        },
        {
          "trait_id": "facet_emotionality",
          "name": "情動性",
          "category": "personality",
          "percentile": 0.01269078854956246,
          "significant": true
        },
        {
          "trait_id": "facet_imagination",
          "name": "想像力",
          "category": "personality",
          "percentile": 0.07828538874336671,
          "significant": true
        },
        {
          "trait_id": "facet_intellect",
          "name": "思考力",
          "category": "personality",
          "percentile": 0.3185276327945955,
          "significant": true
        },
        {
          "trait_id": "facet_liberalism",
          "name": "現状打破",
          "category": "personality",
          "percentile": 0.7757134968207737,
          "significant": true
        }
      ]
    },
    {
      "trait_id": "big5_conscientiousness",
      "name": "誠実性",
      "category": "personality",
      "percentile": 0.5968521957686052,
      "significant": true,
      "children": [
        {
          "trait_id": "facet_achievement_striving",
          "name": "達成努力",
          "category": "personality",
          "percentile": 0.4680138014818657,
          "significant": true
        },
        {
          "trait_id": "facet_cautiousness",
          "name": "注意深さ",
          "category": "personality",
          "percentile": 0.2776913924363156,
          "significant": true
        },
        {
          "trait_id": "facet_dutifulness",
          "name": "忠実さ",
          "category": "personality",
          "percentile": 0.646560141519093,
          "significant": true
        },
        {
          "trait_id": "facet_orderliness",
          "name": "秩序性",
          "category": "personality",
          "percentile": 0.9673235293559517,
          "significant": true
        },
        {
          "trait_id": "facet_self_discipline",
          "name": "自制力",
          "category": "personality",
          "percentile": 0.9534233902088189,
          "significant": true
        },
        {
          "trait_id": "facet_self_efficacy",
          "name": "自己効力感",
          "category": "personality",
          "percentile": 0.8047098839215382,
          "significant": true
        }
      ]
    },
    {
      "trait_id": "big5_extraversion",
      "name": "外向性",
      "category": "personality",
      "percentile": 0.886006317418282,
      "significant": true,
      "children": [
        {
          "trait_id": "facet_activity_level",
          "name": "活発度",
          "category": "personality",
          "percentile": 0.05870481709245734,
          "significant": true
        },
        {
          "trait_id": "facet_assertiveness",
          "name": "自己主張",
          "category": "personality",
          "percentile": 0.35164467138634853,
          "significant": true
        },
        {
          "trait_id": "facet_cheerfulness",
          "name": "明朗性",
          "category": "personality",
          "percentile": 0.7584765889006709,
          "significant": true
        },
        {
          "trait_id": "facet_excitement_seeking",
          "name": "刺激希求性",
          "category": "personality",
          "percentile": 0.4626730374972881,
          "significant": true
        },
        {
          "trait_id": "facet_friendliness",
          "name": "友好性",
          "category": "personality",
          "percentile": 0.9993552110743642,
          "significant": true
        },
        {
          "trait_id": "facet_gregariousness",
          "name": "社交性",
          "category": "personality",
          "percentile": 0.821949816324011,
          "significant": true
        }
      ]
    },
    {
      "trait_id": "big5_agreeableness",
      "name": "協調性",
      "category": "personality",
      "percentile": 0.43064808235299346,
      "significant": true,
      "children": [
        {
          "trait_id": "facet_altruism",
          "name": "利他主義",
          "category": "personality",
          "percentile": 0.8011665196131375,
          "significant": true
        },
        {
          "trait_id": "facet_cooperation",
          "name": "協働性",
          "category": "personality",
          "percentile": 0.23474709969090873,
          "significant": true
        },
        {
          "trait_id": "facet_modesty",
          "name": "謙虚さ",
          "category": "personality",
          "percentile": 0.2789880236799839,
          "significant": true
        },
        {
          "trait_id": "facet_morality",
          "name": "強硬さ",
          "category": "personality",
          "percentile": 0.555139731625757,
          "significant": true
        },
        {
          "trait_id": "facet_sympathy",
          "name": "共感度",
          "category": "personality",
          "percentile": 0.33013703370248587,
          "significant": true
        },
        {
          "trait_id": "facet_trust",
          "name": "信用度",
          "category": "personality",
          "percentile": 0.932881844256453,
          "significant": true
        }
      ]
    },
    {
      "trait_id": "big5_neuroticism",
      "name": "感情起伏",
      "category": "personality",
      "percentile": 0.5317695782374742,
      "significant": true,
      "children": [
        {
          "trait_id": "facet_anger",
          "name": "激情的",
          "category": "personality",
          "percentile": 0.1661703194086514,
          "significant": true
        },
        {
          "trait_id": "facet_anxiety",
          "name": "心配性",
          "category": "personality",
          "percentile": 0.2511270165944251,
          "significant": true
        },
        {
          "trait_id": "facet_depression",
          "name": "悲観的",
          "category": "personality",
          "percentile": 0.44009168638198337,
          "significant": true
        },
        {
          "trait_id": "facet_immoderation",
          "name": "利己的",
          "category": "personality",
          "percentile": 0.690512849700951,
          "significant": true
        },
        {
          "trait_id": "facet_self_consciousness",
          "name": "自意識過剰",
          "category": "personality",
          "percentile": 0.05802391279529695,
          "significant": true
        },
        {
          "trait_id": "facet_vulnerability",
          "name": "低ストレス耐性",
          "category": "personality",
          "percentile": 0.2799141848650055,
          "significant": true
        }
      ]
    }
  ],
  "needs": [
    {
      "trait_id": "need_challenge",
      "name": "挑戦",
      "category": "needs",
      "percentile": 0.912788130027943,
      "significant": true
    },
    {
      "trait_id": "need_closeness",
      "name": "親密",
      "category": "needs",
      "percentile": 0.47916836503889804,
      "significant": true
    },
    {
      "trait_id": "need_curiosity",
      "name": "好奇心",
      "category": "needs",
      "percentile": 0.968932858050091,
      "significant": true
    },
    {
      "trait_id": "need_excitement",
      "name": "興奮",
      "category": "needs",
      "percentile": 0.9976361241706758,
      "significant": true
    },
    {
      "trait_id": "need_harmony",
      "name": "調和",
      "category": "needs",
      "percentile": 0.009193757113194367,
      "significant": true
    },
    {
      "trait_id": "need_ideal",
      "name": "理想",
      "category": "needs",
      "percentile": 0.917518963157462,
      "significant": true
    },
    {
      "trait_id": "need_liberty",
      "name": "自由主義",
      "category": "needs",
      "percentile": 0.9970523431240045,
      "significant": true
    },
    {
      "trait_id": "need_love",
      "name": "社会性",
      "category": "needs",
      "percentile": 0.710476275913941,
      "significant": true
    },
    {
      "trait_id": "need_practicality",
      "name": "実用主義",
      "category": "needs",
      "percentile": 0.9942052654351621,
      "significant": true
    },
    {
      "trait_id": "need_self_expression",
      "name": "自己表現",
      "category": "needs",
      "percentile": 0.283590546642394,
      "significant": true
    },
    {
      "trait_id": "need_stability",
      "name": "安定性",
      "category": "needs",
      "percentile": 0.3676226427668019,
      "significant": true
    },
    {
      "trait_id": "need_structure",
      "name": "仕組",
      "category": "needs",
      "percentile": 0.03713112525416268,
      "significant": true
    }
  ],
  "values": [
    {
      "trait_id": "value_conservation",
      "name": "現状維持",
      "category": "values",
      "percentile": 0.1174651842220481,
      "significant": true
    },
    {
      "trait_id": "value_openness_to_change",
      "name": "変化許容性",
      "category": "values",
      "percentile": 0.8841545033162561,
      "significant": true
    },
    {
      "trait_id": "value_hedonism",
      "name": "快楽主義",
      "category": "values",
      "percentile": 0.05959893144723466,
      "significant": true
    },
    {
      "trait_id": "value_self_enhancement",
      "name": "自己増進",
      "category": "values",
      "percentile": 0.9674816463059281,
      "significant": true
    },
    {
      "trait_id": "value_self_transcendence",
      "name": "自己超越",
      "category": "values",
      "percentile": 0.08191121317174338,
      "significant": true
    }
  ],
  "warnings": []
}
```

percentileがその指標のポイントのようなもの。
正確には、IBMが学習する際に利用した人たちの中で、どのあたりに位置するかを表しているらしい。
下から何%ぐらいという感じだろう。

協働性が下から23%らしい。なんとなく当たっているような。

# まとめ
公式リファレンスが充実していたこともあって、割とすんなり実装できた。
ぶっちゃけ、自分のツイートデータを使って性格推定するだけならば、
公式が用意しているグラフィカルなデモの方が100倍わかりやすい。

[IBM Personality Insights](https://personality-insights-demo.ng.bluemix.net/)

ただ、これだと自分のアカウントとしか紐付けできないので、勝手に人の性格を推定したい場合は実装するしかない。

あとは、今回取得したデータを何に活かすかって部分が重要になってくるのだろうけど、
思いつかないので気ままに考えます。