Open1

主成分分析

osn_Lofiosn_Lofi
  • 主成分分析(Principal Component Analysis, PCA)は、多次元データを圧縮して次元削減することを目的とする統計的手法です。
  • PCAは、高次元データの情報を、少数の主成分と呼ばれる新しい特徴量に圧縮することができます。これにより、多次元データをより単純な形式で表すことができます。
  • scikit-learnのライブラリーを使えば1行で算出することもできますが、ここでは、numpyを使った実装を通じて、一つずつ計算の過程をみていきます。

全体感

  • 以下が、numpyを使って主成分分析を行う関数になります。
  • ひとつずつ何を計算しているのかをみていきます。
def pca(X, n_components=2):
    ① X = X- X.mean(axis=0)
    ②   cov = np.cov(X, rowvar=False)
    ③   l, v = np.linalg.eig(cov)
    ④    l_index = np.argsort(l)[::-1]
    ⑤   v_ = v[:, l_index]

    ⑥   components = v_[:,:n_components]

    ⑦  T = np.dot(X, components)

    return T

Xの準備

  • 100×5次元の行列をXとして生成しておきます。
  • 主成分分析は次元圧縮です。5次元を2次元に圧縮していくのが目的になります。
X = np.random.rand(100,5)
X[:3]

[out]
array([[0.95927047, 0.32503164, 0.16065997, 0.36034609, 0.46927506],
       [0.86403093, 0.02959833, 0.13703161, 0.3636126 , 0.9330399 ],
       [0.65145658, 0.1835818 , 0.91643428, 0.24846823, 0.61529736]])

❶各要素と平均との差を算出

  • Xの各要素と各次元の平均との差を算出します。
X = X-X.mean(axis=0)
X[:3]

[out]
array([[ 0.40344141, -0.1789226 , -0.35687984, -0.15906571, -0.01172766],
       [ 0.30820188, -0.47435591, -0.38050819, -0.1557992 ,  0.45203718],
       [ 0.09562753, -0.32037244,  0.39889447, -0.27094357,  0.13429464]])

❷共分散行列を生成

  • 共分散行列は、多変量データのk個の変数に対して、各変数の組み合わせの共分散を記述する行列です。
  • 共分散は、2つの変数がどの程度に同時に変動するかを示します。
  • 共分散は正の数であれば、2つの変数は正の相関を持ちます。共分散は負の数であれば、2つの変数は負の相関を持ちます。共分散は0であれば、2つの変数は独立であることを示します。
  • 次元数×次元数となります
cov =np.cov(X,rowvar=False)
cov

[out]
array([[ 0.08999058, -0.00547484, -0.0088472 ,  0.00446651,  0.01157695],
       [-0.00547484,  0.09707911, -0.00421609,  0.0080685 , -0.00265034],
       [-0.0088472 , -0.00421609,  0.09334372, -0.00715198,  0.00616764],
       [ 0.00446651,  0.0080685 , -0.00715198,  0.07963141,  0.00369422],
       [ 0.01157695, -0.00265034,  0.00616764,  0.00369422,  0.07095071]])

❸固有値・固有ベクトルを計算

  • 固有値は、行列Aに対する線形変換の「大きさ」を表します。
  • 固有ベクトルは、行列Aに対する線形変換の「方向」を表します。
  • 固有値と固有ベクトルは、行列の「特徴」を表すものであり、行列Aに対して、固有値と固有ベクトルを求めることにより、行列Aを解析することができます。
#lに固有値、vに固有ベクトルが入ります
l, v = np.linalg.eig(cov)

l
[out]
array([0.06189794, 0.07396456, 0.08742091, 0.10242342, 0.1052887 ])

v
[out]
array([[-0.40975186, -0.36982006, -0.36271054,  0.69353739,  0.2877212 ],
       [ 0.0064176 , -0.36978536, -0.39478444, -0.63069096,  0.55640971],
       [-0.32303312,  0.04932353, -0.6348379 , -0.22094408, -0.66436537],
       [-0.20261986,  0.84961928, -0.26582834,  0.02539724,  0.40716403],
       [ 0.82864771,  0.04696996, -0.48877649,  0.26790575, -0.02146753]])

❹❺固有値の大きい順に固有ベクトルを並び替え

  • argsortを使って、固有値のインデックスを降順にインデックス順に並び替えます・
  • [::-1]で降順になります
l_index = np.argsort(l)[::-1]
l_index

[out]
array([4, 3, 2, 1, 0])
v[:,l_index]

array([[ 0.2877212 ,  0.69353739, -0.36271054, -0.36982006, -0.40975186],
       [ 0.55640971, -0.63069096, -0.39478444, -0.36978536,  0.0064176 ],
       [-0.66436537, -0.22094408, -0.6348379 ,  0.04932353, -0.32303312],
       [ 0.40716403,  0.02539724, -0.26582834,  0.84961928, -0.20261986],
       [-0.02146753,  0.26790575, -0.48877649,  0.04696996,  0.82864771]])

❻n_components個の固有ベクトルを取得

  • 列の次元で最初から、n_components個を取り出す
n_components =2
components = v_[:,:n_components]
components

#5×2の固有ベクトルが取得できる
[out]
array([[ 0.2877212 ,  0.69353739],
       [ 0.55640971, -0.63069096],
       [-0.66436537, -0.22094408],
       [ 0.40716403,  0.02539724],
       [-0.02146753,  0.26790575]])

❼Xに固有ベクトルを掛け合わせることで、Xを2次元に圧縮する

  • もともとXは100×5の行列でしたが、この操作により、100×2の行列になります。
T = np.dot(X,components)
T[:3]

#3行だけ抽出します。実際は100行あります。
[out]
array([[ 0.18910891,  0.46431532],
       [ 0.0043965 ,  0.71413904],
       [-0.52895739,  0.20934099]])