機械学習:特徴量の抽出:Extracion PCA
前記事の特徴量の選択と、この記事の抽出には大きな違いがあります。選択では、前記事で見られたように、特徴量はそのままの形で維持されて学習アルゴリズムに渡されます。しかし、抽出では、特徴量は維持されず、空間的に言えば、別の特徴量空間へ変換され射影されるのです。
例えば、ある特徴量が別の特徴量と連動して動いている場合、この二つの特徴量には相関があるでしょうし、その相関を特徴量として扱えば次元を一つ減らせます。こうして互いに無関係な特徴量だけが残る空間で、学習モデルを扱えば、過学習を防ぎ、予測性能を向上することができるのです。
抽出には3つの方法があります。
PCA:主成分分析
LDA:線型判断分析
KPCA:Kernel主成分分析
PCA
PCAは教師無しの線形変換法です。
探索的データ解析や、株取引での雑音除去、生物情報学でのゲノムデータ等の解析に利用されます。
PCAの目的は高次元データにおいて分散が最大になる方向を見つけ出し、非相関データで作られた元の次元以下の空間へ射影することです。
分散が最大となる、とは、特徴量軸が互いに直行していることで、相関がないという意味になります。このため、特徴量から共分散行列を作る必要があるので、特徴量は必ず標準化されていなくてはいけません。
元々に特徴量が$${d}$$個、つまり特徴量空間は$${d}$$次元とすれば、
$${\bf{x}=[x_1,x_2,\dots,x_d] : \bf{x} \in \bf{R}^d}$$
と表されます。
元の$${\bf{x}}$$のベクトル空間を、訓練データの共分散行列の固有値と固有ベクトルを用いて,
$${d \times k}$$次元の変換行列$${\bf{W}}$$を作り、この変換行列によって、特徴量空間は、
$${\bf{x} \bf{W} = \bf{z}}$$
により、
$${\bf{z}=[z_1,z_2,\dots,z_k] : \bf{z} \in \bf{R}^k}$$
の$${k}$$次元の新しい空間に写ることになります。
このPCAの手順は以下の通りになります。
$${d}$$次元の訓練データを$${[0,1]}$$に標準化する
標準化した訓練データの共分散行列(Cov:Covariant Matrix)を作る
Covの固有値と固有ベクトルをとる
固有値を降順にソートして対応する固有ベクトルをランク付けする
最も大きい$${k}$$個の固有値に対応する、$${k}$$個の固有ベクトル($${k}$$)を選択する。ここでの$${k}$$は新しい特徴量空間の次元である
5で選ばれた固有ベクトルで変換行列$${\bf{W}}$$を作る
$${\bf{x} \bf{W} = \bf{z}}$$で新しい特徴量空間を作る
このPCAはscikit_learnに搭載されていますが、雑音除去などの作業でPCAをパッケージに任せるのもざっくりしすぎてしまうし、共分散行列はこれからもちょくちょく出てくるので、慣れておくためにも、一度順を追ってプログラムを書いてみます。
例として使うのは、やはり同じワインデータです。
今までの復習も兼ねて、データ読み込みの最初から書いておきます。標準化で使うのは、StandardScalerです。
import pandas as pd
import numpy as np
df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data',
header=None)
df_wine.columns = ['Class label', 'Alcohol', 'Malic acid', 'Ash',
'Alcalinity of ash', 'Magnesium', 'Total phenols',
'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins',
'Color intensity', 'Hue', 'OD280/OD315 of diluted wines',
'Proline']
from sklearn.model_selection import train_test_split
X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=0, stratify=y)
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X_train_std = sc.fit_transform(X_train)
X_test_std = sc.transform(X_test)
cov_mat = np.cov(X_train_std.T)
eigen_vals, eigen_vecs = np.linalg.eig(cov_mat)
eigen_vals
固有値の出力は以下のようになります。
全ての固有値を足し上げた総量に対し、各固有値が占める割合で順序付けをします。大きい順でランク付けをしたのと結果は一緒ですが、各固有値が全体情報をどれだけ保持しているかの目安になります。
tot = sum(eigen_vals)
var_exp = [(i / tot) for i in sorted(eigen_vals, reverse=True)]
var_exp
これにより、一番大きい固有値に対する固有ベクトルが全体情報の30%以上を担っていることがわかります。
そしてこれを全て足し上げて、
cum_var_exp = np.cumsum(var_exp)
cum_var_exp
情報量をどれだけ使いたいかで、次の$${k}$$次元を決めるのです。
例えば、ここで、60%で良いと思えば、$${k}$$は3となり、固有値の大きい三つのベクトルを選べば、変換行列$${\bf{W}}$$が作れます。
eigen_pairs = [(np.abs(eigen_vals[i]), eigen_vecs[:, i])
for i in range(len(eigen_vals))]
eigen_pairs.sort(key=lambda k: k[0], reverse=True)
w = np.hstack((eigen_pairs[0][1][:, np.newaxis],
eigen_pairs[1][1][:, np.newaxis],
eigen_pairs[2][1][:, np.newaxis]))
print('Matrix W:\n', w)
このまま進めても良いのですが、例として、PCA後の変数分布は2次元平面が見やすいので、上位二つの固有値を選び、$${13\times2}$$の変換行列$${\bf{W}}$$を作ります。
w = np.hstack((eigen_pairs[0][1][:, np.newaxis],
eigen_pairs[1][1][:, np.newaxis]))
print('Matrix W:\n', w)
これを元々の13次元の訓練ベクトルにかけて、2次元に落とした訓練ベクトルの分布が以下です。訓練ベクトルのクラス分け$${y}$$の値で色別して示します。
X_train_pca = X_train_std.dot(w)
colors = ['r', 'b', 'g']
markers = ['s', 'x', 'o']
for l, c, m in zip(np.unique(y_train), colors, markers):
plt.scatter(X_train_pca[y_train == l, 0],
X_train_pca[y_train == l, 1],
c=c, label=l, marker=m)
plt.xlabel('X_PCA1')
plt.ylabel('x_PCA2')
plt.legend(loc='lower left')
plt.tight_layout()
plt.show()
クラス分けのクラスターが綺麗にできているので、問題がシンプルになったことがわかります。
sciket_learnには、次元削減の方法としてPCAがカバーされているので、それを使えば、共分散行列を自前で作る必要はありません。
ただ、使用している情報量がブラックボックスであることに留意する必要があるでしょう。
from sklearn.linear_model import LogisticRegression
pca = PCA(n_components=2)
X_train_pca = pca.fit_transform(X_train_std)
X_test_pca = pca.transform(X_test_std)
lr = LogisticRegression(multi_class='ovr', random_state=1, solver='lbfgs')
lr = lr.fit(X_train_pca, y_train)