Feature Selection#

변수 선택

Feature Selection은 주어진 고차원 데이터 분석 시에 모델링에 사용할 변수를 선택하거나 제거하는 방법입니다.

Further Reading

변수 자체의 Variance Threshold 를 통한 변수 제거#

Feature Selection의 가장 기본적인 접근방법으로, Variance 가 Threshold 보다 작은 변수를 제거합니다. Variance 가 낮다는 것은 변수 안에 같은 값의 데이터가 많다는 의미입니다.

예를 들면 변수의 데이터의 다수가 0 으로 채워져 있는 경우입니다.

Variance Threshold 는 Independent Variable(독립변수, x)와 Dependent Variable(종속변수, y)간의 관계를 고려하지 않기 때문에 분석 차원에서 제거시 이 부분을 고려해봐야 합니다.

# 경고 메시지 출력 끄기
import warnings 
warnings.filterwarnings(action='ignore')

%matplotlib inline
import matplotlib.pyplot as plt
import IPython

import sys

rseed = 22
import random
random.seed(rseed)

import numpy as np
np.random.seed(rseed)
np.set_printoptions(precision=3)
np.set_printoptions(formatter={'float_kind': "{:.3f}".format})

import pandas as pd
pd.set_option('display.max_rows', None) 
pd.set_option('display.max_columns', None) 
pd.set_option('display.max_colwidth', None)
pd.options.display.float_format = '{:,.5f}'.format

import sklearn

print(f"python ver={sys.version}")
print(f"pandas ver={pd.__version__}")
print(f"numpy ver={np.__version__}")
print(f"sklearn ver={sklearn.__version__}")
python ver=3.8.9 (default, Jun 12 2021, 23:47:44) 
[Clang 12.0.5 (clang-1205.0.22.9)]
pandas ver=1.2.4
numpy ver=1.19.5
sklearn ver=0.24.2
from sklearn import feature_selection

xs = np.array(
    [[0, 2, 0, 3],
     [0, 1, 4, 3],
     [0, 1, 1, 3]]
)
print(xs)

selector = feature_selection.VarianceThreshold()
fit = selector.fit(xs)
print(fit.variances_)

xs_selected = selector.fit_transform(xs)
print(xs_selected.shape)
print(xs_selected)
[[0 2 0 3]
 [0 1 4 3]
 [0 1 1 3]]
[0.000 0.222 2.889 0.000]
(3, 2)
[[2 0]
 [1 4]
 [1 1]]

Independet Variable와 Dependent Variable간의 관계를 통해 변수 제거#

주어진 고차원 독립변수(Independent Variable) 각각을 종속변수(Dependent Variable)와의 관계를 계산하여 그 중 모델의 성능에 영향을 줄수 있는 독립변수를 우선적으로 선택합니다.

독립변수와 종속변수간의 관계가 강한 Top-K 변수를 선택함으로써 아래와 같은 이점을 얻을 수 있습니다.

  • Overfitting 감소

  • 모델 성능 향상

  • 모델 훈련 시간 감소

Scikit-learn 에서는 단변량 통계 (Univariate Statistics)를 이용한 선택 방법 및 종속 변수의 범주에 따른 분석 방법이 Regression, Classification 이냐에 따라 다른 특징 선택 방법을 제공합니다.

변수 선택 방법

  • SelectKBest: 가장 높은 점수를 받은 K 개 변수외의 변수를 제거

  • SelectPercentile: 가장 높은 점수순으로 사용자가 제공한 Percent 를 제외한 변수를 제거

  • SelectFpr: False Positve Rate를 통해 변수를 제거

  • SelectFdr: False Discovery Rate를 통해 변수를 제거

  • SelectFwe: Family Wise Error를 통해 변수를 제거

종속 변수 분석 방법

  • Regression: f_regression, mutual_info_regression

  • Classification: chi2, f_classif, mutual_info_classif

Regression 문제에 Classification 특징 선택 함수를 쓸 경우에는 잘못된 결과를 얻을 수 있기 때문에 주의해야 합니다.

한가지 더 생각해보아야 할 점은 종속변수와 관계가 약한 독립변수의 제거에 있습니다. 많은 특징 데이터를 학습하여 복잡한 모델을 만들게 되는 딥러닝 기법에서는 약한 관계에 있는 변수들도 사용 시에 도움이 될 수 있기에 Top-K 변수를 사용하는 것 보다는 전혀 관계가 없는 변수만을 제거하는 것을 분석 차원 축소의 의미로 사용하는 것을 생각해보아야 합니다.

Regression#

Scikit-learn 에서 제공하는 샘플 데이터 셋에서 Regression 에 적합한 데이터 셋에 대해서 특징 선택을 수행해봅니다. 데이터 셋 로딩 함수의 도움말을 통해 데이터 컬럼 정보를 확인해 봅니다.

from sklearn import datasets

datasets.load_boston?
Signature: datasets.load_boston(*, return_X_y=False)
Docstring:
Load and return the boston house-prices dataset (regression).

==============   ==============
Samples total               506
Dimensionality               13
Features         real, positive
Targets           real 5. - 50.
==============   ==============

Read more in the :ref:`User Guide <boston_dataset>`.

Parameters
----------
return_X_y : bool, default=False
    If True, returns ``(data, target)`` instead of a Bunch object.
    See below for more information about the `data` and `target` object.

    .. versionadded:: 0.18

Returns
-------
data : :class:`~sklearn.utils.Bunch`
    Dictionary-like object, with the following attributes.

    data : ndarray of shape (506, 13)
        The data matrix.
    target : ndarray of shape (506, )
        The regression target.
    filename : str
        The physical location of boston csv dataset.

        .. versionadded:: 0.20

    DESCR : str
        The full description of the dataset.
    feature_names : ndarray
        The names of features

(data, target) : tuple if ``return_X_y`` is True

    .. versionadded:: 0.18

Notes
-----
    .. versionchanged:: 0.20
        Fixed a wrong data point at [445, 0].

Examples
--------
>>> from sklearn.datasets import load_boston
>>> X, y = load_boston(return_X_y=True)
>>> print(X.shape)
(506, 13)
File:      ~/.pyenv/versions/3.8.9/envs/skp-n4e-jupyter-sts/lib/python3.8/site-packages/sklearn/datasets/_base.py
Type:      function

전체 13개의 변수 중에 5개의 특징을 선택해 보도록 하겠습니다.

from sklearn import datasets, feature_selection

xs, ys = datasets.load_boston(return_X_y=True)
print(f"shape: xs={xs.shape}, ys={ys.shape}")

selector = feature_selection.SelectKBest(score_func = feature_selection.f_regression, k = 'all')
fit = selector.fit(xs, ys)

# Display score
columns = pd.DataFrame(np.arange(xs.shape[1]))
scores = pd.DataFrame(fit.scores_)
feature_scores = pd.concat([columns, scores], axis = 1)
feature_scores.columns = ['column', 'score']
display(feature_scores)

# Select Top-N score
display(feature_scores.nlargest(5, 'score'))

index =  feature_scores.nlargest(5, 'score')['column'].values
display(index)

xs_selected = xs[:, index]
print(xs_selected.shape)
display(xs_selected[:5])
shape: xs=(506, 13), ys=(506,)
column score
0 0 89.48611
1 1 75.25764
2 2 153.95488
3 3 15.97151
4 4 112.59148
5 5 471.84674
6 6 83.47746
7 7 33.57957
8 8 85.91428
9 9 141.76136
10 10 175.10554
11 11 63.05423
12 12 601.61787
column score
12 12 601.61787
5 5 471.84674
10 10 175.10554
2 2 153.95488
9 9 141.76136
array([12,  5, 10,  2,  9])
(506, 5)
array([[4.980, 6.575, 15.300, 2.310, 296.000],
       [9.140, 6.421, 17.800, 7.070, 242.000],
       [4.030, 7.185, 17.800, 7.070, 242.000],
       [2.940, 6.998, 18.700, 2.180, 222.000],
       [5.330, 7.147, 18.700, 2.180, 222.000]])

Classification#

Scikit-learn 에서 제공하는 샘플 데이터 셋에서 Classication 에 적합한 데이터 셋에 대해서 특징 선택을 수행해봅니다. 데이터 셋 로딩 함수의 도움말을 통해 데이터 컬럼 정보를 확인해 봅니다.

from sklearn import datasets

datasets.load_wine?
Signature: datasets.load_wine(*, return_X_y=False, as_frame=False)
Docstring:
Load and return the wine dataset (classification).

.. versionadded:: 0.18

The wine dataset is a classic and very easy multi-class classification
dataset.

=================   ==============
Classes                          3
Samples per class        [59,71,48]
Samples total                  178
Dimensionality                  13
Features            real, positive
=================   ==============

Read more in the :ref:`User Guide <wine_dataset>`.

Parameters
----------
return_X_y : bool, default=False
    If True, returns ``(data, target)`` instead of a Bunch object.
    See below for more information about the `data` and `target` object.

as_frame : bool, default=False
    If True, the data is a pandas DataFrame including columns with
    appropriate dtypes (numeric). The target is
    a pandas DataFrame or Series depending on the number of target columns.
    If `return_X_y` is True, then (`data`, `target`) will be pandas
    DataFrames or Series as described below.

    .. versionadded:: 0.23

Returns
-------
data : :class:`~sklearn.utils.Bunch`
    Dictionary-like object, with the following attributes.

    data : {ndarray, dataframe} of shape (178, 13)
        The data matrix. If `as_frame=True`, `data` will be a pandas
        DataFrame.
    target: {ndarray, Series} of shape (178,)
        The classification target. If `as_frame=True`, `target` will be
        a pandas Series.
    feature_names: list
        The names of the dataset columns.
    target_names: list
        The names of target classes.
    frame: DataFrame of shape (178, 14)
        Only present when `as_frame=True`. DataFrame with `data` and
        `target`.

        .. versionadded:: 0.23
    DESCR: str
        The full description of the dataset.

(data, target) : tuple if ``return_X_y`` is True

The copy of UCI ML Wine Data Set dataset is downloaded and modified to fit
standard format from:
https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data

Examples
--------
Let's say you are interested in the samples 10, 80, and 140, and want to
know their class name.

>>> from sklearn.datasets import load_wine
>>> data = load_wine()
>>> data.target[[10, 80, 140]]
array([0, 1, 2])
>>> list(data.target_names)
['class_0', 'class_1', 'class_2']
File:      ~/.pyenv/versions/3.8.9/envs/skp-n4e-jupyter-sts/lib/python3.8/site-packages/sklearn/datasets/_base.py
Type:      function

전체 13개의 변수 중에 5개의 특징을 선택해 보도록 하겠습니다.

from sklearn import datasets, feature_selection

xs, ys = datasets.load_wine(return_X_y=True)
print(f"shape: xs={xs.shape}, ys={ys.shape}")

selector = feature_selection.SelectKBest(score_func = feature_selection.f_classif, k = 'all')
fit = selector.fit(xs, ys)

# Display score
columns = pd.DataFrame(np.arange(xs.shape[1]))
scores = pd.DataFrame(fit.scores_)
feature_scores = pd.concat([columns, scores], axis = 1)
feature_scores.columns = ['column', 'score']
display(feature_scores)

# Select Top-N score
display(feature_scores.nlargest(5, 'score'))

index =  feature_scores.nlargest(5, 'score')['column'].values
display(index)

xs_selected = xs[:, index]
print(xs_selected.shape)
display(xs_selected[:5])
shape: xs=(178, 13), ys=(178,)
column score
0 0 135.07762
1 1 36.94342
2 2 13.31290
3 3 35.77164
4 4 12.42958
5 5 93.73301
6 6 233.92587
7 7 27.57542
8 8 30.27138
9 9 120.66402
10 10 101.31680
11 11 189.97232
12 12 207.92037
column score
6 6 233.92587
12 12 207.92037
11 11 189.97232
0 0 135.07762
9 9 120.66402
array([ 6, 12, 11,  0,  9])
(178, 5)
array([[3.060, 1065.000, 3.920, 14.230, 5.640],
       [2.760, 1050.000, 3.400, 13.200, 4.380],
       [3.240, 1185.000, 3.170, 13.160, 5.680],
       [3.490, 1480.000, 3.450, 14.370, 7.800],
       [2.690, 735.000, 2.930, 13.240, 4.320]])

Recursive 한 변수 조합 평가를 통한 변수 제거#

이 방법은 전체 종속 변수에 대한 서브셋을 만들고 그중 가장 나쁜 성능을 나타내는 변수 조합을 제거하는 방법입니다.

예를 들어 5개의 변수 중 3개의 변수를 선택한다고 하면, 재귀적으로 표현 가능한 변수의 조합 셋을 만들고, n-1 조합에서 가장 좋은 셋을 찾고 그 셋에서 n-2 조합 중에 가장 좋은 셋을 찾는 식입니다.

평가에 사용하는 함수는 원하는 모델 함수를 파라미터로 선택하여 이용할 수 있습니다. 이 방법은 변수의 수가 많은 고차원 데이터 셋에서는 많은 시간이 들기 때문에 고차원 데이터라면 추천되지는 않습니다.

Regression#

from sklearn import datasets, feature_selection, svm

xs, ys = datasets.load_boston(return_X_y=True)
print(f"shape: xs={xs.shape}, ys={ys.shape}")

estimator = svm.SVR(kernel="linear")
selector = feature_selection.RFE(estimator, 5)
%time fit = selector.fit(xs, ys)

# Display score
columns = pd.DataFrame(np.arange(xs.shape[1]))
ranks = pd.DataFrame(fit.ranking_)
feature_ranks = pd.concat([columns, ranks], axis = 1)
feature_ranks.columns = ['column', 'rank']
display(feature_ranks)

# Select Top-N score
index = fit.support_
display(index)

xs_selected = xs[:, index]
print(xs_selected.shape)
display(xs_selected[:5])
shape: xs=(506, 13), ys=(506,)
CPU times: user 3.6 s, sys: 8.43 ms, total: 3.61 s
Wall time: 3.62 s
column rank
0 0 3
1 1 5
2 2 4
3 3 1
4 4 1
5 5 1
6 6 6
7 7 2
8 8 8
9 9 9
10 10 1
11 11 7
12 12 1
array([False, False, False,  True,  True,  True, False, False, False,
       False,  True, False,  True])
(506, 5)
array([[0.000, 0.538, 6.575, 15.300, 4.980],
       [0.000, 0.469, 6.421, 17.800, 9.140],
       [0.000, 0.469, 7.185, 17.800, 4.030],
       [0.000, 0.458, 6.998, 18.700, 2.940],
       [0.000, 0.458, 7.147, 18.700, 5.330]])

Classification#

from sklearn import datasets, feature_selection, svm

xs, ys = datasets.load_wine(return_X_y=True)
print(f"shape: xs={xs.shape}, ys={ys.shape}")

estimator = svm.SVC(kernel="linear")
selector = feature_selection.RFE(estimator, 5)
%time fit = selector.fit(xs, ys)

# Display score
columns = pd.DataFrame(np.arange(xs.shape[1]))
ranks = pd.DataFrame(fit.ranking_)
feature_ranks = pd.concat([columns, ranks], axis = 1)
feature_ranks.columns = ['column', 'rank']
display(feature_ranks)

# Select Top-N score
index = fit.support_
display(index)

xs_selected = xs[:, index]
print(xs_selected.shape)
display(xs_selected[:5])
shape: xs=(178, 13), ys=(178,)
CPU times: user 103 ms, sys: 1.82 ms, total: 105 ms
Wall time: 103 ms
column rank
0 0 1
1 1 4
2 2 1
3 3 5
4 4 8
5 5 3
6 6 1
7 7 1
8 8 6
9 9 2
10 10 7
11 11 1
12 12 9
array([ True, False,  True, False, False, False,  True,  True, False,
       False, False,  True, False])
(178, 5)
array([[14.230, 2.430, 3.060, 0.280, 3.920],
       [13.200, 2.140, 2.760, 0.260, 3.400],
       [13.160, 2.670, 3.240, 0.300, 3.170],
       [14.370, 2.500, 3.490, 0.240, 3.450],
       [13.240, 2.870, 2.690, 0.390, 2.930]])

Modeling 을 통한 Feature selection#

이 방법은 모델 알고리즘 중에 훈련시에 변수의 중요도가 뽑혀저 나오는 알고리즘을 이용하여 중요도에 따라서 Feature를 선택하는 방법입니다. 이 방법의 장점은 Feature 선택과 동시에 Baseline 모델로도 활용할 수 있다는 것입니다.

Regression#

estimator로 LinearRegression을 사용해도 되지만 좀 더 좋은 선택을 위해 Lasso Regression를 사용해봅니다. Lasso는 L1 Regularization 을 통해 모델의 복잡성을 줄이면서 Overfit 을 방지하는 Regression 알고리즘입니다. 이 알고리즘을 통해 모델을 훈련시킬때 사용되는 coefficient 를 통해 변수를 선택합니다. 사용할 수 있는 다른 estimator는 LogisticRegression(linear_model.LogisticRegression), LinearSVM(svm.LinearSVC) 이 있습니다.

from sklearn import datasets, feature_selection, linear_model

xs, ys = datasets.load_boston(return_X_y=True)
print(f"shape: xs={xs.shape}, ys={ys.shape}")

estimator = linear_model.LassoCV(cv=5) # cv: cross validation
selector = feature_selection.SelectFromModel(estimator, threshold=0.25)
%time fit = selector.fit(xs, ys)

xs_seleted = fit.transform(xs)
print(xs_selected.shape)
shape: xs=(506, 13), ys=(506,)
CPU times: user 63.8 ms, sys: 1.01 ms, total: 64.8 ms
Wall time: 65 ms
(178, 5)

Classification#

estimator로 Tree 기반 알고리즘인 RandomForest를 사용해봅니다.

from sklearn import datasets, feature_selection, ensemble

xs, ys = datasets.load_wine(return_X_y=True)
print(f"shape: xs={xs.shape}, ys={ys.shape}")

estimator = ensemble.RandomForestClassifier(n_estimators=100)
selector = feature_selection.SelectFromModel(estimator, threshold='1.25*median')
%time fit = selector.fit(xs, ys)

xs_seleted = fit.transform(xs)
print(xs_seleted.shape)
shape: xs=(178, 13), ys=(178,)
CPU times: user 161 ms, sys: 1.85 ms, total: 163 ms
Wall time: 163 ms
(178, 5)