【機械学習】特徴量選択～組み込み法をscikitlearnで実装してみる～

#1.目的
機械学習における特徴量選択について、初学者向けの説明が易しくなかったり、きちんとまとまっていなかったりするなと思い、まとめてみました。

今回は、特徴量選択の中でも組み込み法についてです。
学習に際して、[機械学習のための特徴量エンジニアリング]
(https://www.oreilly.co.jp/books/9784873118680/)を参考にしています。

#2.　特徴量選択について
特徴量選択とは、有用でない特徴量を取り除くことでモデルの複雑さを軽減する手法です。

特徴量選択には、主に下記の３種類があります。

| 　　　| フィルタ法| ラッパー法|組み込み法       
|:------|-------:|:-------:|:------------:|:------------:|
| 計算時間 | 　◎　　| △     | 〇            |
| 精度 | 　     △   　　|　 ◎　      | 〇            |

##(1)フィルタ法
相関係数などの統計量を計算し、特徴量を選択する方法。
たとえば、説明変数同士で相関が高い変数がある場合、どちらか片方の変数を削除します。
計算時間は少なく済みますが精度の点でやや劣ります。

##(2)ラッパー法
モデルを学習する中で、学習と特徴選択を何度も繰り返し、ベストな特徴量を選択します。精度は非常に良いですが、「何度も繰り返す」ため、計算時間がかかります。

##(3)組み込み法
今回投稿するのはこちらです。モデルの学習プロセスに、特徴量選択が組み込まれていることから「組み込み法」と呼ばれます。
言葉だとわかりづらいと思うので、実際にScikit-learnを用いて実装してみましょう。

#3.scikit-learnで組み込み法を実装してみる
##(1)データセット
kaggleのKickstarter Projectsのデータセットを用います。
https://www.kaggle.com/kemical/kickstarter-projects

##(2)必要なもののインポート、データ読み込み
###(ⅰ)インポート
今回の実装には直接は不要なものも含まれていますが、ひとまずインポートします。

```python
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import math
import pandas as pd
import seaborn as sns
matplotlib.style.use('ggplot')

from sklearn.linear_model import SGDClassifier
from sklearn.metrics import log_loss, accuracy_score, confusion_matrix

from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import datetime
%matplotlib inline

from sklearn.model_selection import KFold 
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler

from sklearn.feature_selection import SelectFromModel
from sklearn.linear_model import LassoCV
```

###(ⅱ)データ読み込み

```python
df = pd.read_csv(r"C:~~\ks-projects-201801.csv")
```

###(ⅲ)データ外観
下記より、(378661, 15)のデータセットであることが分かります。

```python
df.shape
```

また、.headでデータをざっと確認しておきましょう。

```python
df.head()
```

##(3)データ成形
###(ⅰ)募集日数
今回はあくまで特徴量選択に主軸を置くので詳細は割愛しますが、クラウドファンディングの募集開始時期と終了時期がデータの中にありますので、これを「募集日数」に変換します。

```python
df['deadline'] = pd.to_datetime(df["deadline"])
df["launched"] = pd.to_datetime(df["launched"])
df["days"] = (df["deadline"] - df["launched"]).dt.days
```
###(ⅱ)目的変数について
こちらも詳細は割愛しますが、目的変数である「state」が成功（"successful")と失敗("failed")以外にもカテゴリがありますが、今回は成功と失敗のみのデータにします。

```python
df = df[(df["state"] == "successful") | (df["state"] == "failed")]
```

この上で、成功を1、失敗を0に置き換えます。

```python
df["state"] = df["state"].replace("failed",0)
df["state"] = df["state"].replace("successful",1)
```

###(ⅱ)不要な行の削除
特徴量選択の前に、不要だと思われるidやname（これは本来は残しておくべきかもしれないですが今回は消します）、そしてクラウドファンディングをしてからでないとわからない変数を削除します。

```python
df = df.drop(["ID","name","deadline","launched","backers","pledged","usd pledged","usd_pledged_real","usd_goal_real"], axis=1)
```

###(ⅲ)カテゴリ変数処理
pd.get_dummiesでカテゴリ変数処理を行います。

```python
df = pd.get_dummies(df,drop_first = True)
```

###(ⅳ)標準化
詳細は割愛しますが、一部の列に標準化もしてみます。
※本来的には標準化はこれ以降に行った方がいいのですが、今回はデータ量も十分なので、ここで標準化を行っておきます。

```python
stdsc = StandardScaler()
df["goal"] = stdsc.fit_transform(df[["goal"]].values)
df["days"] = stdsc.fit_transform(df[["days"]].values)
```

##(4)いよいよ本題～データ分割と、組み込み法による特徴量選択～
###(ⅰ)データ分割
訓練データとテストデータにまずは分割します。

```python
train_data = df.drop("state", axis=1)
y = df["state"].values
X = train_data.values

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1234)
```

###(ⅱ)組み込み法

```python
estimator = LassoCV(normalize = True, cv = 10)
sfm = SelectFromModel(estimator, threshold = 1e-5)
sfm.fit(X_train,y_train)
```

◆1行目 ```estimator = LassoCV(normalize = True, cv = 10)```
ここでは、クロスバリデーションと LASSO 回帰を同時に実行してくれる LassoCVを使います。normalizeをTrueにすることで、正則化は自動計算しています。cvにはクロスバリデーションするFold（=グループ）の数を入れます。

◆2行目　```sfm = SelectFromModel(estimator, threshold = 1e-5)```
SelectFromModelは機械学習を用いて特徴量選択する方法です。
ざっくりした意味合いとしては、「estimatorでラッソ回帰を実行するから、その過程で特徴量選択していきますよ！それをsfmとして定義します」という感じです。

また、thresholdは係数の閾値設定で、ここに入れた数値以下である特徴量が削除されます。

続けていきます。

```python
X_train_selected = sfm.transform(X_train)
X_test_selected = sfm.transform(X_test)
classifier = SGDClassifier()
classifier.fit(X_train_selected, y_train)
classifier.score(X_test_selected, y_test)
```

◆1,2行目```X_train_selected = sfm.transform(X_train)```と```X_test_selected = sfm.transform(X_test)```

sfm.fit(X_train,y_train)だけでは、まだ特徴量が削減されていない状態なので、X_train_selectedとX_test_selectedに、特徴量を削減した状態のデータをtransformで上書きます。

◆3～5行目```classifier = SGDClassifier()
classifier.fit(X_train_selected, y_train)
classifier.score(X_test_selected, y_test)```

特徴量を削減した状態に上書きしたデータでロジスティック回帰モデルにfitさせ、精度を出しています。

ここまでで一連の流れは終了です。


#4.組み込み法補足
##(1)詳細
3ではとりあえずScikit-learnで実装してみました。ここでは、具体的にどの説明変数が削除されているのか？といったことを確認してみたいと思います。

###(ⅰ)使用されている特徴量
下記コードで、Trueになっている特徴量が使用される特徴量です。

```python
sfm.get_support()
```
このようにarrayで出力されます。
<img width="237" alt="キャプチャ1.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/465870/594e5d53-7726-dae5-09cf-25390ed4cac0.png">

###(ⅱ)削除すべき特徴量の表示
「~」をつけるとTrueとFalseを逆にできるので、下記のように記述する

```python
removed_idx = ~sfm.get_support()
train_data.columns[removed_idx]
```

そうすると、「Index(['category_Children's Books', 'category_Design', 'category_Kids',
       'category_Vegan', 'main_category_Design', 'main_category_Film & Video',
       'main_category_Games', 'main_category_Technology', 'currency_CAD',
       'currency_NOK', 'country_CH', 'country_DK', 'country_GB', 'country_JP',
       'country_MX', 'country_NZ', 'country_SE', 'country_US'],
      dtype='object')」と表示され、これらが組み込み法によって削除すべきと判断された特徴量です、。

###(ⅲ)組み込み法が使えない場合
組み込み法はLassoCVと記載している通り、ラッソ回帰を使用しています。ラッソ回帰は線形のモデルを前提としているため、目的変数と説明変数に線形の関係性がない場合は組み込み法は使わず、冒頭に紹介した別のフィルタ法やラッパー法を使うことになります。

#5.最後に
いかがでしたでしょうか。kaggleなどに取り組むと、特徴量が多すぎて、どの特徴量を選べばいいのかわからないという壁に直面すると思います。
特徴量選択についての初学者向けの説明や、具体的にコードはこう使う、という説明が世の中に多くは出回ってないと思いまとめてみました。

少しでも理解の深化の助けになりましたら幸いです。