2018年8月21日火曜日

階層的クラスタリングでユークリッド距離とコサイン類似度の結果を比べてみる

階層的クラスタリングでは距離や類似度をもとにクラスターを作っていくわけだけど、距離や類似度にはいくつか種類があって、分析に適したものを選択することになる。距離としてはユークリッド距離が使われる例がたくさんある一方で、テキスト分析ではコサイン類似度がよく使われるらしい。じゃぁ実際のところ、この2つはどう違うのだろうかというのが疑問で、実際に同じデータをPythonのScipyで階層的クラスタリングして比べてみた。


環境


Bash on Ubuntu on WindowsとJupyter Notebook。



ユークリッド距離とコサイン類似度


ユークリッド距離は、一般的に言うところの2点間の距離なので感覚的に理解できる。今回はScipyで階層的クラスタリングをするけれど、デフォルトはユークリッド距離になっている。一方のコサイン類似度は2つのベクトルによる角度をθとしたときのcosθで、θが0度のときは1,90度のときは0になる。つまり角度が小さいほど類似度が1に近づいて高くなる。コサイン類似度(高校数学の美しい物語)の説明がわかりやすい。


データの準備


階層的クラスタリングするためのデータを準備する。データはscikit-learnのirisデータセットを使う。まずはデータセットをPandasのDataframeにして正規化する。
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.preprocessing import MinMaxScaler

%matplotlib inline
import matplotlib.pyplot as plt

def iris_df():
    # scikit-learnのirisデータセット読み込み
    iris = load_iris()
    # irisデータセットをPandasのDetaFrameに変換
    df = pd.DataFrame(data= np.c_[iris['data'], iris['target']], columns= iris['feature_names'] + ['target'])

    # 正規化
    items = df.columns
    index = df.index
    df = pd.DataFrame(MinMaxScaler().fit_transform(df))
    df.columns = items #カラム名を再設定
    df.index = index # インデックス名を再設定
    
    return df

if __name__ == '__main__':
    df = iris_df()
    print(df)

以下のようなDataFrameになる。


Pythonで階層的クラスタリング


Scipyのlinkageで階層的クラスタリングを行い、dendrogramでデンドログラム(樹形図)を作成する。まずはユークリッド距離から。比較しやすいように使うデータはpetal length (cm)とpetal width (cm) の2次元のみにする。クラスタリング手法はウォード法。

import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.preprocessing import MinMaxScaler
from scipy.spatial.distance import pdist
from scipy.cluster.hierarchy import dendrogram, linkage

%matplotlib inline
import matplotlib.pyplot as plt

def draw_dendrogram(Z, labels):
    # デンドログラムの作成
    plt.figure(figsize=(14, 5))
    plt.ylabel('Distance')
    dendrogram(
        Z,
        leaf_rotation=90.,
        leaf_font_size=8.,
        labels=labels
    )
    plt.show()

if __name__ == '__main__':
    df = iris_df()
    print(df)

    # petal length (cm)とpetal width (cm) で階層的クラスタリング
    Z = linkage(pdist(df[df.columns[2:4]].as_matrix(), metric='euclidean'), method='ward')
    draw_dendrogram(Z, df.index.values)


次はユークリッド距離の代わりにコサイン類似度で階層的クラスタリングをする。

import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.preprocessing import MinMaxScaler
from scipy.spatial.distance import pdist
from scipy.cluster.hierarchy import dendrogram, linkage

%matplotlib inline
import matplotlib.pyplot as plt

def draw_dendrogram(Z, labels):
    # デンドログラムの作成
    plt.figure(figsize=(14, 5))
    plt.ylabel('Distance')
    dendrogram(
        Z,
        leaf_rotation=90.,
        leaf_font_size=8.,
        labels=labels
    )
    plt.show()

if __name__ == '__main__':
    df = iris_df()
    print(df)

    # petal length (cm)とpetal width (cm) で階層的クラスタリング
    Z = linkage(pdist(df[df.columns[2:4]].as_matrix(), metric='cosine'), method='ward')

    draw_dendrogram(Z, df.index.values)



階層的クラスタリングの結果を比較してみる


デンドログラムだとわかりずらいので散布図で比較してみる。
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.preprocessing import MinMaxScaler
from scipy.spatial.distance import pdist
from scipy.cluster.hierarchy import dendrogram, linkage, fcluster

%matplotlib inline
import matplotlib.pyplot as plt

def draw_scatter(df):
    # targetごとに色分けしてプロットする
    groups = df.groupby('fcluster')
    for status, group in groups:
        plt.plot(group[df.columns[2]], group[df.columns[3]], marker='o', linestyle='', ms=4)
    plt.grid(True)
    plt.show()

if __name__ == '__main__':
    df = iris_df()
    print(df)

    metric_t = ('euclidean', 'cosine') # ユークリッド距離、コサイン類似度
    th_t = (4.0, 0.6) # 分割する距離の閾値
    for metric, th in zip(metric_t, th_t):
        # petal length (cm)とpetal width (cm) で階層的クラスタリング
        Z = linkage(pdist(df[df.columns[2:4]].as_matrix(), metric=metric), method='ward')
        draw_dendrogram(Z, df.index.values)

        # 距離をもとにフラットクラスターを作成
        df['fcluster'] = fcluster(Z, th, criterion='distance')

        draw_scatter(df)

ユークリッド距離を使ったときの結果。

コサイン類似度を使ったときの結果。

コサイン類似度のときはベクトルの角度で分割されているのがわかる。

全体コードはこちら

0 件のコメント:

コメントを投稿