Pythonで自然言語処理を行いツイート語句の文脈的な意味を観察する

はじめに

前回の投稿前々回の投稿と"Python"を検索語句として日本語と英語でそれぞれツイートを抽出し、言語処理を行っていいねの数が多いツイートの傾向を見てきました。 その際には語句の数といいね数との関係を中心に見てきましたが、今回はもう少し別の切り口で分析を行いました。

以下の視点で分析を行いました。

  • 各個人のツイート数がいいねの数に影響を受けるのか
  • 検索語句(Python)がいいねの数によってどのような文脈で使用されているのか(collocation、concordance)
  • 検索語句がいいねの数によってどのような意味を持つもの(イメージ)として使用されているのか(similarity、Word2Vec)

使用するツイートデータ

前々回と同じく、Twitter APIを使用して「Python lang: ja -RT OR @a -@a」を検索条件として取得した最近5000件のツイートデータを使用しました。

    import pandas as pd
word = "Python"
df = pd.read_csv(f"./{word}.csv", index_col=0)
    

各個人のツイート数といいねの数の関係

ある関連語句に関してのツイート数が多ければ多いほどインプレッションが増えるため、いいねなどのエンゲージメント率が高くなるようなケースがあったり、 ツイートしすぎてスルーされる傾向も高くなるようなケースも考えられます。このような疑問を解消するために、各アカウントごとのツイート数といいね数の平均を見ました。

集計して散布図に出力するコードは以下になります。 twitterアカウントごとにgroupby + aggregateしてmatplotlibに渡しました。

    df_group = df[["author_id", "like_count"]].groupby("author_id").agg([("count", "count"), ("like_mean", "sum")]).droplevel(0, axis=1)
df_group = df_group.sort_values('like_mean', ascending=False).reset_index().drop("author_id", axis=1)

import matplotlib.pyplot as plt
plt.scatter(df_group["count"], df_group["like_mean"])
plt.xlabel('count')
plt.ylabel('like_mean')
    

どうやらPythonの場合はツイート数といいね数の間にはそれほど関係はないようです。 ツイートした人物やツイートの内容の方を重視されるのかもしれません。

いいね数でツイートを分割

前々回と同じくいいね数の大小でツイートの傾向を見るために、いいね数ごとにツイートをクラス分けしました。 分け方は以下になります。いいね数が凝集しすぎており、どこで分けるかの判断が難しかったため境界はざっくりと分けました。

  • low : いいね数 0-1
  • mid : いいね数 2-19
  • high : いいね数 20-

クラス分けのコードは以下になります。

    df_class = df.copy()
df_class["classes"] = 0
df_class.loc[df_class['like_count'] < 2, "classes"] = 0
df_class.loc[(df_class['like_count'] > 2) & (df_class['like_count'] <= 20), "classes"] = 1
df_class.loc[df_class['like_count'] > 20, "classes"] = 2

lowfav = df_class[df_class["classes"] == 0]
midfav = df_class[df_class["classes"] == 1]
highfav = df_class[df_class["classes"] == 2]
    

ツイート文章をトークン化(単語に分割/クリーニング/Normalization処理)

ツイートの文章を扱いやすくするために、テキストのnormalizationを行いました。 処理の内容としてはテキストからツイート特有のURL、ハッシュタグ、引用部分を除いた後、 MeCabにより形態素解析を行い、語句は見出し語に変換してから名詞・動詞・形容詞の自立語のみを抽出しました。 辞書にはmecab-ipadic-NEologdを使用しました。

    import re
import MeCab

def pick_sentense(df):
    me = MeCab.Tagger()
    sentences = []
    for id, text in df["text"].items():
        words = []
        text = re.sub(r'http\S+', '', text)
        text = re.sub(r'#\w+', '', text)
        text = re.sub(r'@[\w\d_]*', '', text)

        lines = me.parse(text).splitlines()
        for line in lines:
            if line == "EOS\n":
                break
            fields = line.split("\t")
            if len(fields) != 2 or fields[0].isdigit():
                continue
            
            fields = fields[1].split(",")
            
            pos = fields[0]
            pos1 = fields[1]
            base = fields[6]
            if base == "*":
                continue
            
            if pos == "名詞" and pos1 in ['一般','固有名詞','サ変接続','形容動詞語幹']:
                words.append(base)
            elif pos == '形容詞' and pos1 == '自立':
                words.append(base)
            elif pos == "動詞" and pos1 == "自立":
                words.append(base)
            

            #words.append(base)
        sentences.append(words)
    
    return sentences
    

頻繁に出てくる連語を観察

形態素解析の後、まずは連語(collocation)の抽出を行い、どのような連語が頻繁に使用されているかを見ました。 nltk.Text.collocationsは文章中で頻出する連語の上位を出力します。 最初にいいね数ごとにクラス(low,mid,high)分けを行い、それぞれにテキストのnormalizationを行った後に、 連語を出力します。

    import nltk
labels = ['low', 'mid', 'high']

low_sentences = pick_sentense(lowfav)
low_text = nltk.Text(sum(low_sentences, []))
mid_sentences = pick_sentense(midfav)
mid_text = nltk.Text(sum(mid_sentences, []))
high_sentences = pick_sentense(highfav)
high_text = nltk.Text(sum(high_sentences, []))

for text, label in zip([low_text, mid_text, high_text], labels):
    print(f"{label}: ")
    print(text.collocations(num=20, window_size=5))
    

出力は以下になります。

    low: 
アイテム クリエイティブ; クリエイティブ DESKTOP; ではじめる DESKTOP; 
クリエイティブ ではじめる; DESKTOP アプリ開発; おすすめ クリエイティブ;
クリエイティブ アプリ開発; おすすめ アイテム; ではじめる アプリ開発; 四角形 四角形;
ハート ハート; ネットワーク サーバー; スキル 偏差値; プロフィール リンク; 募集中 答える;
tweet プロフィール;プロフィール くださる; リンク くださる; 偏差値 診断結果; tweet リンク

mid: 
アイテム クリエイティブ; クリエイティブ DESKTOP; クリエイティブ ではじめる;
ではじめる DESKTOP; クリエイティブ アプリ開発; DESKTOP アプリ開発; ではじめる アプリ開発;
おすすめ クリエイティブ; おすすめ アイテム; スキル 偏差値;
レッスン コース; HTML CSS; 100本 ノック; 偏差値 診断結果; START 800円; サーバー VPS;
いつお チャンネル; 高電圧 幅広い; cospa START; 圧倒的 cospa

high: 
チェックボックス チェックボックス; HTML CSS; チェックボックス 応用情報; 難易度 難易度; グリッド サーチ;
65日目 チェック; Pokemon! あぶる; アルゴリズム トレード; 積み上げ 情報技術; Excel シミュレーション;
はじめる アルゴリズム; はじめる トレード; コピペ YES; サーチ ハイパーパラメータ; 英語学習 小論文;
Unity チェックボックス; チェックボックス Unity; 難易度 JavaScript; コード コピペ; サンプル コード
    

出力を見ると、いいね数が少ない場合「おすすめ」などが目立つので、何かの宣伝のようなフレーズが頻出しているのがわかりました。 いいね数が多くなるほど、プログラミングに関連性のある語句が目立つようになりました。 前々回と同じツイートデータを使用していますが、語句のみを抽出したときと比較してかなり印象が変わりました。

検索語句周りの文章の抽出

次にconcordanceつまりPythonがどのような文脈で使用されているかを検索して抽出し、その例を見ていきます。 nltk.Text.concordanceはPythonが含まれる文章の前後のテキストを抽出します。

    for text, label in zip([low_text, mid_text, high_text], labels):
    print(f"{label}: ")
    print(text.concordance("Python",lines=5))
    

出力は以下になります。

    low: 
Displaying 5 of 2519 matches:
    "Python" 学習 レッスン コース 修了 する 勉強 する 参考 なる 本 サイト
る 搭乗 席 反対 合格 できる 解く かかる 自力 解ける 学 ドア "Python" とる やる 遅い 知る かる 系列 アセンブラ 見慣れる 形 書く 非
い 知る かる 系列 アセンブラ 見慣れる 形 書く 非常 勉強 なる "Python" 試行 使用 する ファイル 定型 コード テンプレート 2020年 定
計算機 プログラミング 統計データ 解析 計算 理論 図形 科学 演習 "Python" コンピュータ グラフィクス 図形 科学 基礎 統計 クラス 雑 理解 
    よる 開発 楽しい 記法 シンプル 書く 気持ち 良い 日本文教出版 "Python" Webページ JavaScript 著者 監修 黒上 晴夫 先生 堀田

mid: 
Displaying 5 of 858 matches:
普通 高校1年 教師 良い "Python" だるい 合同会社 インターン メンバー 募集中 プログラミング HTM
バー 募集中 プログラミング HTML CSS Java script "Python" Webサービス 運用 企画 動画編集 SNS 運用 新事業 企画 一緒
画 動画編集 SNS 運用 新事業 企画 一緒 やる 経験 歓迎 退屈 "Python" やる プログラマー できる 自動化 処理 プログラミング 大人気 プロ
    自動化 処理 プログラミング 大人気 プログラミング言語 魅力 迫る "Python" スクレイピング する お仕事 求人 ページ 見つける 在宅 勤務 OK
    スクレイピング ハッキング ラボ 読者 力量 試し 応募 する 良い "Python" お仕事 コマンド 利用 する コマンド 操作 する 変 エラー 出る 

high: 
Displaying 5 of 89 matches:
じ Apple Store 行く Apple 買う GAS 勉強 する "Python" 勉強 する テンション 言う 戦略的 怠惰 つぶやく 土日 終わる ネ
 テンション 言う 戦略的 怠惰 つぶやく 土日 終わる ネット 英語 "Python" 情報 勝手 コード データセット 構築 する 自分 解析 出来る なる
妻 LINE ワイン 飲む 行く 笑顔 確か 外出 する 会話 少ない "Python" 勉強 諦める 酔っ払う 皆さん 酔い 東京 4月20日 発売 書籍 ラ
解 する 量子コンピュータ 4月25日 発売 Interface 特集 "Python" 体験 量子コンピュータ 前者 基礎 実験 理解 する 後者 Pytho
ython 体験 量子コンピュータ 前者 基礎 実験 理解 する 後者 "Python" プログラム 打つ 体感 する 前者 Interface 2019年 3
    

検索語句の"Python"を中心に前後の文章が抽出されていることがわかります。 これはPythonの語句が出現する文章すべてにマッチするので、5つの文章のみを挙げています。 検索語句が実際にどのような文脈で使用されているかを簡単に実際に確認することができます。

nltk.ContextIndexを使ってクラスごとに検索語句と似た語句を抽出する

nltk.ContextIndexは、それぞれのテキスト内の文脈においての単語のインデックス、意味のようなものを定義します。 このオブジェクトのメソッドsimilar_wordsは、low~highのクラスごとにツイートした人物が検索語句Pythonと他のどのような語句と関連性が深いと感じているかを観察するようなものだと思います。

    for text, label in zip([low_text, mid_text, high_text], labels):
    print(f"{label}: ")
    print(nltk.ContextIndex(text).similar_words("Python"))
    

出力は以下になります。

    low: 
['する', '使う', 'やる', 'なる', '思う', 'できる', 'Java', '基礎', '書く', 
'言語', 'サンプル', '勉強', 'Jupyter Notebook', 'わかる', '作る', '初心者', 
'簡単', '実行', 'C++', 'プログラム']

mid: 
['プログラミング', '終える', '使う', 'スクレイピング', '欲しい', 
'三角形', 'Twitter', 'Java', 'C++', '是非', '専用アプリ', 'やる', 
'マクローリン展開', '作る', '短い', '勉強', 'デフォルト', 'Docker', 'OpenCV', 'RUBY']

high: 
['笑顔', '難易度', '英語', '思う', '解']
    

曖昧な側面もありますが、それぞれのクラスでそれとなくPythonに対するイメージが出ているように思えます。 何か目標がなければ、いいねの数が多めの語句に対して注目してみるのもよいかもしれません。 ただイメージに多様性を持ち分散しています。もう少しターゲットを絞って抽出を行うべきだったかもしれません。

word2vecを使ってクラスごとに検索語句と似た語句を抽出する

word2vecは学習したモデルを使用して、それぞれの単語をベクトル化します。 このベクトルは学習したモデルに使用されたテキスト内での意味を表現したもの(特徴)になります。 語句や文章の特徴をベクトル化することでNNの入力する形式にすることができるので、DLの入力としてよく使用されているかと思います。 ここでのword2vecのベクトル表現の観察はnltk.ContextIndexと同じく、 low~highのクラスごとにツイートした人物が検索語句Pythonと他のどのような語句と関連性が深いと感じているかを観察するようなものだと思います。

今回はgensimライブラリを用いてそれぞれのクラスでtrainingを行い、検索語句Pythonとベクトルの距離的に近いものを抽出することで、 どのような文脈でPythonが使用されているかを見ました。 gensim.models.Word2Vecはベクトル化のモデルとしてskip-gramとCBOWがありますが今回はデフォルトのskip-gramを使用しました。

skip-gramは着目した語句に対して周辺の語句を学習することで、着目した単語の周辺の語句を推測するモデルです。 普通は入力を着目語句、出力を周辺語句としてNNを用いて学習し中間層を着目語句の特徴ベクトルとして使用します。

    from gensim.models import Word2Vec
for text, label in zip([low_sentences, mid_sentences, high_sentences], labels):
    model = Word2Vec(vector_size=200, window=7, epochs=30, min_count=2)
    model.build_vocab(text)
    model.train(text, total_examples=len(text), epochs=30)
    print(f"{label}: ")
    print(*model.wv.most_similar(positive=["Python"], topn=20), sep="\n")
    

出力は以下になります。

    low: 
('自然言語', 0.7189319729804993)
('大半', 0.7108237147331238)
('強化学習', 0.70674729347229)
('めちゃくちゃ', 0.6967571377754211)
('駆る', 0.69488126039505)
('我流', 0.6933131217956543)
('完了', 0.6898371577262878)
('上げ', 0.6875693202018738)
('完全', 0.6864277124404907)
('UNIX', 0.684899091720581)

mid: 
('レベル', 0.9900107979774475)
('独学', 0.9866885542869568)
('基礎', 0.9835615158081055)
('AI', 0.9808483123779297)
('C言語', 0.9738596677780151)
('データサイエンス', 0.9724497199058533)
('読む', 0.9703874588012695)
('1年', 0.9694126844406128)
('IDE', 0.9680488109588623)
('必須', 0.9666246175765991)

high: 
('する', 0.9998970627784729)
('できる', 0.9998274445533752)
('コード', 0.9997736215591431)
('ある', 0.9997674226760864)
('プログラミング', 0.9997549057006836)
('使う', 0.9997478723526001)
('数学', 0.9997442960739136)
('機械学習', 0.9997415542602539)
('JS', 0.9997409582138062)
('やる', 0.9997333884239197)
    

word2vecもpythonと似通った語句を抽出しますが、nltk.ContextIndexと違った結果になりました。 右の数値は類似度になりますがmidとhighでは1.0に近い値をとっているので、学習に使用すたのテキストの量が少なかった可能性があります。 しかしこちらでも、それぞれのクラスでそれとなくPythonに対するイメージが出ているように思えますが、 nltk.ContextIndexと同じくイメージに多様性が存在しているようです。

word2vecを使ってwikipediaから見た検索語句と似た語句を抽出する

最後に比較としてすでに学習したモデルを使用して、Pythonと似た語句を抽出しました。 学習済みモデルはこちらを使用しました。 wikipediaテキストをコーパスとしてskip-gramを用いて学習したモデルになります。

    from gensim.models import KeyedVectors
model = KeyedVectors.load_word2vec_format("../jawiki.all_vectors.100d.txt")
print(*model.most_similar(positive=["Python"]), sep="\n")
    

出力は以下になります。 ツイートで学習した時と比べて、概念的にPythonを近いものが出力されているのがわかります。 生物とプログラムパッケージ的な概念を足し合わせたものというような感じになっています。

    ('Monty', 0.8415217399597168)
('##Python##', 0.7614157199859619)
("Python's", 0.761127769947052)
('Rhino', 0.7516815066337585)
('python', 0.7443267107009888)
('Weta', 0.7425349354743958)
('Rhinoceros', 0.7420356273651123)
('Armadillo', 0.7394540905952454)
('##モンティ・パイソン##', 0.7378020882606506)
('Wrapper', 0.736846923828125)
    

まとめ

今回はツイートの文脈的な特徴を抽出して分析を行いました。 いいね数ごとに分けてクラスごとの傾向がぼんやりと浮かび上がらせることができたと思います。 ただ、確実につかんだとは言い切れません。 これは前述したとおり抽出したツイートが多様性を持つという側面もあるかと思います。 例えば特定の人物のアカウントのツイートに焦点を当てて分析を行えばまた違った結果が得られたかもしれません。

Reference

  1. https://github.com/singletongue/WikiEntVec
Next Post Previous Post
No Comment
Add Comment
comment url