見出し画像

tf-idfでベクトル化したラジオ感想ツイートをクラスタリングして可視化する

機械学習の勉強がてら、ラジオ番組のレコメンド機能を作っています。番組の感想ツイートから類似のラジオを探せないかを試しています。

前回の記事↓

前回は感想ツイートからtf-idfで特徴量を抽出し、コサイン類似度を使って番組間の類似度を出すところまでできました。

今回は前回出せたデータの確認の意味も込めて、似ている番組同士をクラスタリングしてみます。「k-means法によるクラスタリング」をしたあと、「データの可視化」をして視覚的に結果を確認してみます。

※初心者が勉強しながら手を動かしたメモなので、間違っている部分があれば優しく教えてもらえるとうれしいですm

k-means法によるクラスタリング

データの準備

前回は5つのラジオ番組で試しました。今回はクラスタリングをしたいので、もう少し対象の数を増やしてみます。

# 対象にした番組と公式ハッシュタグ
Creepy Nutsのオールナイトニッポン0 (#cnann0)
DAYS (#days1242)
THE TRAD (#THETRAD)
ジュグラー (#ジュグラー)
菅田将暉のオールナイトニッポン (#菅田将暉ANN)
霜降り明星のオールナイトニッポン0 (#霜降り明星ANN0)
編集長 稲垣吾郎 (#編集長稲垣吾郎)
水瀬いのり MELODY FLAG (#melody_flag)
SixTONESのオールナイトニッポンサタデースペシャル (#SixTONESANN)
乃木坂46の「の」 (#のぎのの)
ONE MORNING (#ワンモ)
水溜りボンドのオールナイトニッポン0 (#水溜りボンドANN0)
オードリーのオールナイトニッポン (#annkw)
欅坂46 こちら有楽町星空放送局 (#欅坂46こち星)

前回の記事では最新ツイート100件で分析しましたが、100件では番組というよりも「放送」単位のトレンドが強く反映されていました。
そこで、今回は直近1,000件のツイートを取得して利用します。

まずはツイートの取得からです。

import tweepy

consumer_key = '{YOUR_CONSUMER_KEY}'
consumer_secret = '{YOUR_CONSUMER_SECRET}'
access_token = '{ACCESS_TOKEN}'
access_token_secret = '{ACCESS_TOKEN_SECRET}'

auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)

# wait_on_rate_limitにしてRate Limit対策!
api = tweepy.API(auth, wait_on_rate_limit=True)

def export(filename, full_tweet):
   path = "data/" + filename + ".txt"
   print(path)
   with open(path, mode='w') as f:
       f.write(full_tweet)

def get_tweets(hashtag):
   keyword = hashtag
   count = 1000
   
   query = keyword + ' -filter:retweets'
   tweets = []
   for tweet in tweepy.Cursor(api.search, q=query).items(count):
       tweets.append(tweet.text.replace('\n', ' '))
   full_tweet = " ".join(tweets)
   
   export(keyword, full_tweet)

tweepy を使ってこのように実装しました(ツイートの取得についての詳しい方法はこちらの記事に書きました)。

今回は番組数 x 1,000件の取得になるため、大量のリクエストになります。Twitter APIにはRate Limitが設けられており、制限を超えるとエラーになり15分ほど待つ必要があります。

その対策として、今回は tweepy.API を初期化するときに wait_on_rate_limit=True と指定しています。これによりRate Limitが復活したら再度叩く、というのを自動で行ってくれます。便利!!

tf-idfにより特徴量を抽出

1,000件のツイートから特徴量を抽出します。詳しい実装は前回の記事に任せるとして、今回のコードはこちら。

sample = np.array(tweets)

vectorizer = TfidfVectorizer(
                           max_df=0.9,
                           min_df=5,
                           max_features=1280,
                           tokenizer=mecaber.parse,
                           stop_words=['ann0'],
                           analyzer='word',
                           ngram_range=(1, 1))

# フィッティング                           
vectorizer.fit(sample)

# ベクトルに変換
tfidf = vectorizer.transform(sample)

TfidfVectorizerの初期化の際に色々とパラメータを渡しています。

・max_df: よく出る単語を特徴量から除く
・min_df: あまりに出ない単語を特徴量から除く
・max_features: 特徴量を1280個で表現する
・stop_words: 対象としない単語のリスト
・analyzer: stop_wordsを使う場合は "word" を指定
・ngram_range: 前後何単語を使うかを指定

今回の場合、特に効いたのは min_df と max_features でした。結果の一部を見てみます。

--------- #霜降り明星ANN0 の特徴 ---------

[('モノマネ', 0.2870733295149087),
('31', 0.22235644253465384),
('人気', 0.2090692075063604),
('tf', 0.19801693276599264),
('メール', 0.19289392594586907),
('金', 0.18454714040244133),
('配信', 0.1830095299842931),
('ラジオネーム', 0.18055977011912944),
('会', 0.17684127313083922),
('笑っ', 0.16203089779453003)]
--------- #のぎのの の特徴 ---------

[('たん', 0.685789460257597),
('乃木坂46', 0.38715720421517036),
('文化放送', 0.20543776202212274),
('ら', 0.17102931437142543),
('来週', 0.17102931437142543),
('高校生', 0.14851623219705787),
('エンディング', 0.14695488434091364),
('タイトル', 0.10578895994758501),
('😍', 0.10307011128122023),
('かわいい', 0.09915331157190942)]

それらしい特徴が出ているように見えます。直近100件のツイートを扱ったときには時期性の単語が多かったですが、1,000件に増やしたことで番組自体を表す単語も増えているような気がします。

k-meansクラスタリング

番組ごとの特徴量が出せたところで、クラスタリングをしてみます。

クラスタ数(いくつのグループに分類するか)はひとまず3にしてみました。

from sklearn.cluster import KMeans

# k-meansでクラスタ分析。とりあえず3つのグループに分けてみる
km_model = KMeans(n_clusters=3)
km_model.fit(tfidf)

# クラスタリング結果の確認
for index, label in enumerate(km_model.labels_):
   print("{} -> {}".format(filenames[index], label))

特徴量 tfidf でフィッティングすると、km_model.labels_ にクラスタの値が入ります。確認してみます。

#ジュグラー -> 1
#霜降り明星ANN0 -> 2
#欅坂46こち星 -> 1
#編集長稲垣吾郎 -> 1
#のぎのの -> 1
#ワンモ -> 1
#days1242 -> 1
#melody_flag -> 1
#fm802.____txt -> 1
#菅田将暉ANN -> 2
#水溜りボンドANN0 -> 2
#SixTONESANN -> 1
#THETRAD -> 1
#annkw -> 0
#cnann0 -> 0

0, 1, 2 にクラスタ分類されました。このままだと分かりにくいのでグラフにプロットしてみます。

クラスタリングを可視化する

from mpl_toolkits.mplot3d import Axes3D

df = km_model.transform(tfidf)

# 3次元でプロット
fig = plt.figure()
ax = Axes3D(fig)

# 分かりやすくクラスタで色分け
color_codes = {0: '#00FF00', 1: '#FF0000', 2: '#0000FF'}
colors = [color_codes[x] for x in km_model.labels_]

ax.scatter(df[:, 0], df[:, 1], df[:, 2], alpha=0.8, color=colors)

plt.show()

画像1

3次元でプロットしてみました。色がクラスタを表しています。
これでも確認できていますが、もう少し可視化に手を入れてみましょう。

今回は3次元でしたが、もっと高い次元のデータを扱うときも多くあります。データ分析において可視化は重要ですが、高次元のデータをグラフにするのは難しい。そこで、次元削減を行います。

次元削減は、高い次元のデータを特徴はそのままに低い次元で表現するもので、いろいろな方法があります。今回は主成分分析(PCA)を使い、2次元にプロットしてみます。

主成分分析(PCA)

# 主成分分析
pca = PCA(n_components=2)
pca.fit(df)

feature = pca.transform(df)

これだけです。n_components=2 により2次元にすることを指定しています。

得られた2次元の値 feature をプロットします。

# プロットする
plt.figure(figsize=(8, 8))

for x, y, name in zip(feature[:, 0], feature[:, 1], filenames):
   plt.text(x, y, program_title(name), alpha=0.8, size=12)

plt.scatter(feature[:, 0], feature[:, 1], alpha=0.8, color=colors)

plt.title("主成分分析")
plt.xlabel("第一主成分")
plt.ylabel("第二主成分")
plt.show()

画像2

ラベルが重なって見にくいですが、2次元のプロットで表現できていることが分かります。

未知のデータをクラスタ分けしてみる

k-meansでクラスタ分類ができたので、未知のデータについてもクラスタ分けできるようになったはず。

僕の好きな番組のひとつ「バナナマンのバナナムーンGOLD」(#bananamoon)を入力として与え、k-meansのモデルがどの分類にカテゴライズするかを確認してみます。

# test_tweets は #bananamoon の感想ツイートを文章にしたもの
test_sample = np.array(test_tweets)

vectorized_sample = vectorizer.transform(test_sample)

# 学習したモデルで分類
result = km_model.predict(vectorized_sample)

# クラスタ結果の表示
for index, label in enumerate(result):
   print("#bananamoon -> {}".format(label))

結果は「 #bananamoon -> 2 」となりました。

クラスタ2はさっきのグラフの「青」の点です。
#霜降り明星ANN0、#水溜りボンドANN0などが入っているクラスタ。

僕は霜降り明星ANN0も好きですが、どちらも大変面白い笑えるラジオです。感覚的には近い属性があると思うので、良い分類ではないでしょうか。

水溜りボンドANN0は聴いたことないですが、試しに聴いてみようと思います!

おまけ:最適なクラスタ数を見つける(エルボー法)

k-meansでクラスタ分類をする際、決め打ちで3つのクラスタに分類しました。最適なクラスタ数を調べる方法があるのでやってみます。今回は「エルボー法」を試します。

エルボー法とはクラスタ内誤差平方和(SSE)を用いてグラフを作り、肘のようにガクっと下がった箇所を最適なクラスタ数とする方法。

グラフの形状として例えば↓のようになっていた場合、最適なクラスタ数は3であるということができる(参考: k-meansの最適なクラスター数を調べる方法)。

画像3


では実際に今回のデータでグラフを作ってみよう。

# 最適なクラスタ数を求める(K-means)
distortions = []

# 1~6の中から見つける
for i in range(1, 6):
   km = KMeans(n_clusters=i, n_init=10, max_iter=300, random_state=0)
   km.fit(tfidf)
   distortions.append(km.inertia_)
   
plt.plot(range(1, 6),distortions,marker='o')

plt.xlabel('クラスタ数')
plt.ylabel('Distortion')
plt.show()

画像4

全然エルボーのないグラフになった。調べてみたが、エルボーのように綺麗な形にならないケースもあるらしい。このあたりはもう少し勉強が必要。

まとめ

まとめです。

前回までに出したtf-idfで抽出した特徴量を使い、クラスタリングをしてみました。

最終的にはレコメンドエンジン(好きなラジオを入れたら似ているラジオを推薦してくれる)が作りたいのですが、データを可視化していくことが重要だと多くの本に記されているのでデータを視覚的に把握することをやってみました。

未知のデータである「バナナムーンGOLD」のクラスタ分類もできました。学習に使うデータの数も増やしつつ、まったく知らない番組を教えてくれるものを作れればと思っています。
とりあえず水溜りボンドのオールナイトニッポン0を聴いてみます!

参考リンク

↑主成分分析(PCA)の勉強になりました。

↑k-means法のクラスタリングはこちらの本が大変分かりやすかったです。

この記事が気に入ったらサポートをしてみませんか?