MLエンジニアへの道 #8 - PCA

Last Edited: 8/8/2024

このブログ記事では、主成分分析(PCA)を紹介します。

ML

注意:この記事は、共分散や線形代数(固有ベクトル、固有値、射影)の前提知識を要します。もしこれらの概念の理解に不安がある場合は、 StatQuestによるCovariance, Clearly Explained!!!や、 3Blue1BrownによるEssence of linear algebra を見たりして、予習をすることをおすすめします。

次元の問題

これまでの記事では、モデル定義、モデル選択、正則化、モデル評価まで、機械学習パイプラインの基本を説明してきました。しかし、使用したデータセットは 5つの特徴量(次元)しか持たないIrisデータセットのみでした。それでは、もう少し現実的な(とはいえ、まだ完全に現実的ではない)例として、MNISTデータセット を見てみましょう。

MNISTデータセットは、60,000個の手書き数字の画像サンプルを含む、非常に有名なデータセットです。MNISTデータセットの画像例を見てみましょう。

import keras
import matplotlib.pyplot as plt
 
# MNISTをダウンロード
(X_train, y_train), (X_test, y_test) = keras.datasets.mnist.load_data()
 
# 10サンプルを表示
plt.figure(figsize=(10, 4))
for i in range(10):
    plt.subplot(2, 5, i + 1)
    plt.imshow(X_train[i], cmap='gray')
    plt.axis('off')
plt.tight_layout()
plt.show()
MNIST samples

上の画像はかなりぼやけていますが、それでも28x28ピクセルあり、合計784ピクセルを含んでいます。すべての個々のピクセルを特徴量として扱い、 以下のようにソフトマックス回帰モデルを訓練して数字を分類することができます。

log(odds)=w1pixel1+w2pixel2+w3pixel3+...+w784pixel784+w785 log(odds) = w_1 pixel_1 + w_2 pixel_2 + w_3 pixel_3 + ... + w_{784} pixel_{784} + w_{785}

このようなモデルを訓練することは不可能ではありませんが、このサイズの60,000枚の画像を使ってモデルを訓練するには長い時間がかかることが容易に 予想できます。より現実的な画像分類では、512x512ピクセル(262,144ピクセル)の画像に直面することがよくあります。このような高次元なデータは ソフトマックスのような単純なモデルには手に負えません。(この記事のカバー画像は4051x2408ピクセルです!)

音声、動画、遺伝子データなど、他にも高次元のデータはたくさんあります。したがって、限られた時間と計算資源の中でモデルを訓練できるようにするには、 これらの高次元データを低次元データに圧縮する方法、つまり次元削減が必要です。

主成分分析(PCA)

そのような次元削減を行う1つの方法が**主成分分析(PCA)**です。単刀直入に言えば、PCAはデータの共分散行列を取得し、固有ベクトルを計算し、最大の固有値を持ついくつかの 固有ベクトルを基に次元を削減します。以下は、2つの主成分を使用して784次元を2次元に減らす方法を示しています。

imgoriginal=[p1p2p784],pc1,pc2=[c1c2c784],[d1d2d784]imgoriginal=pc1e1+pc2e2imgnew=[e1e2] \overrightharpoon{img}_{original} = \begin{bmatrix} p_1 \\ p_2 \\ \vdots \\ p_{784} \end{bmatrix}, \overrightharpoon{pc}_1, \overrightharpoon{pc}_2 = \begin{bmatrix} c_1 \\ c_2 \\ \vdots \\ c_{784} \end{bmatrix}, \begin{bmatrix} d_1 \\ d_2 \\ \vdots \\ d_{784} \end{bmatrix} \\ \overrightharpoon{img}_{original} = \overrightharpoon{pc}_1 e_1 + \overrightharpoon{pc}_2 e_2 \\ \overrightharpoon{img}_{new} = \begin{bmatrix} e_1 \\ e_2 \end{bmatrix}

共分散行列の固有ベクトルを最大の固有値を持つものを選ぶのは、それらの固有ベクトルが、次元間の線形関係を最も多く捉えることができるからです。 それがなぜなのかを、数学的に考えてみましょう。

PCAの数学的背景

まず、2つのピクセルと4つの画像についてのみ考えてみましょう。これらデータはベクトルとして表現できます。ここでは、2次元のベクトル間の距離を最大限に 表現できる1次元の線を見つけることで次元を削減することが目的です。下の画像から、ベクトルの射影または射影の分散が最大になるときに一次元の線が 2次元のベクトル間の距離を最大限に表現できることを観察できます。

ML PCA

オレンジの線は、ベクトル間の距離に関する情報を失っていますが、射影の分散を最大化する緑の線は、可能な限り両方の次元で距離を捉えています。 したがって、射影の分散を最大化する単位ベクトルを見つけることができれば、情報を最大限に残しながら次元を削減できることがわかります。 射影の分散は以下のように表現できます。

Var(projux)=1ni=0n(uxiuxˉ)2 Var(proj_{u}x) = \frac{1}{n} \sum_{i=0}^{n} (u x_i - u \bar{x})^2

ここで、xˉ\bar{x}xxの平均です。上の式は次のように変形できます。

Var(projux)=1ni=0n[u(xixˉ)]2=uT1n(xixˉ)(xixˉ)Tu Var(proj_{u}x) = \frac{1}{n} \sum_{i=0}^{n} [u (x_i - \bar{x})]^2\\ = u^T \frac{1}{n} \sum (x_i-\bar{x}) (x_i-\bar{x})^T u

上の式に、共分散行列の式(1n(xixˉ)(xixˉ)T\frac{1}{n} \sum (x_i-\bar{x}) \cdot (x_i-\bar{x})^T)が含まれていることがわかります。 これをCCと置き換えることができます。さらに、線が単位ベクトルであるという条件を満たすためにラグランジュ乗数を使用できます。 (uTu=1u^T u = 1) 従って、次の関数を最大化する必要があります。

uTCuλ(uTu1) u^T C u - \lambda (u^T u - 1)

上記の式をuuに関して微分し、0とすることで以下のようになります。

dduuTCuλ(uTu1)=02Cuλ2u=0Cu=λu \frac{d}{d u} u^T C u - \lambda (u^T u - 1) = 0 \\ 2 C u - \lambda 2 u = 0 \\ Cu = \lambda u

上記から、射影の分散を最大化するuuの集合は共分散行列CCの固有ベクトルであることがわかります。 どの固有ベクトルを使用するかどうかを選択する方法はあるでしょうか?上記の式をより変形してみましょう。

Cu=λuλ=uTCuλ=Var(projux) Cu = \lambda u \\ \lambda = u^T C u \\ \lambda = Var(proj_u x)

上記の式から、固有ベクトルに関連する固有値が高いほど、射影の分散が高くなることがわかります。以上の理由から、 PCAでは共分散行列の固有ベクトルを固有値の高い順から選ぶわけです。

コードの実装

幸運なことに、sklearnは上記のすべてを自動的に行うPCAクラスを要しています。

from sklearn.decomposition import PCA
 
def performPCA(X_train, X_test, n_components=0.95):
  # PCAを見つける
  pca = PCA(n_components=n_components)
  pca.fit(X_train)
  # データをPCAに変換する
  X_train = pca.transform(X_train) 
  X_test = pca.transform(X_test) 
  return X_train, X_test

n_componentsパラメータは、主成分の固有値の合計がすべての主成分の総固有値のn_components%になるときに停止することにより、 主成分の数を決定します。つまり、PCAを使用した次元削減後のデータは、次元間の線形関係のn_components%を保持します。 この関数をMNISTデータセットに適用してみましょう。

# (, 28, 28) -> (, 784)
X_train = X_train.reshape(X_train.shape[0], X_train.shape[1]*X_train.shape[2])
X_test = X_test.reshape(X_test.shape[0], X_test.shape[1]*X_test.shape[2])
print("X_train's shape before PCA: ", X_train.shape)
print("X_test's shape before PCA: ", X_test.shape)
 
# PCAで次元削減を実行する
X_train, X_test = performPCA(X_train, X_test)
print("X_train's shape after PCA: ", X_train.shape) # (, 154)
print("X_test's shape after PCA: ", X_test.shape) # (, 154)

次元削減後、154次元になり、はるかに扱いやすい次元数になります。また、処理済みデータで最近傍法アルゴリズムを使用して分類を実行することもできます。 このことから、PCAは教師なし学習アルゴリズムであるとも言えます。(クラスラベルを使用せずにデータの基礎的な関係を学習するため、 教師なし学習であると言えます。)

時間があれば、PCA後のMNISTデータセットでSoftmaxRegressionGDを適合させてみることをお勧めします。また、LDA、T-SNE、UMAPなど、他にも多くの次元削減/教師なし学習アルゴリズムがありますので、 興味がある方はぜひ調べてみてください。(リクエストがあれば将来取り上げるかもしれません。)

リソース