クロスデータ行列法 with Python
はじめに
今回は、高次元主成分分析(高次元PCA)の1つである、クロスデータ行列法(cross-data-matrix methodology)を紹介します。
高次元データについて知りたい方はこちらをチェック
高次元データと主成分分析について知りたい方はこちらをチェック
基本
d>nの状況を考える。
平均
ここで、
クロスデータ行列法
Step1.
データ行列Xを
Step2.
クロスデータ行列を計算する。
Step3.
対称行列
Step4.
対称行列
Step5.
を計算し、固有ベクトル
Step6.
第k主成分スコアは
そして各kで
いまクロスデータ行列法で固有値などを推定する方法を紹介した。この手順は高次元の統計学に載っている。Step3,4で対称行列にしてから固有値分解を行なっている。
ここで以下の特異値分解ができればよいのでは?という疑問が生まれた。
実際 Pythonでは特異値分解を計算できるので、やってみるとちょっとズレる。
Pythonコード
高次元の統計学で用いられているやり方。固有値分解を使用。
#cross-data-matrix methodology 固有値分解
def CDM1(X):
# Step1
d, n = X.shape
n1 = np.ceil(n/2).astype(int)
n2 = n - n1
X1 = X[:, :n1]
X2 = X[:, n1:]
# Step2
X1_bar = np.mean(X1, axis=1).reshape(-1, 1)
X2_bar = np.mean(X2, axis=1).reshape(-1, 1)
S_D1 = (X1 - X1_bar).T @ (X2 - X2_bar) / np.sqrt((n1 - 1) * (n2 - 1))
S_D2 = S_D1.T
# Step3 S_D1@S_D2の固有値・固有ベクトル
l1, u1 = np.linalg.eigh(S_D1@S_D2)
index = np.argsort(l1)[::-1][:(n2-1)]
acute_lam = np.sqrt(l1[index])
acute_u1 =u1[:, index]
# Step4 S_D2@S_D1の固有値・固有ベクトル
l2, u2 = np.linalg.eigh(S_D2@S_D1)
index2 = np.argsort(l2)[::-1][:(n2-1)]
u2 = u2[:, index2]
acute_u2 =np.array([np.sign(acute_u1[:,i].T @ S_D1 @ u2[:,i]) * u2[:,i] for i in range(n2-1)]).T
# Step5 固有ベクトル
acute_h1 = np.array([(X1 - X1_bar) @ acute_u1[:, i]/np.sqrt((n1 - 1) * lam) for i, lam in enumerate(acute_lam)])
acute_h2 = np.array([(X2 - X2_bar) @ acute_u2[:, i]/np.sqrt((n2 - 1) * lam) for i, lam in enumerate(acute_lam)])
acute_h = np.array([(h1 + h2) /np.linalg.norm(h1+h2) for h1, h2 in zip(acute_h1, acute_h2)])
# Step6 主成分スコア
s1 = acute_u1 * np.sqrt((n1 - 1) * acute_lam)
s2 = acute_u2 * np.sqrt((n2 - 1) * acute_lam)
s = np.vstack((s1,s2))
return acute_lam, acute_h, s
特異値分解を使用するやり方。
#cross-data-matrix methodology 特異値分解
def CDM2(X):
# Step1
d ,n = X.shape
n1 = np.ceil(n/2).astype(int)
n2 = n - n1
X1 = X[:, :n1]
X2 = X[:, n1:]
# Step2
X1_bar = np.mean(X1, axis=1).reshape(-1, 1)
X2_bar = np.mean(X2, axis=1).reshape(-1, 1)
S_D1 = (X1 - X1_bar).T @ (X2 - X2_bar) / np.sqrt((n1 - 1) * (n2 - 1))
S_D2 = S_D1.T
# Step3 , Step4 固有値
u1, l, u2 = np.linalg.svd(S_D1, full_matrices=False)
acute_lam = l[:(n2-1)]
acute_u1 = u1[:,:(n2-1)]
acute_u2 = u2[:(n2-1)].T
# Step5 固有ベクトル
acute_h1 = np.array([(X1 - X1_bar) @ acute_u1[:, i]/np.sqrt((n1 - 1) * lam) for i, lam in enumerate(acute_lam)])
acute_h2 = np.array([(X2 - X2_bar) @ acute_u2[:, i]/np.sqrt((n2 - 1) * lam) for i, lam in enumerate(acute_lam)])
acute_h = np.array([(h1 + h2) /np.linalg.norm(h1+h2) for h1, h2 in zip(acute_h1, acute_h2)])
# Step6 主成分スコア
s1 = acute_u1 * np.sqrt((n1 - 1) * acute_lam)
s2 = acute_u2 * np.sqrt((n2 - 1) * acute_lam)
s = np.vstack((s1,s2))
return acute_lam, acute_h, s
CDM1は固有値分解を用いた。一方でCDM2は特異値分解を用いた。
CDM1とCDM2によって求められた固有値は一致した。しかし固有ベクトルの符号が一致しなかった。そのため主成分スコアも符号が一致しなかった。とりあえず、高次元の統計学にはCDM1が使われているのでCDM1をつかのが無難である。
まとめ
この方法はシンプルにすごいなと思いました。普通ならブートストラップみたいにデータ数を増やす方向を考えると思います。しかしデータを半分にして掛け合わせることで、小さいところはさらに小さく、大きいところはさらに大きくなるようにしています。すごい発想力。
参考文献
- 青嶋誠、矢田和善 (2019)「高次元の統計学」
https://www.kyoritsu-pub.co.jp/book/b10003167.html
Discussion