次元削減の概要を掴んで実装してみる

「カーネル多変量解析」を読んで、ゆるふわな理解を進めています。全体像はこちら。次から第3章ですが、そこで必要になる前提知識、主成分分析を理解したいなぁと思います。

が、その前に主成分分析のイメージを掴むための準備運動として次元削減を体感してみます。

この記事のゴール

次元削減の概念をざっくり理解するために、簡単な次元削減を実装してイメージを掴む。

ちなみに、今回の記事は下記の書籍をめちゃ参考にしています。

前処理としての次元削減

機械学習の流れを示すと、こんな感じらしいです。

  1. 前処理でデータをいい感じにする

  2. アルゴリズムで学習する

  3. 学習結果を評価する

  4. 未知の値を予測する

で、この「1. 前処理でデータをいい感じにする」が今回の主題なんですが、このステップはアルゴリズムの予測性能をあげたり、高速化したりするのに重要なステップです。

この「前処理」の過程の一つに次元削減があります。次元削減っていうのは、特微量の数を減らすんですね。あんまり重要じゃなさそうなデータは無視しちゃう。そうすると、計算が速くなったり、過学習が起きにくくなって予測が良くなったりします。

で、更に次元削減には2種類あります。特微量選択と特微量抽出です。

  • 特微量選択:元の特微量の中から良さそうなものだけピックアップする

  • 特微量抽出:特微量から別の特微量を幾つか作って、元の特微量は無視する(元特微量の数より新特微量の数を少なくして次元を圧縮する)

んで、主成分分析は特微量抽出です。入れ子になってわかりづらいので言い直すと、主成分分析は、学習の前の前処理の過程で使われるもので、次元削減のために特微量抽出をする方法らしいです。

わかったような、わかんないような。イメージをつけやすくするために今回はイメージしやすい特微量選択を先にやってから、次回特微量抽出の主成分分析をやってみます。

準備1:データを用意

特微量選択(と次回の特微量抽出)を試すために、wineデータを使います。$${y}$$であるワインの種類が1列目のClassにあり、2-14行目の13個が特微量$${x}$$です。177サンプルあります。

     Class  Alcohol  Malic acid  ...   Hue  OD280/OD315  Proline
0        1    13.20        1.78  ...  1.05         3.40     1050
1        1    13.16        2.36  ...  1.03         3.17     1185
2        1    14.37        1.95  ...  0.86         3.45     1480
..     ...      ...         ...  ...   ...          ...      ...
175      3    13.17        2.59  ...  0.60         1.62      840
176      3    14.13        4.10  ...  0.61         1.60      560

データは、こんな感じで貰います。毎回取得するの面倒なのでcsvでローカルに保存してロードするようにしてます。

import pandas as pd

def data():
	raw = _load()
	X = raw.iloc[:, 1:].values
	y = raw.iloc[:, 0].values
	return X, y

def _load(from_web=False):
	if(from_web):
		return _load_from_web()
	return _load_from_local()

def _load_from_web():
	url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data'
	return pd.read_csv(url)

def _load_from_local():
	return pd.read_csv('wine.data')

特微量選択の例:逐次特微量選択アルゴリズム

特微量選択は、元の特微量の中から良さそうなものだけをピックアップするものでした。それを単純に行うのが、逐次特微量選択アルゴリズムです。

逐次特微量選択アルゴリズムは、予想性能を見ながら特微量を一個ずつ減らしていく方法です。wineデータの例でいうと、こういうステップになります。

  1. 13個の特微量の中から1つ外してみて予想の正確さがどのくらい下がるか評価してみる

  2. 1を各特微量に対して実行する(13回計算する)

  3. 一番正確さが下がらなかった特微量を外す

  4. 12個の特微量に対して1-3を行う。終わったら11個の特微量に対して1-3を行う……と繰り返して特微量を減らしていく

  5. 正確さが満足いく数値以下になったらやめる

単純ですね。

準備2:予想値の計算式を用意する

今回の主題は「前処理」なので、「学習」「評価」の部分は決め打ちにしちゃいます。方法に興味なければ次の章に飛んでください。

「学習」「評価」を行う関数は下記のような設計にします。

  • 前処理されたXとyを受けて、そのデータからどのくらい正確に予想できるかを返す関数

  • 受けたデータを7:3に分けて、7で学習train、3で評価test

  • 学習は、trainデータを標準化した上でガウスカーネルを使ったサポートベクトルマシンで実行

  • 評価はtestデータに対する正解率

学習の「標準化」とか「サポートベクトルマシン」はこのシリーズでは出てきてないですが、今回の論点じゃないので取り上げません。また、評価の方法も色々ありますが、こちらも今回の論点じゃないので適当にします。

学習・評価を行うコードとしてはこんな感じ。predictibity.pyファイルに保存しました。モデル違うの使いたくなったり、評価をクロスバリデーションとかにしたくなったらこのファイルだけ書き換えればOKです。

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn import svm

def score(X, y):
	X_train, X_test, y_train, y_test \
			= train_test_split(X, y, stratify=y, test_size=0.3, random_state=1)
	stdsc = StandardScaler()
	stdsc.fit(X_train)
	X_train = stdsc.transform(X_train)
	X_test = stdsc.transform(X_test)
	m = svm.SVC(kernel='rbf', random_state=1, gamma=0.01, C=10.0)
	m.fit(X_train, y_train)
	return m.score(X_test, y_test)

逐次特微量選択アルゴリズムを実行してみる

準備できたので実行します。コードはシンプル

import numpy as np
from itertools import combinations
import wine
import predictability

X, y = wine.data()
dim = X.shape[1]
indices = tuple(range(dim)) #残っている列

while dim > 1:
	scores = []#各パターンのスコア
	subsets = []#各パターンで計算される列の集合
	drops = []#各パターンで落とされた列
	#indicesからdim-1個を抽出した全パターンで実行
	for one_less_indices in combinations(indices, dim - 1):
		scores.append(predictability.score(X[:, one_less_indices], y))
		subsets.append(one_less_indices)
		drops.append(np.setdiff1d(indices,one_less_indices))
	best_idx = np.argmax(scores)
	print("drop:",drops[best_idx][0],", indices:",subsets[best_idx], ", score:", scores[best_idx])
	indices = subsets[best_idx]
	dim -= 1

結果

drop: 4 , indices: (0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12) , score: 1.0
drop: 12 , indices: (0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11) , score: 1.0
drop: 11 , indices: (0, 1, 2, 3, 5, 6, 7, 8, 9, 10) , score: 1.0
drop: 10 , indices: (0, 1, 2, 3, 5, 6, 7, 8, 9) , score: 1.0
drop: 6 , indices: (0, 1, 2, 3, 5, 7, 8, 9) , score: 1.0
drop: 5 , indices: (0, 1, 2, 3, 7, 8, 9) , score: 1.0
drop: 7 , indices: (0, 1, 2, 3, 8, 9) , score: 0.9814814814814815
drop: 2 , indices: (0, 1, 3, 8, 9) , score: 0.9444444444444444
drop: 1 , indices: (0, 3, 8, 9) , score: 0.9444444444444444
drop: 0 , indices: (3, 8, 9) , score: 0.9259259259259259
drop: 3 , indices: (8, 9) , score: 0.8703703703703703
drop: 8 , indices: (9,) , score: 0.7962962962962963

特微量$${(0, 1, 2, 3, 7, 8, 9)}$$だけにしても100%正解。これだけでも次元を6つも削れました。計算量もかなり削れることでしょう。

なお、このscoreは未知データに対してでないことだけ注意した方がよさそうです。同じテストデータで特微量選択しているので、テストデータ自体が訓練に使われてしまっています。

最後まで残った特微量8と9でデータをマッピングするとこんな感じです。x軸が8、y軸が9のデータで、3種類のクラスに色分けしています。この2種類だけでだいぶきれいに分かれてますね。

特微量8と9でデータをマッピング(左:trainデータ、右:testデータ)

ちなみにこれを行列で表すと右から13x2行列を掛けて、13次元から2次元の圧縮を行ってるってことですね。
$${\begin{pmatrix}x^{(1)}_1&x^{(1)}_2&x^{(1)}_3&…&x^{(1)}_{13}\\x^{(2)}_1&x^{(2)}_2&x^{(2)}_3&…&x^{(2)}_{13}\\…&…&…&…&…\\x^{(n)}_1&x^{(n)}_2&x^{(n)}_3&…&x^{(n)}_{13}\end{pmatrix}\begin{pmatrix}0&0\\0&0\\0&0\\0&0\\0&0\\0&0\\0&0\\1&0\\0&1\\0&0\\0&0\\0&0\\0&0\end{pmatrix}=\begin{pmatrix}x^{(1)}_8&x^{(1)}_9\\x^{(2)}_8&x^{(2)}_9\\…&…\\x^{(n)}_8&x^{(n)}_9\end{pmatrix}}$$


なんとなく、前処理に使う感じがつかめました。たぶん。ということで次は本丸、主成分分析をやってみます(本丸と言いつつ、第3章の前提知識ですが……)
次回はこちら

この記事が気に入ったらサポートをしてみませんか?