ナード戦隊データマン

データサイエンスを用いて悪と戦うぞ

マルコフクラスタリングによるニュース分類

ニュース・イベントのインスタンスとは、同一のイベントを参照する記事のことです。ここでは、教師なし学習の一つであるマルコフクラスタリングを使ってイベントインスタンスの分類を試します。

手法の概要

ここで説明する手法は以下の手順を取ります。

  1. BoWで文書をベクトル化。
  2. 類似度行列を生成。
  3. マルコフクラスタリングアルゴリズムに類似度行列を入力し、クラスタリング結果を得る。

事前に以下を実行してください。

pip install markov_clustering

Jupyter notebookで実行

afp通信からスクレイピングした約60000件のデータを読み込みます。

In[1]:

import pandas as pd
df = pd.read_csv("afp.csv")

ターゲットとなる文書を、基準時間から半径12時間以内の記事から取得します。

In[2]:

import numpy as np
from functools import partial
from dateutil import parser

def diff_date(t1, t2):
    if t1 < t2:
        tmp = t1
        t1 = t2
        t2 = tmp
    return (t1-t2).to_timedelta64().astype("timedelta64[h]")/np.timedelta64(1,'h')

def within(t1, base, hour=12):
    return diff_date(t1, base) <= hour

def get_around_12(df, base, hour=12, column="day_fixed"):
    func = partial(within, base=base, hour=hour)
    return df[list(map(func, df[column].tolist()))]

def get_day(start, plus):
    return pd.Timestamp(start + datetime.timedelta(days=plus))

start = datetime.datetime(2017,2,1,12,0,0)
day = get_day(start, 0)
targets = get_around_12(df, day, hour=12*30)

タイトルと本文の最初の一文を結合し、トークナイズします。

In[3]:

import  MeCab
def extract_data(d, tagger, column1, column2):
    return tagger.parse(d[1][column1] + " " + d[1][column2].split("。")[0])

tagger = MeCab.Tagger("-Owakati")
func = partial(extract_data, tagger=tagger, column1="title", column2="body")

df['data'] = list(map(func, df.iterrows()))

あとはクラスタリングだけです。

In[4]:

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import markov_clustering as mc

count_vectors = CountVectorizer().fit_transform(targets['data']).toarray()
similarities = cosine_similarity(count_vectors)
result = mc.run_mcl(similarities)
clusters = mc.get_clusters(result)
print(clusters)

Out[4]:

...[略]
(6, 116, 251, 395, 458, 493, 553),
 (7, 11),
 (18,),
 (26, 35, 36),
 (29, 228),
 (37, 119, 120),
 (59, 281, 336),
 (65, 526, 541),
 (70,),
 (72, 105, 209, 260),
 (84, 92),
 (86,),
 (95,),
 (96,),
 (97, 446, 464),
 (98,),
 (107, 285),
 (109,),
 (124,),
 (142,),
 (149, 436, 556),
 (155, 264, 302, 459, 463),
 (156,),
 (190,),
 (195,),
 (204,),
 (216,),
 (236,),
 (256,),
 (275,),
 (301,),
 (306,),
 (312, 352, 564),
 (334,),
 (347,),
 (378,),
 (435, 509),
 (453,),
 (457, 507),
 (465,),
 (539,),
 (543,),
 (544,),
 (554,),
 (569,),
 (576,)]

クラスタの一つを見てみます。

In[5]:

print(targets.iloc[312]['data'])
print(targets.iloc[352]['data'])
print(targets.iloc[564]['data'])

Out[5]:

マレーシア 沖 の 沈没 事故 、 捜索 範囲 を 拡大 船長 ら を 拘束 マレーシア 沖 で 発生 し た 中国人 観光 客 5 人 と マレーシア 人 乗組 員 1 人 が 行方 不明 と なっ て いる 船 の 沈没 事故 で 、 救助 隊 は 30 日 、 捜索 範囲 を 拡大 し た 

中国人 観光 客 28 人 乗せ た 船 、 マレーシア 沖 で 行方 不明 に マレーシア の ボルネオ 島 ( Borneo Island 、 別名 : カリマンタン 島 、 Kalimantan Island ) の 沖合 で 、 中国人 観光 客 28 人 を 含む 31 人 を 乗せ た 船 が 行方 不明 と なっ た 

不明 マレー 機 、 捜索 打ち切り 真相 は 謎 の まま ( 更新 ) 2014 年 に 消息 を 絶っ た マレーシア 航空 ( Malaysia Airlines ) MH 370 便 について 、 捜索 活動 の 打ち切り が 17 日 、 発表 さ れ た 

考察

CountVectorizerでベクトル化しているので、同じ語を含んでいると同一のインスタンスとして分類されやすくなります。ただ、上記例を見ればわかるように、似ているけどぜんぜん違うイベントを参照していることがあります。ただ、この手法の場合、Word2Vec等を使っても正しく分類されなかったので、CountVectorizerを使いました。

「イベント」の定義をもっと広く捉え、「マレーシア 行方不明」のように捉えるなら、分類できていると言えるかもしれません。しかし、この手法は類似度を取っているだけなので、時間が考慮されていません。

時間を考慮するための前処理(特定の時間範囲の記事を取得する)を行いましたが、モデルそのものに時間を組み込むには、各類似度を以下で割ると良いと思います。

exp(diff_date(d1, d2)/24)

リンク

https://github.com/sugiyamath/credibility_analysis/blob/master/notebook/markov_clustering_experiments.ipynb