14
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ツイートの性格分析による本のマッチングシステム

Last updated at Posted at 2018-10-30

はじめに

こんにちは。もっちゃそと申します。情報系大学の学部4年生です。
8月末からWeekend Engineer(WE)に参加し、アイデア出し&開発の経験を積んでいます。
その活動の中で、予め分析しておいた本の性格情報とツイートの性格情報を比較することで、
オススメの本をサジェストするシステムを作りました。

デモ動画

まず、自分のツイートで分析してみます。
・・・not foundでした。本の数や性格のパターンが少ないためよく出てきます...。


もう一度ダメ元でやってみたら、性格パターンが見つかりました。
同じユーザで結果が変わってしまっています。
同じ性格情報を持つ本が複数あって別の性格パターンが出力に選ばれるのなら
納得なんですが、「not found → 一致」は謎です...。


検索対象を渡辺さんに変えてみると、一致する性格パターンが見つかりました。
何回か試すと出力は変わりましたが、これはパターン候補が複数のケースでした。

きっかけ

当コミュニティでは以下のステップに従って経験を積んでいきます。

  1. アイデア出し:渡辺さん、開発:メンバー(@Mocchaso)
  2. アイデア出し・開発:メンバー + 渡辺さんのヘルプ
  3. アイデア出し・開発:メンバー + 基本ヘルプ無し

ステップ1では、渡辺さんが持っているアイデアから1つ選択して開発する流れとなっています。
Pythonをもっと鍛えたいのと、APIの使い方を勉強したかったので、このシステムを選択しました。

仕組み

システム図

本のサジェスト_システム図.jpg

本のデータをDL

青空文庫から、坊ちゃん、走れメロス、注文の多い料理店、人間失格、怪人二十面相をDLしました。
今回は手動で取得しましたが、自動化するプログラムを書けるともっと良かったかなと思います。

性格分析

IBM Watsonの一種であるPersonality Insightsを用いています。
Big Five Modelという性格の特性論に従って性格分析をしてくれるAPIです。
以下の処理により、分析結果をJSON形式で取得することができます。

def get_personality(text):
    api_version = ""
    api_username = ""
    api_password = ""
    api_url = "https://gateway.watsonplatform.net/personality-insights/api"
    
    personality_insights = PersonalityInsightsV3(
        version = api_version,
        username = api_username,
        password = api_password,
        url = api_url
    )
    
    profile = personality_insights.profile(
        content = text,
        content_type = "text/plain",
        accept = "application/json",
        content_language = "ja",
        accept_language = "en",
        raw_scores = True,
    )
    
    return profile

ツイートを取得

Twitter APIを用いて取得した後、正規表現で不要な情報を取り除いています。

from requests_oauthlib import OAuth1Session
from twitter import Twitter, OAuth

#APIキーの設置
CONSUMER_KEY =  ''
CONSUMER_SECRET = ''
ACCESS_TOKEN = ''
ACCESS_SECRET = ''

t = Twitter(auth=OAuth(
    ACCESS_TOKEN,
    ACCESS_SECRET,
    CONSUMER_KEY,
    CONSUMER_SECRET
))
    
twitter = OAuth1Session(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_SECRET)
url = "https://api.twitter.com/1.1/search/tweets.json"
userTweets = []

def get_userstweets_again(screen_name, max_id):
    max_id = max_id
    count = 200 #一度のアクセスで何件取ってくるか
    aTimeLine = t.statuses.user_timeline(screen_name = screen_name, count=count, max_id=max_id)
    for tweet in aTimeLine:
        userTweets.append(tweet['text'])

def get_userstweets(screen_name):
    number_of_tweets = 0
    count = 200 #一度のアクセスで何件取ってくるか
    aTimeLine = t.statuses.user_timeline(screen_name = screen_name, count=count)
    for tweet in aTimeLine:
        number_of_tweets += 1
        userTweets.append(tweet['text'])
        if number_of_tweets >= 200:
            max_id = tweet["id"]
            print(max_id)
            get_userstweets_again(screen_name, max_id)
    return userTweets
            
            
def get_shaped_tweets(tweets_list):
    shaped_tweets = []
    rm_replie = re.compile(r'@([A-Za-z0-9_]+)')
    rm_url = re.compile(r'https?://t.co/([A-Za-z0-9_]+)')
    rm_hashtag = re.compile(r'#(\w+)')
    for tweet in tweets_list:
        shape = rm_replie.sub('', tweet)
        shape = rm_url.sub('', shape)
        shape = rm_hashtag.sub('', shape)
        shape = shape.replace('&gt;', '>').replace('&lt;', '<').replace('&amp;', '&').replace('\n', ' ')
        shaped_tweets.append(shape)
    return shaped_tweets

性格パターンの抽出

ユーザのツイートを性格分析して得られた5つの要素の内、上から2番目まで高い要素を抽出して性格パターンとします。候補(同じ性格パターンを持つ本)が複数存在する場合は、ランダムで選択します。
例:{"Openness": 0.82, "Conscientiousness": 0.63, "Extraversion": 0.81, "Agreeableness": 0.67, "Emotional range": 0.74} -> 性格パターンは{"Openness", "Extraversion"}

def main(my_user_name):
    tweets = get_userstweets(my_user_name)
    tweets = get_shaped_tweets(tweets)

    ## ユーザのツイートを性格APIで分析する

    tweets_joined = " ".join(tweets) # 集めた複数のツイートを結合
    user_personality_dict = OrderedDict()

    tweet_personality_dict = get_personality(tweets_joined) # ツイートを結合したデータをAPIに入力
    # ツイートから分析した性格の名前
    personality_name = [big5["name"] for big5 in tweet_personality_dict["personality"]]
    # 性格の値。小数点第2位で四捨五入した後、decimal.Decimalからfloatに変換している。
    # 参考サイト(四捨五入):https://note.nkmk.me/python-round-decimal-quantize/
    personality_percentile = [float(Decimal(str(big5["percentile"])).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)) \
                            for big5 in tweet_personality_dict["personality"]]

    # ユーザのツイートの分析結果
    all_tweets_big5 = OrderedDict(zip(personality_name, personality_percentile))

    # { my_user_id: { personality_name1: percentile1, personality2: percentile2, ... } }
    user_personality_dict[my_user_name] = all_tweets_big5
    # print("finished analyzing all({}) tweets.".format(len(tweets)))

    # Agreeableness: 協調性, Conscientiousness: 真面目さ, Emotional range: 精神的安定性, Extraversion: 外向性, Openness: 開放性
    # 参考サイト:https://note.nkmk.me/python-math-factorial-permutations-combinations/
    big5_list = ["Agreeableness", "Conscientiousness", "Emotional range", "Extraversion", "Openness"]

    # big5から2つの性格を選ぶ組み合わせを列挙する
    category_patterns = [set(pat) for pat in itertools.combinations(big5_list, 2)]

    # ユーザのツイートのカテゴリ分けを記録する辞書
    user_tweet_category_table = OrderedDict()

    # ユーザのツイートの分析結果を、値の大きい順にソートする
    sorted_result = sort_personality(user_personality_dict[my_user_name])

    # big5のうち大きい順から2番目までの値の性格名のみを取得し、その本のカテゴリとする(例:{Openness, Extraversion})
    # この時、複数の性格の順番を考慮させないために、setで用意する
    # 2つを組み合わせているため、1つだけの場合よりも幅広くカテゴリ分けできそう
    tweet_category = {big5_elm[0] for big5_elm in sorted_result[0:2]}
    user_tweet_category_table[my_user_name] = tweet_category


    user_personality = user_tweet_category_table[my_user_name]

一致する性格パターンの本をサジェスト

ユーザのツイートを分析した結果を、事前に分析しておいたの本の性格情報と比較し、
性格パターンが一致する本を出力結果とします。

def main():
    ### 上記の続き

    suggest_book = ""
    img_path = "./static/images/nothing.jpg" # templates/index.htmlを基準にしたパス
    if user_personality == {'Openness', 'Extraversion'}:
        candidates = ['chumonno_oi_ryoriten', 'hashire_merosu']
        suggest_book = random.choice(candidates)
        img_path = "./static/images/" + suggest_book + ".jpg"
    elif user_personality == {'Openness', 'Emotional range'}: 
        suggest_book = 'bocchan'
        img_path = "./static/images/" + suggest_book + ".jpg"
    elif user_personality == {'Openness', 'Agreeableness'}:
        candidates = ['kaijin_nijumenso', 'ningen_shikkaku']
        suggest_book = random.choice(candidates)
        img_path = "./static/images/" + suggest_book + ".jpg"
    else:
        suggest_book = "not found a suggested book..."

    return suggest_book, img_path

上記のmain関数から返ってきた値をFlask側で取得し、ブラウザ上に表示します。
Flaskを用いて、簡易的なWebアプリケーションとして実装しています。
この時、それぞれの本に対応した画像も併せて出力されます。

@app.route('/result', methods=['POST'])
def add_user():
    title = "分析結果"
    my_user_name = request.form['my_user_name']
    if not my_user_name:
        return redirect('/')
    result, result_img = analyze_books.main(my_user_name)
    return render_template('index.html', title=title, result=result, result_img=result_img, my_user_name=my_user_name)

最後に

感想

JSONファイルやAPIの操作が初めてだったため苦戦しましたが、非常に勉強になりました。
課題としては、出力する本の候補が見つからない(not found)点です。
本の種類を増やして性格パターンを全て網羅させれば防げるはず・・・。

また、後半の実装の時間をとれず、ツイート取得やFlaskの実装を渡辺さんに任せてしまいました。
次回はステップ2なので、特に時間配分に気を付けたいと思います。

※コード全体はこちらをご参照ください
ツイートの性格分析による本のマッチングシステム

(初めての記事投稿意外と楽しかった)

Weekend Engineerとは

渡辺大智さん (@ahpjop) 率いる開発コミュニティ。
「どんなアイデアもカタチにする」ことに焦点を置いて活動しています。
ホームページはこちら。このホームページも、WEのメンバーが制作しました。

14
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?