次元削減の基礎

## はじめに
株式会社POLという会社でエンジニアをやっている @HHajimeW です。
この記事は「POL Advent Calendar 2020」の20日目の記事です。

昨日の @sho-kanamaru さん（あだ名はなっつ）からバトンを頂きました。

最近、機械学習を勉強しようとおもって、[機械学習クックブック](https://www.oreilly.co.jp/books/9784873118673/)を読んでいたのですが、その中で次元削減についてまとめられていてわかりやすかったので、紹介したいと思います。
上述の本の9章と10章を参考にしているので、興味のある方はぜひ、そちらも見てみてください。

## 次元削減とは
英語のWikipediaの[次元削減](https://en.wikipedia.org/wiki/Dimensionality_reduction)を翻訳したものでは以下のように説明されています。 
>次元削減は、高次元空間から低次元空間へのデータの変換であり、低次元表現は、理想的にはその固有の次元に近い、元のデータのいくつかの意味のあるプロパティを保持します。

次元削減とは文字通り、特徴量ベクトルの次元を削減することであり、その目的は元の特徴量ベクトルの情報を保持したまま特徴量の数を減らすことです。

手法としては大きく分けて以下の2つがあります。

* 特徴量抽出による次元削減
    * 高次元の空間のデータを低次元の空間のデータに変換
    * 変換されたデータが人間が理解できないものになりがち
* 特徴量選択による次元削減
    * 多くの情報を持つ特徴量を選択し、影響の少ない特徴量を取り除く

今回の記事では解釈ができる範囲での次元削減が気になったので、特徴量選択による次元削減の手法である以下の4つを紹介します。

1. 数値特徴量の分散による閾値処理
2. 2値特徴量の分散閾値処理
3. 強く相関した特徴量の処理
4. クラス分類に無関係な特徴量の削除

## 数値特徴量の分散による閾値処理
この方法では、分散が低い特徴量は情報量が少ないと考えています。つまり、ほとんどのデータで似たような値になる特徴量は価値が薄いと考えて、その特徴量を捨てるということです。

以下は参考文献①のp.166のソースコードです。特徴量が数値である場合は、特徴量データから分散を計算し閾値を設定することで、特徴量を選択しています。

```python
# ライブラリをロード
from sklearn import datasets
from sklearn.feature_selection import VarianceThreshold

# テスト用のデータをロード
iris = datasets.load_iris()

# 特徴量とターゲットを作成
features = iris.data
target = iris.target

# 閾値を設定
thresholder = VarianceThreshold(threshold=.5)

# 分散の大きい特徴量行列を作成
features_high_variance = thresholder.fit_transform(features)

# 分散の大きい特徴量行列を表示
features_high_variance[0:3]
```

結果

```
array([[ 5.1, 1.4, 0.2],
       [ 4.9, 1.4, 0.2],
       [ 4.7, 1.3, 0.2]])
```


## 2値特徴量の分散による閾値処理
2値特徴量でも同じように考えて、分散が低い特徴量を捨てることができます。数値特徴量と異なる点としては、2値特徴量はベルヌーイ分布にしたがうと考えることができるため、閾値を分散の値で設定するのではなく、2値のうちの一方がでる確率pの閾値で設定することができます。

以下は参考文献①のp.167のソースコードです。以下の例では確率pを0.75で設定しているため、全体のデータのうち同じ値が75%以上の特徴量は捨てられます。

```python
# ライブラリをロード
from sklearn.feature_selection import VarianceThreshold

# 特徴量行列を下記のように作成：
# 特徴量 0: 80% クラス 0
# 特徴量 1: 80% クラス 1
# 特徴量 2: 60% クラス 0, 40% クラス 1
features = [[0, 1, 0],
            [0, 1, 1],
            [0, 1, 0],
            [0, 1, 1],
            [1, 0, 0]]

# 分散で閾値処理
thresholder = VarianceThreshold(threshold=(.75 * (1 - .75))
features_high_variance - thresholder.fit_transform(features)
```

結果

```
array([[0],
       [1],
       [0],
       [1],
       [0]])
```

## 強く相関した特徴量の処理
2つの特徴量が強く相関しているということは、それらの特徴量が持つ情報が似ているということです。両方を保持するのは冗長なので、どちらかを捨てます。

以下は参考文献①のp.168,169のソースコードです。特徴量が数値である場合は、特徴量データから分散を計算し閾値を設定することで、特徴量を選択しています。

```python
# ライブラリをロード
import pandas as pd
import numpy as np

# 2つの特徴量が強く相関した特徴量行列を作成
features = np.array([[1, 1, 1],
                     [2, 2, 0],
                     [3, 3, 1],
                     [4, 4, 0],
                     [5, 5, 1],
                     [6, 6, 0],
                     [7, 7, 1],
                     [8, 8, 0],
                     [9, 9, 1]]

# 特徴量行列をDataFrameに変換
dataframe = pd.DataFrame(features)

# 相関行列を作成
corr_matrix = dataframe.corr().abs()

# 相関行列の上三角を選択
upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape),
                          k=1).astype(np.bool))

# 相関が0.95以上になる特徴量列のインデックスを抽出
to_drop = [column for column in upper.columns if any(upper[column] > 0.95)]

# 特徴量を削除
dataframe.drop(dataframe.columns[to_drop], axis=1).head(3)
```

結果

|  |0  |2  |
|---|---|---|
|0  |1  |1  |
|1  |2  |0  |
|1  |3  |1  |

## クラス分類に無関係な特徴量の削除
ターゲットベクトルがカテゴリの場合は、カイ二乗統計量を用いることで、情報を含まない特徴量を削除することができます。カイ二乗統計量は、 「実際に観測された回数」 と、 「2つの変数がまったく関係がなかった場合に観測されることが予測される回数」 との差を1つの数値として表したものです。この値が大きければ、特徴量とターゲットの相関が強いということなので、関連が高い順に特徴量を選択することができる。

以下は参考文献①のp.170のソースコードです。

```python
# ライブラリをロード
from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import shi2, f_classif

# データをロード
iris = load_iris()
feature = iris.data
target = iris.target

# データを整数に変換して、カテゴリデータとして扱う
features = features.astype(int)

# カイ二乗統計量が最大の2つの特徴量を選択
chi2_selector = SelectKBest(chi2, k=2)
features_kbest = chi2_selector.fet_transform(features, target)

# 結果を表示
print("もとの特徴量数：", features.shape[1])
print("削除後の特徴量数：", features_kbest.shape[1])
```

結果

```
もとの特徴量数： 4
削除語の特徴量数： 2
```

## おわりに
最後までお読みいただき、ありがとうございます！
知識としての次元削減の方法としては知っていましたが、実際に手を動かしたり、体系的にまとめてみたのは初めてだったので、統計量の復習とかにもなったりして、非常に勉強になりました。

特徴抽出の方も軽く調べたのですが、それぞれの手法のメリット・デメリット（t-SNEは視覚化に向いてるけど、距離・密度とかは保持されるとは限らないとか）とかもあったので、実データでもやってみてまとめたいなと思いました。

明日21日目は、まーさん（@takahashik0422）です！お楽しみに！

## 参考文献
1. [Python機械学習クックブック(9章・10章)](https://www.oreilly.co.jp/books/9784873118673/)
2. [Dimensionality Reductioin (Wikipedia)](https://en.wikipedia.org/wiki/Dimensionality_reduction)