PythonによるScikit-Learnを用いた多次元スケーリング入門

このガイドでは、多次元尺度法(MDS)について説明します。

このガイドでは、多次元尺度法(MDS)と呼ばれる次元削減、データ埋め込み、データ可視化の技法について説明します。

Scikit-Learnは非常にシンプルで強力なAPIを持っているので、Multidimensional Scalingを実行するためにScikit-Learnを利用することにします。

このガイドでは、AT&TのOlivetti facesデータセットを使用して、低次元空間へのデータの埋め込みを説明します。

このガイドの終わりには、多次元尺度法とそのハイパーパラメータ、およびそれらがこの手法に与える影響について、しっかりと理解できるようになっています。

多次元尺度法とは?

MDSとは、データを低次元空間に埋め込むための非線形技法です。

高次元空間に存在する点を、その点間の距離をできるだけ保持したまま、低次元空間に写像するものである。

このため、低次元空間における点間の対の距離は実際の距離に近くなる。

下図は、3次元空間から2次元空間、1次元空間への点のマッピングの可能性の例である。

3次元空間における3点のペアワイズ距離は、2次元空間では正確に保存されるが、1次元空間では保存されない。

MDSを実行すれば、実際のペアワイズ距離とマッピングされた点のペアワイズ距離の差が最小になることが保証される。

MDSは、分類や回帰問題における次元削減の前処理として用いることができる。

多次元尺度法以外にも、主成分分析(PCA)や特異値分解(SVD)など、他の次元削減手法も使用できます。

これらの手法やその活用方法については、PythonでScikit-Learnを使った次元削減のガイドをお読みください。

をご覧ください。

MDSは次元削減だけでなく、データの可視化にも有効な手法です。

MDSは高次元のデータのクラスタやパターンを低次元の空間でも維持するので、例えば5次元のデータセットを3次元のデータセットに煮詰めることができ、より簡単かつ自然に解釈することができるのです。

通常、MDSで使用される距離尺度はユークリッド距離ですが、MDSを適用する際には他の適切な非類似度メトリックを使用することができます。

MDSを実装する方法は主に2つあります。

  • このバージョンのMDSは、ペアワイズ距離/非類似度指標を可能な限り保持することを目的としています。
  • 非メトリックMDS:非類似度メトリックのランクのみが分かっている場合に適用される方法です。MDSでは、ランクを可能な限り保持するようにオブジェクトをマッピングする。

Scikit-Learnを用いたPythonによる多次元スケーリングの実行

Scikit-Learn ライブラリの sklearn.manifold モジュールは、多様体学習とデータ埋め込み技術を実装しています。

今回は、このモジュールの MDS クラスを使用します。

埋め込みは、stress minimization using majorization (SMACOF) アルゴリズムを用いて決定されます。

MDS` オブジェクトをセットアップするための重要なパラメータは以下の通りです(すべてを網羅したものではありません)。

  • n_components: n_components`: ポイントをマッピングする次元の数です。デフォルトは2です。
  • metric: ブール変数で、デフォルト値はメトリックデータシートでは True 、非メトリックデータシートでは False です。
  • dissimilarity: デフォルト値は euclidean で、ユークリッド距離のペアワイズ距離を指定します。もう一つの値は precomputed です。precomputedを利用する場合,ペアワイズ距離の行列を計算し,その行列をfit()fit_transform()` 関数の入力として利用する必要があります.

MDS` オブジェクトに関連する 4 つの属性は以下の通りです。

  • embedding_: 新しい空間における点の位置
  • stress_: MDS で使用される適合度統計量。
  • dissimilarity_matrix_: 対の距離/非類似度の行列.
  • n_iter_: 最適な適合度測定に必要な反復回数.

他の scikit-learn の次元削減クラスと同様に、 MDS クラスも fit()fit_transform() メソッドを実装しています。

シンプルな図解

このセクションでは、非常に簡単な例を使ってデータシートの適用方法を説明します。

まず、インポートのセクションを追加します。

from sklearn.manifold import MDS
from matplotlib import pyplot as plt
import sklearn.datasets as dt
import seaborn as sns         
import numpy as np
from sklearn.metrics.pairwise import manhattan_distances, euclidean_distances
from matplotlib.offsetbox import OffsetImage, AnnotationBbox


以下のコードは MDS オブジェクトをセットアップし、そのメソッド fit_transform() を呼び出しています。

このメソッドは、2次元空間に埋め込まれた点を返します。

結果のマッピングを表示してみましょう。

X = np.array([[0, 0, 0], [0, 0, 1], [1, 1, 1], [0, 1, 0], [0, 1, 1]])
mds = MDS(random_state=0)
X_transform = mds.fit_transform(X)
print(X_transform)


[[ 0.72521687  0.52943352]
 [ 0.61640884 -0.48411805]
 [-0.9113603  -0.47905115]
 [-0.2190564   0.71505714]
 [-0.21120901 -0.28132146]]


埋め込みはストレス最小化アルゴリズムに基づいて作成されているので、変数 stress を見てみることもできます。

stress = mds.stress_
print(stress)


という結果になります。

0.18216844548575467


MDSを適用するもう一つの方法は、以下のコードに示すように、距離行列を構築し、この行列に直接MDSを適用する方法です。

この方法は、ユークリッド距離以外の距離尺度が必要な場合に有効です。

以下のコードでは、ペアワイズマンハッタン距離(シティブロック距離またはL1距離とも呼ばれる)を計算し、データシートを用いてデータを変換している。

引数 dissimilarityprecomputed に設定されていることに注意してください。

dist_manhattan = manhattan_distances(X)
mds = MDS(dissimilarity='precomputed', random_state=0)
# Get the embeddings
X_transform_L1 = mds.fit_transform(dist_manhattan)


この結果

[[ 0.9847767   0.84738596]
 [ 0.81047787 -0.37601578]
 [-1.104849   -1.06040621]
 [-0.29311254  0.87364759]
 [-0.39729303 -0.28461157]]


しかし、これでは何が起こったのか直感的に理解することができません。

人間は数字を計算するのがそれほど得意ではないのです。

そこで、ユークリッド距離を保存した原点とその埋め込みをプロットしてみる。

原点とそれに対応する埋め込み点は、同じ色で表示されます。

colors = ['r', 'g', 'b', 'c', 'm']
size = [64, 64, 64, 64, 64]
fig = plt.figure(2, (10,4))
ax = fig.add_subplot(121, projection='3d')
plt.scatter(X[:,0], X[:,1], zs=X[:,2], s=size, c=colors)
plt.title('Original Points')


ax = fig.add_subplot(122)
plt.scatter(X_transform[:,0], X_transform[:,1], s=size, c=colors)
plt.title('Embedding in 2D')
fig.subplots_adjust(wspace=.4, hspace=0.5)
plt.show()


右のプロットは、紫、緑、青は近接しており、シアン、赤と比較すると互いの相対位置はほぼ同じで、相対距離は概ね保たれています。

AT&TのOlivetti Facesデータセットにおける多次元尺度法の実用化

データシートの実用的な例として、AT&TのOlivetti facesデータセットを用いて、2次元の低い空間における埋め込みを示します。

このデータセットには、1人あたり10枚の64×64ビットマップ画像があり、それぞれの画像は表情や照明条件を変えながら取得されています。

MDSは、2次元空間において、同一人物の異なる顔画像が互いに近く、別の人物のマッピングされた顔から遠く離れているように、データのパターンを保持します。

乱雑さを避けるために、4人の異なる人物の顔だけを取り出して、MDSを適用します。

データセットを取得し、MDS を適用する前に、小さな関数 mapData() を書いてみましょう。

この関数は、入力引数、つまり、ペアワイズ距離行列 dist_matrix、生データ行列 X、クラス変数 y、ブール変数 metric およびグラフの title を受け取ります。

この関数は、距離行列に MDS を適用し、変換された点を 2 次元空間に表示します。

同じ色の点は、同じ人物の画像をマッピングしたものであることを示します。

また、2つ目の図では、グラフ上の各顔の画像を低次元空間に写像したものを表示しています。

非計量的なデータシートとともに、異なる距離尺度を用いたデータシートのデモを行います。

def mapData(dist_matrix, X, y, metric, title):
    mds = MDS(metric=metric, dissimilarity='precomputed', random_state=0)
    # Get the embeddings
    pts = mds.fit_transform(dist_matrix)
    # Plot the embedding, colored according to the class of the points
    fig = plt.figure(2, (15,6))
    ax = fig.add_subplot(1,2,1)    
    ax = sns.scatterplot(x=pts[:, 0], y=pts[:, 1],
                         hue=y, palette=['r', 'g', 'b', 'c'])


# Add the second plot
    ax = fig.add_subplot(1,2,2)
    # Plot the points again
    plt.scatter(pts[:, 0], pts[:, 1])

    # Annotate each point by its corresponding face image
    for x, ind in zip(X, range(pts.shape[0])):
        im = x.reshape(64,64)
        imagebox = OffsetImage(im, zoom=0.3, cmap=plt.cm.gray)
        i = pts[ind, 0]
        j = pts[ind, 1]
        ab = AnnotationBbox(imagebox, (i, j), frameon=False)
        ax.add_artist(ab)
    plt.title(title)    
    plt.show()


以下のコードは、Olivetti faces datasetを取得し、ラベル < 4を持つ例を抽出します。

faces = dt.fetch_olivetti_faces()
X_faces = faces.data
y_faces = faces.target
ind = y_faces &lt; 4
X_faces = X_faces[ind,:]
y_faces = y_faces[ind]


それでは、データをロードして mapData() 関数を実行してみましょう。

ユークリッド型一対距離の利用

ユークリッド距離を用いたオリベッティの顔データセットのマッピングは以下の通りである。

ユークリッド距離は、汎用性が高く、一般的に使用されているため、データシートのデフォルト距離となっています。

dist_euclid = euclidean_distances(X_faces)
mapData(dist_euclid, X_faces, y_faces, True, 
        'Metric MDS with Euclidean')


64×64の画像を2次元空間にうまくマッピングしており、ほとんどの場合、各画像のクラスが他とうまく分離していることがわかります。

64×64の空間に存在する画像が、2次元の空間に縮小されても、その情報価値が損なわれないという事実は、一見の価値がある。

マンハッタン対距離の利用

比較のために、同じデータに対してManhattenのペアワイズ距離を用いてデータシートを実行することができます。

以下のコードでは、Manhatten距離行列をmapData()の入力として使用しています。

dist_L1 = manhattan_distances(X_faces)
mapData(dist_L1, X_faces, y_faces, True, 
        'Metric MDS with Manhattan')


マッピングはユークリッド距離で得られたものと非常によく似ていることがわかります。

各クラスは低次元空間できれいに分離されていますが、プロット上では少し異なるオフセットになっています。

非計量的な多次元尺度法の実行

最後の例として、同じデータセットでユークリッド距離を用いたノン・メトリック多次元尺度法を行い、対応するメトリック版との比較を見てみましょう。

mapData(dist_euclid, X_faces, y_faces, False, 
        'Non-metric MDS with Euclidean')


ここでは、かなり多くの問題があります。

このバージョンのデータシートは、Olivettiの顔のデータセットではあまりうまく機能しないことがわかります。

これは主にデータの量的な性質によるものです。

ノン・メトリックMDSは、実際の距離ではなく、オブジェクト間のランク付けされた距離を保持します。

MDS の n_components パラメータ。

MDSにおける重要なハイパーパラメータの1つは、点が埋め込まれる低次元空間の大きさである。

これは、データシートを作成する際に非常に重要なパラメータです。

MDSが次元削減の前処理として使用される場合、これは非常に重要である。

という疑問が湧いてきます。

gt; 重要な情報を失うことなく、できる限り次元を減らすには、何次元を選べばよいのでしょうか?
ということです。

このパラメータを選ぶ簡単な方法は、「n_components」の値を変えてMDSを実行し、各埋め込みに対して「stress_」の値をプロットすることです。

stress_の値は次元が高くなるにつれて小さくなることを考えると、 stress_n_componentsの間で公正なトレードオフがある点を選びます。

以下のコードは、次元を1-20の間で変化させてMDSを実行し、各埋め込みに対して対応する stress_ 属性をプロットしています。

stress = []
# Max value for n_components
max_range = 21
for dim in range(1, max_range):
    # Set up the MDS object
    mds = MDS(n_components=dim, dissimilarity='precomputed', random_state=0)
    # Apply MDS
    pts = mds.fit_transform(dist_euclid)
    # Retrieve the stress value
    stress.append(mds.stress_)
# Plot stress vs. n_components    
plt.plot(range(1, max_range), stress)
plt.xticks(range(1, max_range, 2))
plt.xlabel('n_components')
plt.ylabel('stress')
plt.show()


n_components`の値を大きくすると、初期には応力値が減少し、その後、曲線が水平になることがわかります。

18次元と19次元ではほとんど差がありませんが、1次元と2次元では大きな差があります。

曲線のエルボは、n_componentsの最適値を決めるのに適しています。

この場合、値は4と取ることができ、これは特徴/属性の0.09%という驚くべき削減率です。

結論

このガイドは、Scikit-Learnを用いたPythonによる多次元尺度法(Multidimensional Scaling)の入門書でした。

多次元尺度法がどのように機能するか、そのハイパーパラメータ、どのような変数が存在するか、そしてそれを実用的なデータセットに適用しました。

AT&TのOlivetti Facesデータセットを使い、64×64次元空間に存在する画像を2次元空間にマッピングしても、画像間の個々のパターンやクラスタを保持できることを説明しました。

タイトルとURLをコピーしました