LoginSignup
8

More than 1 year has passed since last update.

posted at

updated at

みんなコロナ禍(下)で“旅行”をどう考えてんの??を分析した件 ~秒でトピック分類~

対象読者

本投稿は、以下のようなテーマに関心がある!あるいは実装したい!というかたにうってつけです。

  • 青い鳥のSNSをさくっと自由にスクレイピングしてみたい
  • 日本語の自然言語処理前処理のお作法をかるく知りたい
  • トピックモデルのひとつ「LDA」を実装し、わかりよく可視化してみたい

自己紹介

はじめまして、三度の飯より味噌が好き、味噌アライグマと申します(ここでは味噌は調味料といたします)
 スイカもたしなむ

わたくし、勤め先にて旅行にかんする営業を担当している関係で、かねてよりマーケティング手法としてのデータサイエンスに高い関心を抱いておりました

たとえば社内で旅行企画ひとつ提案するにしても、経験知にたよるのみでなく、データをもとにそれなりの論拠をもってつよくアピールできればな、などと常日頃考えており・・・
巷で勢いあるAI・データサイエンスなどのスキルは、きっとその後押しをしてくれるだろうと考えていたのです


「いえ!データによりますと・・・これがウケます!!(はい論破w)」

そこで一念発起、AIスクールとして有名どころであるAidemyさんのPremium Plan受講を決断いたしました。



まったくのデータサイエンス初心者であった味噌アライグマでしたが・・
Aidemyさんによる愛ある(?)教育をとおし、プログラミング言語“python”の基礎から機械学習モデルの選定・学習・性能評価まで、ひととおりのスキルを網羅的に身につけることができたと自負しております(まだまだ修行中の身ではございますものの)。


さて、これより先は、Aidemyさんでの成果物としてトライした事のひとつ、すなわち表題にありますとおり、「日本のみなさんはコロナ状況下で“旅行”についてなにを考えているのか?を分析してみた」 際の方法と、その結果・考察についてご報告したいとおもいます。

やったこと

旅行関係という勤め先の業界柄、コロナのあおりをはげしくうけております。
端的に、「やっちまったなあ!」というかんじです

しぜん、不肖わたくし味噌グマは、このような状況下で世間の皆さまが「旅行」一般についていったいなにを考えていらっしゃるのかに、おおいなる関心を抱いたのです。
またあわよくば、マーケティングに活かせないかなぁ、などと画策したり・・

そこで、Aidemyさんにおける学習成果物として・・
「旅行」にかんする「大衆意見」の「傾向」的なものをスマートに把握するため、以下のような実験を考案いたしました。

  1. 「SNS」から「旅行・コロナ」と同時につぶやいているものをスクレイピングする
  2. 自然言語処理ツールによる前処理をほどこす
  3. 「教師なし学習」の一手法「LDA」によって、「旅行・コロナ」のつぶやき群からトピック(観点あるいはテーマ)を抽出してくる

おっと、すでに「スクレイピング」・「教師なし学習」・「LDA」など、とくに初心者の方には耳慣れない用語を並べてしまったようにおもいます(ドヤァ)

次の章では、それらもふくめ、わたくしの実験をばくっと理解するのにひつような前提知識たちを導入しておきます(「とくに問題ないで~^^」という方は、「実装と結果」の章までスキップ頂くとよいかと存じます)

前提知識

スクレイピング

Web上に存在する情報から必要なものをかき集めることを指します。
たとえばこれを一発しかけるだけでも、やりようでビッグなデータを相手にした分析が可能となります。
今回は、コロナ禍(下)での多くのひとの旅行についての考えを収集したかっったので、青い鳥マークで有名な某SNSを対象にスクレイピングを実行しました。
具体的手法については後述いたします。

教師なし学習

学習データに正解を与えない状態で、モデルに学習をさせる手法です。
おおむねその目的は、データ内に存在している未知のパターンを見つけ出すことにあり、俗に言う「クラスタリング」など、データを勝手に(いい感じに)グルーピングしてもらいたいときなどに多用します。
今回は、茫漠と「コロナ+旅行」という概念について、みなさんの観点を分類してみたかったので、自然言語で有名な教師なし学習の一種、トピックモデル(後述)を使用しました。



自然言語処理

機械学習の一分野です。人間が日常的に使っている言語をルールに則り前処理することで、言葉を機械が処理できるようにします。今回は青い鳥SNSのつぶやきを対象データとした機械学習をおこないましたので、ジャンルとしては自然言語処理にあたります



トピックモデル

ある文書がどんなトピック(話題、あるいは観点)に関する内容なのかを判定(帰属)する機械学習モデル全般を指します。
たとえば、ニュース記事ひとつをとっても、政治・経済・スポーツはたまた芸能ゴシップと多用なトピックが存在しますよね。
今回は青い鳥SNSのつぶやきをトピックモデルによって分類することを試みました。



LDA

本投稿で使用したトピックモデルです。
LDAとは "Latent Dirichlet Allocation" の略語で、本来的には文章中に「ある単語が出現する確率」を推定するモデルといえます。
まず、LDAにおいてはひとつの文章は複数の仮のトピックに属するものと考えます。なお仮のトピック群は任意の数だけ設定します。
つぎに、分析であつかうすべての単語について、上記の単語出現確率を(仮の)トピックごとに推定しようとこころみます(仮トピックごとに単語の出現確率が書かれた表を埋めていくようなイメージです)。
さいごに、(逆転的発想で)実際の文章群中で、ある仮トピックにおける高確率単語の出現回数が多いとき、その仮トピックはもっともらしい正式トピックとして採用する、というような考え方をします(ついでに「この文章はどれだけトピックAに、どれだけトピックBに属する」というような帰属的なこともおこなえる)
上記の仕組みで、文章群をあたえ学習させると、そこに存在するトピックを単語が共起する確率とともに返してくれるのです。

実装と結果

環境

  • 開発環境:Google Collaboratory

    • Hardware Accelerator: None
    • Runtime Shape: Standard
  • 日本語解析ツール:mecab-python3-0.7

  • スクレイピングツール:twint

環境につきまして、上から順にコメントしてまいります。

  1. より多くの方に気軽に再現して頂きたく、今回はローカルでなく、Google Colabを使用しております。
  2. 日本語解析ツールとして、今回はmecabを使用しております(他にjanomeなどがメジャーですが、好みでよいかと存じます)
    注意点としまして、Colabにはmecabはプリインストールされておらず、手動でのインストールが必要となります。 以下を順に実行し、適切なインストールを試みていただけますと幸いです。
!apt install aptitude
!aptitude install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file -y
!pip install mecab-python3==0.7

3. 青い鳥SNSからのスクレイピングには、pythonで書かれた高度なツール"twint"を使用いたしました。
 ただしこちらはローカルにて使用しております。
 インストールはターミナル(ないしgit bashなどで)以下のように可能です(pip3が入っていることが前提です!)

pip3 install twint

実装

順を追ってご説明します。

まず、青い鳥アイコンのSNSからから、上述のTwint:Pythonベーススクレイピングツールを用い、以下条件に絞ってつぶやきをかき集め、.csv形式で保存いたしました。

  • 「コロナ」「旅行」をかならず含む
  • 2020/1/1 以降に投稿されている
  • いいねが5件以上ついている

具体的には、以下をターミナルにて実行したのみです。
アカウントすら必要なく、めちゃくちゃ簡単でした。

twint -s "コロナ, 旅行" --since 2020-01-01 --min-likes 5 -o covid_travel_sinse2020.csv --csv

結果、92908件のつぶやきを収集いたしました。
出力先は“covid_travel_sinse2020.csv”と命名しております。

twintの使用法は以下にくわしいです。
https://github.com/twintproject/twint/wiki/Basic-usage

なお、いいね数5件以上にしぼった理由は、集まってくるつぶやきたちが(「正常」なトピック分類を阻害しない程度に)あまりにクセ強でないことを担保できるかな、と考えたためです。

データがあつまりましたので、以降は完全にColab環境にて開発しております。

まずは必要なモジュールのインポートです。

import pandas as pd
import MeCab
import gensim
import numpy as np
import matplotlib.pyplot as plt

先述のMecabインストールが済んでいないと、ここで持ってくることはできませんので、ご注意を。

次に、さきほどスクレイピングしたつぶやきファイルをDataFrameにします。(Driveのマウントをお忘れなく!)

df = pd.read_table("covid_travel_sinse2020.csv")

不要なカラムをガンガン削ります。2度に分けていることに特に意味はありません。

df = df.drop(['id', 'conversation_id', 'created_at', 'timezone', 'user_id', 'name', 'place', 'geo', 'source', 'user_rt_id', 'user_rt', 'retweet_id', 'reply_to', 'retweet_date', 'translate', 'trans_src', 'trans_dest'], axis=1)
df = df.drop(['language', 'mentions', 'urls', 'photos','hashtags', 'cashtags', 'link', 'retweet', 'quote_url', 'video', 'thumbnail', 'near'], axis=1)

ここらでつぶやきがきちんと取得できているか、ちょっぴりのぞいてみましょう。

df.head()

image.png
いろんな方が、いらっしゃいますね。。
いろんなつぶやきが、ありますね。。
でも今はあんまり考えすぎず先にすすみましょう。9万件ありますから

さあ、ここからはLDAが処理できるよう、つぶやきたちを加工して参ります。

まずは形態素解析用の関数を定義します。
Mecabを使用しておりますが、とくに変わったことはしておりません。
品詞分類までできた結果は、一度DataFrameにしてしまう予定です。

def parse(tweet_temp):
    t = MeCab.Tagger()
    temp1 = t.parse(tweet_temp)
    temp2 = temp1.split("\n")
    t_list = []
    for keitaiso in temp2:
        if keitaiso not in ["EOS",""]:
            word,hinshi = keitaiso.split("\t")
            t_temp = [word]+hinshi.split(",")
            if len(t_temp) != 10:
                t_temp += ["*"]*(10 - len(t_temp))
            t_list.append(t_temp)

    return t_list

def parse_to_df(tweet_temp):
    return pd.DataFrame(parse(tweet_temp),
                        columns=["単語","品詞","品詞細分類1",
                                 "品詞細分類2","品詞細分類3",
                                 "活用型","活用形","原形","読み","発音"])

つぎは、つぶやきたちから一般名詞と固有名詞のみを抽出し、それら単語をBag-of-Wordsで出力する関数を定義します。

def make_docs_for_lda(texts):
    docs = []
    for i, text in enumerate(texts):
        print(str(i+1) + " th parse START")
        df = parse_to_df(text)
        extract_df = df[(df["品詞"]+"/"+df["品詞細分類1"]).isin(["名詞/一般","名詞/固有名詞"])]
        extract_df = extract_df[extract_df["原形"]!="*"]
        doc = []
        for genkei in extract_df["原形"]:
            doc.append(genkei)
        docs.append(doc)
    return docs

ではじっさいにつぶやきを加工してあげます
9万件なので、わりと果てしないです。無我です。
Colabの「90分ルール」などお気をつけて。

texts = df["tweet"].values # ndarrayにしてあげる
docs = make_docs_for_lda(texts)

ここで心配性な味噌グマはいったんセーブしたりします。
けっこう時間かかりましたからね。

import pickle

with open("./docs_01", mode="wb") as jugemu:
    pickle.dump(docs, jugemu)

"""
docs = []
with open("./docs_01", mode="rb") as gokoh: 
    docs = pickle.load(gokoh)
"""

おつぎはLDAのための辞書とコーパス作成 です。
ここも特に変わったことはしておりません。gensimで一発です。
セーブ癖は健在です

dictionary = gensim.corpora.Dictionary(docs)

corpus = [dictionary.doc2bow(doc) for doc in docs]
corpus = []
for i, doc in enumerate(docs): # gonna rep 92908 times
    corpus.append(dictionary.doc2bow(doc))
    print(str(i+1) + " th doc2bow DONE")


with open("./coupus_01", mode="wb") as kaijarisuigyo:
    pickle.dump(corpus, kaijarisuigyo)

"""
corpus = []
with open("./coupus_01", mode="rb") as suigyomatsu: 
    corpus = pickle.load(suigyomatsu)
"""

ではおまちかね、LDAの学習です。
「前提知識」セクションでご説明のとおり、トピック数は任意です。
今回は本当に勘でに設定してみました。

n_topics = 6
lda = gensim.models.ldamodel.LdaModel(corpus=corpus,
                                      id2word=dictionary,
                                      num_topics=n_topics,
                                      random_state=0)

いろいろと定量的な分析も試みましたが、うまくまとまらなかったため(汗)、定性的分析で締めることにいたしました。
そうなると、やはりWordCloudによる可視化がファーストチョイスかなとおもい、迷わず実装いたしました。


def normarize_then_square(x, alpha, axis=None):
    #2次元配列を列方向に正規化し2乗する関数
    x_min = x.min(axis=axis, keepdims=True)
    x_max = x.max(axis=axis, keepdims=True)
    return ((x - x_min) / (x_max - x_min))**2 + alpha

def weight_dict(dict, alpha):

    dict_arr = np.random.randn(1,2)
    #print(dict_arr.shape)

    for i, value in dict.items():
        row_i = np.array([i, value]).reshape([1,2])
        dict_arr = np.block([[dict_arr], [row_i]])
        #print(dict_arr.shape)

    dict_arr = dict_arr[1:,:]
    #print(dict_arr.shape)
    #print(dict_arr)

    keys = dict_arr[:,0] .reshape([len(dict),1])
    values = dict_arr[:,1].reshape([len(dict),1]).astype('float64')

    values_weighted = normarize_then_square(values, alpha, axis=0)
    arr_weighted = np.concatenate([keys, values_weighted], 1)
    #print(arr_weighted)
    #dict_weighted = dict(arr_weighted)

    return arr_weighted

from wordcloud import WordCloud
import math
FONT = "C:/Windows/Fonts/07YasashisaAntique_0.otf"

ncols = math.ceil(n_topics/2)
nrows = math.ceil(lda.num_topics/ncols)
fig, axs = plt.subplots(ncols=ncols, nrows=nrows, figsize=(15,7))
axs = axs.flatten()

def color_func(word, font_size, position, orientation, random_state, font_path):
    return 'darkturquoise'

for i, t in enumerate(range(lda.num_topics)):

    print(str(i)+ ":" + str(t))
    x = dict(lda.show_topic(t, 40))
    x.pop('コロナ')
    try:
        x.pop('ウイルス')
    except KeyError:
        pass
    try:
        x.pop('ウィルス')
    except KeyError:
        pass
    print(x)
    #x = dict(weight_dict(x,0.01))
    print(x)
    im = WordCloud(
        font_path=FONT,
        background_color='white',
        color_func=color_func,
        random_state=0
    ).generate_from_frequencies(x)
    axs[i].imshow(im)
    axs[i].axis('off')
    axs[i].set_title('Topic '+str(t))

plt.tight_layout()
plt.savefig("./visualize.png")

結果

以下のとおりでした!!

output_31_2.png

なお「WordCloud」は、特定トピックにおける出現確率のより高い単語をより大きく表示するよう、うまいぐあいに1枚の図に収めたものです(縦書き、横書きのちがいに意味はありません)
あるトピックがどういった性質をもつのか視覚化しやすいので、トピック分類の定性的分析にはもってこいなのです。

考察

WordCloudによる可視化結果から、各トピックが何を示すか、さらりと推測してみました。

  • Topic 0: 感染源、または水際対策
    「中国」や中国国内特定地名、「マスク」あるいは「クルーズ」、などに着目しました。「日本に入るんか!?入らへんか!?入るんか~い!!!」みたいな時代もありましたねえ(遠い目)。。

  • Topic 1: 国内地域別状況への関心、あるいはあおりを受けた業界への注目
    国内地名、「株」・「ホテル」・「航空」・「見送り」などの単語が目立ちました。どこは大丈夫・どこは危険、はたまた解雇やら倒産やら、暗いニュースにかんするつぶやきが多く成分になっていそうです。直視するにつらいものがありますね。

  • Topic 2: 海外、特に東アジアへの興味関心
    2021年度初期・「春節」の頃穴場の地域であったり、渡航可能な目的地の候補挙げて指している。ように見えました。「沖縄」なども流行りましたね。いっぽう「月」は謎です。こういうときは、個々のツイートを見てみないといけませんね。

  • Topic 3: 「わたくしごと」としてのコロナ蔓延
    Topic0と近いようにおもいました。ただ、病状にかんする単語「新型」「肺炎」「症状」「患者」、あるいはなんらか政治的レスポンスを想起させる単語(「政府」「国民」)から、多くの方がコロナが自分自身にも降りかかること意識しはじめ、いよいよ危機感・焦燥感をつのらせている、そういった観点に見えます。

  • Topic 4: 各国の状況および対応
    各国の状況が明らかになった時点のツイートがおもな成分ではないでしょうか。(例:「台湾」での優秀人材による封じ込め成功と先進国「イタリア」の早期医療崩壊の対比、あるいは我が国のトップによるユニークな政策)

  • Topic 5: 私生活の変化・心情、あるいは適応
    見るからに、コロナで我慢を強いられることになった事柄への心情を吐露している群でしょう。「イベント」「修学旅行」「思い出」「楽しみ」、どの単語をみても胸がふたがるような気がします。しかしこういった赤裸々な観点こそマーケティングに活かしやすいのではないかとも考えました。また一部は「自分なりに適応しだしたよ」いう観点かもしれません。

Future Plan

以下は次にためしてみたいことのリストです。

  • 時期をずらしてみる。第n波ごとに時期を分割してみる
  • 別のクエリの組み合わせで現れる変化を試す
  • 特定トピックを掘り下げるような分析をする

おわりに

教師なし学習、トピックモデルの概念や解釈や難しかったりしましたが、Aidemyさんでまなんだ知識と技術をフル活用でき、われながら良い題材を選んだものだと関心(?)しました。
いっぽうとくにLDAのトピック分類結果は解釈がつきづらいこともしばしばだと、多くの文献で目にしましたが、今回はきれいな結果が得られたように見え、感動しております。まだまだ分析の余地はありますが。

今後も学習を継続してまいります。

ご高覧いただきありがとうございました。

参考文献

文献自然言語処理による文書分類の基礎の基礎、トピックモデルを学ぶ
https://qiita.com/icoxfog417/items/7c944cb29dd7cdf5e2b1
【入門】トピックモデルとは
https://spjai.com/topic-model/
トピックモデルの学習で初学者に分かりづらいポイントについての解説
https://acro-engineer.hatenablog.com/entry/2017/12/11/120000 MeCab で形態素解析をしてみよう
https://toukei-lab.com/natural-language-python
LDA によるトピック解析https://qiita.com/Spooky_Maskman/items/0d03ea499b88abf56819
PythonでLDA(トピックモデル)の実装
http://mathshingo.chillout.jp/blog27.html
LDA の学習、WordCloud
https://ie110704.net/2018/12/29/wordcloud%E3%81%A8pyldavis%E3%81%AB%E3%82%88%E3%82%8Blda%E3%81%AE%E5%8F%AF%E8%A6%96%E5%8C%96%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6/

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
What you can do with signing up
8