【Kaggle】LightGBMを使ってアンサンブル学習をしてみた


# 0. はじめに

今回は学習モデルの手法の一つである、**LightGBM** について調べてみました。
LightGBM はアンサンブル学習のひとつとして Kaggle でもよく利用されます。
伴って、その周辺のプロセス(ROC曲線のプロットなど)も必要になるので、ついでにまとめておきました。

参考にした記事はこちらです。

- [LightGBMの解説](http://data-analysis-stats.jp/2019/11/13/lightgbm%E3%81%AE%E8%A7%A3%E8%AA%AC/)
- [Python: LightGBM を使ってみる](https://blog.amedama.jp/entry/2018/05/01/081842#%E4%BA%8C%E5%80%A4%E5%88%86%E9%A1%9E%E5%95%8F%E9%A1%8C-Breast-Cancer-%E3%83%87%E3%83%BC%E3%82%BF%E3%82%BB%E3%83%83%E3%83%88)
- [lightgbmで二値分類の一連の流れをしたメモ](https://qiita.com/d_desuyon/items/807e01311ad08570ee78)


# 1. LightGBM のインストール

```terminal:terminal
pip install lightgbm
```
します。しかし、

```python:jupyter
import lightgbm as lgb 
```
でエラーが出たので対処法を調べたところ、

[LightGBMのimportでエラー](https://qiita.com/yoko8ma/items/62f0b59e443fce90855d)

に辿り着きました。
これに従って、

```terminal:terminal
brew install libomp
```

をすると、無事解決しました。
これで先に進めます。

# 2. 擬似的なデータセットを用意する

まずは必要なライブラリをインポートします。

```python:In[1]
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

print(pd.__version__) #スクリプト上でバージョンを確認できる。
```

```:Out[1]
0.21.0
```

次にデータセットを読み込みます。
今回は scikit-learn のデータセットにあるアヤメのデータを使用します。


```python:In[2]
from sklearn.datasets import load_iris

iris = load_iris()
#print(iris.DESCR)  #データセットに関する説明を表示
print(type(iris))
```

```:Out[2]
<class 'sklearn.utils.Bunch'>
```




```python:In[3]
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['target'] = iris.target

print(df.shape)
df.head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541751/98bb41aa-e41b-95e8-8571-a986a27cc7d1.png)


2変数だけでこのデータをプロットしてみます。

```python:In[4]
fig = plt.figure(figsize = (5,5))
ax = fig.add_subplot(111)

plt.scatter(df[df['target']==0].iloc[:,0], df[df['target']==0].iloc[:,1], label='Setosa')
plt.scatter(df[df['target']==1].iloc[:,0], df[df['target']==1].iloc[:,1], label='Versicolour')
plt.scatter(df[df['target']==2].iloc[:,0], df[df['target']==2].iloc[:,1], label='Virginica')
plt.xlabel("sepal length[cm]", fontsize=13)
plt.ylabel("sepal width[cm]", fontsize=13)

plt.legend()
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541751/693c38a4-9827-8e75-ae3a-417a7f0a56f5.png)

今回はこの3種類の中で比較的に分類が難しそうな Versicolour と Virginica の2クラス分類問題として解いてみようと思います。そのため、ラベルが0のものは除外し。残りの2種類に0と1のラベルを与えます。

```python:In[5]
data = df[df['target']!=0]
data.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541751/a706fba7-e762-8a29-5b43-eac37563bf8a.png)


```python:In[6]
data['target'] = data['target'] - 1

X = data.drop('target', axis=1)
y = data['target']
```

```python:Out[6]
/Users/{user}/.pyenv/versions/3.6.6/lib/python3.6/site-packages/ipykernel_launcher.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  """Entry point for launching an IPython kernel.
```
このWarningをうまく処理できなかったので、対処法をご存知の方は教えてください...


# 3. LightGBMで学習する

その前に、訓練データとテストデータを分割します。今回は、交叉検証を行うので validation データも用意して置きます。

```python:In[7]
from sklearn.model_selection import train_test_split

X_trainval, X_test, y_trainval, y_test = train_test_split(X, y, test_size=0.2, random_state=7, stratify=y)
X_train, X_val, y_train, y_val = train_test_split(X_trainval, y_trainval, test_size=0.2, random_state=7, stratify=y_trainval)
```

ようやく本題です。lightgbm モデルを構築していきます。

## ハイパーパラメーターについて

lightgbmの関数trainの引数paramsやその他の引数がハイパーパラメーターになります。
それに関するドキュメンテーションは[こちら](https://lightgbm.readthedocs.io/en/latest/Parameters.html)です。

また、以下の記事も参考になると思います。

- [LightGBM 徹底入門 – LightGBMの使い方や仕組み、XGBoostとの違いについて](https://www.codexa.net/lightgbm-beginner/)
- [【機械学習】ハイパーパラメータチューニングの仕方](https://qiita.com/R1ck29/items/50ba7fa5afa49e334a8f)


ここでは、重要そうなものだけ説明しておきます。

- objective : 今回は2クラス分類なので binary を指定しています。
- metric : 2クラス分類に使用可能な指標のひとつである auc を指定しています。
- early_stopping_rounds : 指定したラウンド数で metric が改善しない場合、その前で学習を打ち切る。

※理解が誤っているかもしれませんが、その時は訂正お願いします。

```python:In[8]
import lightgbm as lgb

lgb_train = lgb.Dataset(X_train, y_train)
lgb_val = lgb.Dataset(X_val, y_val)

params = {
    'metric' :'auc', #binary_logloss でも可能
    'objective' :'binary',
    'max_depth' :1,
    'num_leaves' :2,
    'min_data_in_leaf' : 5,    
}

evals_result = {} #結果を格納するための辞書

gbm = lgb.train(params,
                lgb_train,
                valid_sets = [lgb_train, lgb_val],
                valid_names = [ 'train', 'eval'],
                num_boost_round = 500,
                early_stopping_rounds = 20,
                verbose_eval = 10,
                evals_result = evals_result
               )
```


```:Out[8]
Training until validation scores don't improve for 20 rounds
[10]	train's auc: 0.997559	eval's auc: 0.914062
[20]	train's auc: 0.997559	eval's auc: 0.914062
Early stopping, best iteration is:
[3]	train's auc: 0.998047	eval's auc: 0.914062
```


# 4. 学習曲線をプロットする

先ほどの train 関数の引数に入れておいた evals_result の中に指標に指定しておいた auc に関する記録が含まれています。
これを利用して学習曲線を書いていきます。

```python:In[9]
print(evals_result.keys())
print(evals_result['eval'].keys())
print(evals_result['train'].keys())

train_metric = evals_result['train']['auc']
eval_metric = evals_result['eval']['auc']
train_metric[:5], eval_metric[:5]
```


```:Out[9]
dict_keys(['train', 'eval'])
odict_keys(['auc'])
odict_keys(['auc'])

([0.9375, 0.9375, 0.998046875, 0.998046875, 0.998046875],
 [0.8125, 0.8125, 0.9140625, 0.9140625, 0.9140625])
```

このように辞書の中にリストとして含まれているので、キーを指定してこのリストを取得します。


そして、学習曲線を描きます。

```python:In[10]
plt.plot(train_metric, label='train auc')
plt.plot(eval_metric, label='eval auc')
plt.grid()
plt.legend()
plt.ylim(0, 1.1)

plt.xlabel('rounds')
plt.ylabel('auc')
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541751/f8abe62d-b52e-8048-29c8-21b14d98c452.png)

# 5. 重要な特徴量を調べる

lightgbm は分類の際の重要度を lightgbm.plot_importance を使って可視化することができます。

lightgbm.plot_importance のドキュメンテーションは[こちら](https://lightgbm.readthedocs.io/en/latest/pythonapi/lightgbm.plot_importance.html)です。

```python:In[11]
lgb.plot_importance(gbm, figsize=(12, 6), max_num_features=4)
plt.show();
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541751/d411fc77-09cb-940f-810d-53169c502651.png)


# 6. テストデータを推定する・ROC曲線をプロットする

```python:In[12]
from sklearn import metrics

y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration)

fpr, tpr, thresholds = metrics.roc_curve(y_test, y_pred, drop_intermediate=False, )
auc = metrics.auc(fpr, tpr)
print('auc:', auc)
```

```:Out[12]
auc: 1.0
```

roc_curve のドキュメンテーションは[こちら](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.roc_curve.html)です。

またROC曲線について調べていた際に、以下の記事が分かりやすくまとまっていたのでリンクしておきます。

- [scikit-learnでROC曲線とそのAUCを算出](https://note.nkmk.me/python-sklearn-roc-curve-auc-score/)

```python:In[13]
plt.plot(fpr, tpr, marker='o')
plt.xlabel('FPR: False positive rate')
plt.ylabel('TPR: True positive rate')
plt.grid()
plt.plot();
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541751/02c658b7-6dc2-4f64-f35e-4c08f4218125.png)

今回は比較的簡単なものだったので、極端なROC曲線になりました。



最後に、ある閾値を設定し、その閾値を基準に回答ラベルを振り分けます。

```python:In[13]
y_pred = (y_pred >= 0.5).astype(int)
y_pred
```

```:Out[14]
array([1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0])
```


# 7. 混同行列のヒートマップを描く

最後に、

- [seaborn の heatmap で美しく可視化](https://qiita.com/kibinag0/items/47fbb89b0922251a20bb)

を参考にして、混同行列のヒートマップも描いてみました。


```python:In[15]
from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt

cm = confusion_matrix(y_test.values, y_pred)

sns.heatmap(cm, annot=True, annot_kws={'size': 20}, cmap= 'Reds');
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541751/7dcc48b4-4ecd-3917-de32-2a54209ae553.png)