LightGBMでPartial dependence実行時に出たNotFittedError対処メモ

# はじめに

Lightgbmで構築したモデルに対してscikit-learnの```plot_partial_dependence```を実行したときにNotFittedErrorが表示され、うまく実行できませんでした。
その対処についての備忘録です。

# 環境

環境は以下の通り。

```terminal
$sw_vers
ProductName:	Mac OS X
ProductVersion:	10.13.6
BuildVersion:	17G14042
```

Jupyterlab (Version 0.35.4) 上で作業していたので、python kernelのバージョンも記載しておきます。

```terminal
Python 3.7.3 (default, Mar 27 2019, 16:54:48) 
IPython 7.4.0 -- An enhanced Interactive Python. Type '?' for help.
```

# やったこと

## モデル構築

予測するためのデータとモデルを用意します。
データはscikit-learnで用意されているボストンデータセットを使用しました。

```python
import pandas as pd
import sklearn.datasets as skd

data = skd.load_boston()

df_X = pd.DataFrame(data.data, columns=data.feature_names)
df_y = pd.DataFrame(data.target, columns=['y'])
```

以下の通り、506行13列のデータで全列がnon-nullのfloat型なのでこのままモデルを作ります。

```python
df_X.info()
```

    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 506 entries, 0 to 505
    Data columns (total 13 columns):
    CRIM       506 non-null float64
    ZN         506 non-null float64
    INDUS      506 non-null float64
    CHAS       506 non-null float64
    NOX        506 non-null float64
    RM         506 non-null float64
    AGE        506 non-null float64
    DIS        506 non-null float64
    RAD        506 non-null float64
    TAX        506 non-null float64
    PTRATIO    506 non-null float64
    B          506 non-null float64
    LSTAT      506 non-null float64
    dtypes: float64(13)
    memory usage: 51.5 KB

LightGBMで、ハイパーパラメタはでほぼデフォルトのままでモデルを構築しておきます。

### Training APIの場合

Training APIで用意されている```train```の関数でモデル構築した場合は、[Booster](https://lightgbm.readthedocs.io/en/latest/pythonapi/lightgbm.Booster.html#lightgbm.Booster)が返されます。
結論からいうと、Boosterをそのままscikit-learnの```plot_partial_dependence```に渡すとエラーとなります。

どのようなエラーになるのか見ていきたいと思います。
まずはモデルを学習させます。

```python
import lightgbm as lgb
from sklearn.model_selection import train_test_split

df_X_train, df_X_test, df_y_train, df_y_test = train_test_split(df_X, df_y, test_size=0.2, random_state=4)

lgb_train = lgb.Dataset(df_X_train, df_y_train)
lgb_eval = lgb.Dataset(df_X_test, df_y_test)

# Booster

params = {
    'seed':4,
    'objective': 'regression',
    'metric':'rmse'}

lgbm_booster = lgb.train(params,
                lgb_train,
                valid_sets=lgb_eval,
                num_boost_round=200,
                early_stopping_rounds=20,
                verbose_eval=50)
```

    （省略）

    Training until validation scores don't improve for 20 rounds
    [50]	valid_0's rmse: 3.58803
    [100]	valid_0's rmse: 3.39545
    [150]	valid_0's rmse: 3.31867
    [200]	valid_0's rmse: 3.28222
    Did not meet early stopping. Best iteration is:
    [192]	valid_0's rmse: 3.27283

これで学習させたモデル```lgbm_booster```を```plot_partial_dependence```に渡します。

```python
from sklearn.inspection import plot_partial_dependence
plot_partial_dependence(lgbm_booster, df_X_train, ['CRIM', 'ZN']) 
```


    ---------------------------------------------------------------------------

    ValueError                                Traceback (most recent call last)

    <ipython-input-7-9a44e58d2f50> in <module>
          1 from sklearn.inspection import plot_partial_dependence
    ----> 2 plot_partial_dependence(lgbm, df_X_train, ['CRIM', 'ZN'])

    （中略）
    
    /anaconda3/lib/python3.7/site-packages/sklearn/inspection/_partial_dependence.py in partial_dependence(estimator, X, features, response_method, percentiles, grid_resolution, method)
        305     if not (is_classifier(estimator) or is_regressor(estimator)):
        306         raise ValueError(
    --> 307             "'estimator' must be a fitted regressor or classifier."
        308         )
        309 


    ValueError: 'estimator' must be a fitted regressor or classifier.

というわけでValueErrorが出ることが確認できました。

### Scikit-learn APIの場合

それでは[Scikit-learn API](https://lightgbm.readthedocs.io/en/latest/Python-API.html#scikit-learn-api)でモデルを学習させます。
回帰問題のため、LGBMRegressorを使います。

```python
lgbm_sk = lgb.LGBMRegressor(objective=params['objective'],
                           random_state=params['seed'],
                           metric=params['metric'])
lgbm_sk.fit(df_X_train, df_y_train)
```



    LGBMRegressor(boosting_type='gbdt', class_weight=None, colsample_bytree=1.0,
                  importance_type='split', learning_rate=0.1, max_depth=-1,
                  metric='rmse', min_child_samples=20, min_child_weight=0.001,
                  min_split_gain=0.0, n_estimators=100, n_jobs=-1, num_leaves=31,
                  objective='regression', random_state=4, reg_alpha=0.0,
                  reg_lambda=0.0, silent=True, subsample=1.0,
                  subsample_for_bin=200000, subsample_freq=0)


これで学習させたモデル```lgbm_sk```を```plot_partial_dependence```に渡します。

```python
plot_partial_dependence(lgbm_sk, df_X_train, ['CRIM', 'ZN']) 
```


    ---------------------------------------------------------------------------

    NotFittedError                            Traceback (most recent call last)

    <ipython-input-9-d0724528e406> in <module>
    ----> 1 plot_partial_dependence(lgbm_sk, df_X_train, ['CRIM', 'ZN'])

    （中略）

    /anaconda3/lib/python3.7/site-packages/sklearn/utils/validation.py in check_is_fitted(estimator, attributes, msg, all_or_any)
        965 
        966     if not attrs:
    --> 967         raise NotFittedError(msg % {'name': type(estimator).__name__})
        968 
        969 


    NotFittedError: This LGBMRegressor instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator.

このようにNotFittedErrorが発生しました。
ここで使用しているscikit-learnとlightgbmのバージョンはそれぞれ```0.22.2.post1```と```2.3.0```です。

```python
from sklearn import __version__ as sk_ver
print(lgb.__version__)
print(sk_ver)
```

    2.3.0
    0.22.2.post1

ここで発生したNotFittedErrorについて調べていると、[GithubのIssue](https://github.com/microsoft/LightGBM/issues/3014)でこのエラーについてレポートされていました。
どうもscikit-learnの```check_is_fitted```という関数を使ってモデルがフィット済みか確認しているのですが、```check_is_fitted```で確認するattributeがlightgbmの```fit```で設定されていないために起こっているようです。
バージョンアップすることで解決できそうなので、lightgbmをバージョンアップします。
2021年1月14日の段階で最新バージョンは```3.1.1```だったため、このバージョンを指定しました。

```terminal
conda install -c conda-forge lightgbm=3.1.1
```

バージョンを確認します。

```python
from sklearn import __version__ as sk_ver
print(lgb.__version__)
print(sk_ver)
```

    3.1.1
    0.23.1

改めてモデルを学習させます。

```python
lgbm_sk = lgb.LGBMRegressor(objective=params['objective'],
                           random_state=params['seed'],
                           metric=params['metric'])
lgbm_sk.fit(df_X_train, df_y_train)
```

    LGBMRegressor(metric='rmse', objective='regression', random_state=4)

```lgbm_sk```を```plot_partial_dependence```に渡します。

```python
plot_partial_dependence(lgbm_sk, df_X_train, ['CRIM', 'ZN']) 
```

今回はエラーなく実行でき、下図のような結果を得られました。

![output_8_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/73a0c317-d30c-9071-e7bb-c9d4f5dc7bb0.png)

ついでにscikit-learnの他に```pdpbox```を使ってpartial dependenceを出してみます。

```python
from pdpbox import pdp, get_dataset, info_plots
fig = plt.figure(figsize=(14,5))

pdp_goals = pdp.pdp_isolate(model=lgbm_sk, dataset=df_X_train, model_features=df_X_train.columns, feature='LSTAT')
# plot it
pdp.pdp_plot(pdp_goals,'LSTAT')
plt.show()
```
下図のような出力を得られました。scikit-learnよりも見た目はいいです。

![output_10_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/83145be8-e671-e1dd-7467-c5e0af6e13f6.png)

# まとめ

Lightgbmをscikit-learnの```plot_partial_dependence```に渡したときに発生したNotFittedErrorはLightgbmをバージョンアップすることで解決しました。