主成分分析
多変量データの持つ構造をより少数個の指標に圧縮する手法。
この際、できるだけ情報の損失が少なくなるように変数を減らす。
これを利用することで、少数変数を利用した分析や可視化ができる。
流れ
学習データを
\boldsymbol{x}_{i}=(x_{i1},x_{i2}, \dots,x_{im}) \in \mathbb{R}^m, i=1,2,\dots, n
として、各データから平均ベクトルを引いた、データ行列
\bar X = (\boldsymbol{x}_1-\boldsymbol{\bar x}, \dots, \boldsymbol{x}_n-\boldsymbol{\bar x})^\top \in \mathbb{R}^{n \times m}
を定義する。これに対して線形変換をした後のベクトルは、
\boldsymbol{s}_j = (s_{1j}, \dots, s_{nj})^\top = \bar X \boldsymbol{a}_j , \boldsymbol{a}_j \in \mathbb{R}^m
と表される。
線形変換後の分散は、
Var(\boldsymbol{s}_j) = \boldsymbol{a}_j^\top Var(\bar X)\boldsymbol{a}_j
と表される。
主成分分析では、情報の多さを分散の大きさと捉えて、線形変換後の分散が最大となるような射影軸を探していく。
ノルムが1となる制約条件の下で、上式を最大化するような$\boldsymbol{a}_j$を探すので、これは制約条件付き最適化問題として、以下のラグランジュ関数を用いる。
E(\boldsymbol{a}_j) = \boldsymbol{a}_j^\top Var(\bar X)\boldsymbol{a}_j - \lambda (\boldsymbol{a}_j^\top \boldsymbol{a}_j - 1)
これを解くと、以下の解が得られる。
Var(\bar X)\boldsymbol{a}_j = \lambda \boldsymbol{a}_j
寄与率
こうして分散共分散行列を計算して、それに対する固有値問題を解いたとき、$m$個の固有値と固有ベクトルのペアが得られる。
固有値を昇順に並べて、対応する固有ベクトルを第k主成分と呼ぶことにすると、第1~第m主成分の分散は元のデータの分散と一致する。
主成分分析では、どの主成分まで圧縮するかで情報量がどの程度残るかが肝になるので、その際に使う指標が寄与率である。
- 寄与率:第k主成分の分散の全分散に対する割合
- 累積寄与率:第1~k主成分までに圧縮した際の情報損失量の割合
ハンズオン
乳がん検査データをロジスティック回帰モデルを作成。
主成分を利用して、2次元空間に次元圧縮した上でうまく判別できるか確認
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegressionCV
from sklearn.metrics import confusion_matrix
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
%matplotlib inline
cancer_df = pd.read_csv('./cancer.csv')
# 不要な行削除
cancer_df.drop('Unnamed: 32', axis=1, inplace=True)
# 目的変数の抽出
y = cancer_df.diagnosis.apply(lambda d: 1 if d == 'M' else 0)
# 説明変数の抽出
X = cancer_df.loc[:, 'radius_mean':]
# 学習用とテスト用でデータを分離
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
# 標準化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# ロジスティック回帰で学習
logistic = LogisticRegressionCV(cv=10, random_state=0)
logistic.fit(X_train_scaled, y_train)
# 検証
print('Train score: {:.3f}'.format(logistic.score(X_train_scaled, y_train)))
# Train score: 0.988
print('Test score: {:.3f}'.format(logistic.score(X_test_scaled, y_test)))
# Test score: 0.972
print('Confustion matrix:\n{}'.format(confusion_matrix(y_true=y_test, y_pred=logistic.predict(X_test_scaled))))
Train score:0.988、Test score:0.972となるので、判別は出来てそう。
ここからが主成分分析。
pca = PCA(n_components=30)
pca.fit(X_train_scaled)
plt.bar([n for n in range(1, len(pca.explained_variance_ratio_)+1)], pca.explained_variance_ratio_)
寄与率を見ると、上位2つで60%くらいの累積寄与率になっている。
2次元に圧縮してみて、データをプロットしてみる。
# PCA
# 次元数2まで圧縮
pca = PCA(n_components=2)
X_train_pca = pca.fit_transform(X_train_scaled)
print('X_train_pca shape: {}'.format(X_train_pca.shape))
# X_train_pca shape: (426, 2)
# 寄与率
print('explained variance ratio: {}'.format(pca.explained_variance_ratio_))
# explained variance ratio: [ 0.43315126 0.19586506]
# 散布図にプロット
temp = pd.DataFrame(X_train_pca)
temp['Outcome'] = y_train.values
b = temp[temp['Outcome'] == 0]
m = temp[temp['Outcome'] == 1]
plt.scatter(x=b[0], y=b[1], marker='o') # 良性は○でマーク
plt.scatter(x=m[0], y=m[1], marker='^') # 悪性は△でマーク
plt.xlabel('PC 1') # 第1主成分をx軸
plt.ylabel('PC 2') # 第2主成分をy軸
この2次元データを用いて、ロジスティック回帰を行う。
# ロジスティック回帰で学習
logistic = LogisticRegressionCV(cv=10, random_state=0)
logistic.fit(X_train_pca, y_train)
X_test_pca = pca.fit_transform(X_test_scaled)
# 検証
print('Train score: {:.3f}'.format(logistic.score(X_train_pca, y_train)))
print('Test score: {:.3f}'.format(logistic.score(X_test_pca, y_test)))
Train score:0.965、Test score:0.916なので、2次元データでもかなりの精度で判別が可能。(課題)