Kaggle(29) - Optunaでハイパーパラメータ調整 - LightGBM編6

学習パラメータの調整をする場合、グリッドサーチ機能を使うとパラメータの候補をリストで指定し、そのリストの組み合わせより最も成績のよい組み合わせを調べることができました。(前回記事参照)

Optunaを使うと、パラメータの候補リストではなく、パラメータの範囲を指定して最適な組み合わせを調べることができるのでより便利です。

データの読み込み

タイタニックのデータセットを読み込み、データの前処理を行って、正解ラベルとそれ以外にデータを分割します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pandas as pd

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')

# データ前処理
def preprocessing(df):
# 不要な列の削除
df.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)

# カテゴリ変数の変換
df['Sex'] = df['Sex'].astype('category')
df['Embarked'] = df['Embarked'].astype('category')

return df

x_titanic = preprocessing(df_train.drop(['Survived'], axis=1))
y_titanic = df_train['Survived']

Optunaでハイパーパラメータ調整

Optunaで学習パラメータを最適化する場合には、目的関数の定義の定義を行い、その中で学習パラメータの種類と範囲を指定します。

返値としては、成績の良いものほど数値が低くなるようにします。今回は正解率が高いほど、低い数値が返るように定義しています。(13行目)

ハイパーパラメータの自動最適化を行うためにはoptunaのcreate_studyメソッドで最適化のセッションを作り、そのセッションのoptimizeメソッドに目的関数(objective)と試行回数(n_trials)を指定します。(20行目)

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import optuna
import lightgbm as lgb
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

# 目的関数の定義(最小値問題として定式化)
def objective(trial):
learning_rate = trial.suggest_loguniform('learning_rate', 1e-5, 1e-1)
num_leaves = trial.suggest_int('num_leaves', 5, 30)

gbm = lgb.LGBMClassifier(objective='binary', learning_rate=learning_rate, num_leaves=num_leaves)
gbm.fit(train_x, train_y)
return 1.0 - accuracy_score(valid_y, gbm.predict(valid_x))

# 訓練データをtrainとvalidに分割
train_x, valid_x, train_y, valid_y = train_test_split(x_titanic, y_titanic, test_size=0.33, random_state=0)

# ハイパーパラメータの自動最適化
study = optuna.create_study()
study.optimize(objective, n_trials = 100)

print('求めたハイパーパラメータ', study.best_params)
print('正答率', 1.0 - study.best_value)

[出力]

今回は、学習率(learning_rate)と木にある分岐の個数(num_leaves)の最適化を行い、各パラメータが0.08623、8という結果となりました。

正答率は84.06%と、まずまずの結果になっていると思います。

Kaggleに提出

最適化したハイパーパラメータを指定して、LightGBMのインスタンスを作成し、学習を行います。

その後、検証データを読み込み、推論・提出用のCSVの出力を行い、Kaggleに提出します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
gbm = lgb.LGBMClassifier(objective='binary', learning_rate=0.08623, num_leaves=8)

# 学習
gbm.fit(x_titanic, y_titanic)

# 検証データの読み込み
df_test = pd.read_csv('/kaggle/input/titanic/test.csv')
df_test = preprocessing(df_test)

pre = gbm.predict(df_test)

result = pd.DataFrame(df_test['PassengerId'])
result['Survived'] = pre
result.to_csv('result0307c.csv', index=False)

[提出結果]

正解率77.99%となりました。

これまでの結果とほぼ同じ正解率となっていて、パラメータの調整だけでは成績向上は難しいのかもしれません。

もしくは、調整するパラメータの選択がよくないのか、範囲の指定がイマイチなのか・・・壁にぶつかっているような気がします。

Kaggle(28) - パラメータをチューニングする - LightGBM編5

これまではLightGBMの初期パラメータで学習を行ってきました。

LightGBMは各種パラメータを設定することができますので、今回はそのパラメータを調整して学習・推測を行ってみます。

データの読み込み

タイタニックのデータセットを読み込み、データの前処理を行って、正解ラベルとそれ以外にデータを分割します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
import pandas as pd

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')

# データ前処理
def preprocessing(df):
# 不要な列の削除
df.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)

# カテゴリ変数の型変換
df['Sex'] = df['Sex'].astype('category')
df['Embarked'] = df['Embarked'].astype('category')

return df

x_titanic = preprocessing(df_train.drop(['Survived'], axis=1))
y_titanic = df_train['Survived']

パラメータの表示

まず、LightGBMの各種パラメータを表示します。

[ソース]

1
2
3
4
5
6
import lightgbm as lgb

# LightGBMの分類器をインスタンス化
gbm = lgb.LGBMClassifier(objective='binary')

gbm.get_params()

[出力]

グリッドサーチ

いろいろなパラメータが表示されましたが、今回はこのうち次の3パラメータに対してチューニングを行います。

  • num_leaves
    木にある分岐の個数です。大きくすると精度は上がるが過学習しやすくなります。
  • reg_alpha
    L1正則化に相当するものです。デフォルトでは 0.1 ぐらいを使うことが多いです。
    L1正則化にあたるのであまり大きな値を使用するとかなり重要な変数以外を無視するようなモデルになってしまうため、精度を求めている場合あまり大きくしないほうが良いでしょう。
  • reg_lambda
    L2 正則化に相当するものです。デフォルトでは L1 と同様に 0.1 ぐらいを指定します。
    L1と違い大きな値を設定してもその変数が使われないということはありませんので、特徴量の数が多い時や、徐々に木を大きくしたいすぐにオーバーフィットするような問題に対して大きく取ることが多いです。

params変数に候補となるパラメータのリストを指定し、グリッドサーチを使って最適パラメータを探索します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn.model_selection import GridSearchCV

# 試行するパラメータを羅列
params = {
'num_leaves': [3, 4, 5, 6, 7, 8, 9, 10],
'reg_alpha': [0, 1, 2, 3, 4, 5,10, 100],
'reg_lambda': [10, 15, 18, 20, 21, 22, 23, 25, 27, 29]
}

grid_search = GridSearchCV(gbm, param_grid=params, cv=3)
grid_search.fit(x_titanic, y_titanic)

print(grid_search.best_score_)
print(grid_search.best_params_)

[出力]

チューニングしたパラメータの性能確認

グリッドサーチでのパラメータ探索の結果、num_leaves=5、reg_alpha=0、reg_lambda=22が交差検証でのスコアがベストであることが分かりました。

デフォルトのパラメータの場合と、このパラメータを使った場合の正解率の違いを確認してみます。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn import model_selection

print('== デフォルト・パラメータの場合 ==')
gbm = lgb.LGBMClassifier(objective='binary')

score = model_selection.cross_val_score(gbm, x_titanic, y_titanic, cv=3) # cv=3は3分割の意
print('各正解率', score)
print('正解率', score.mean())

print('\n== パラメータをチューニングした場合 ==')
gbm = lgb.LGBMClassifier(objective='binary', num_leaves=5, reg_alpha=0, reg_lambda=22)
score = model_selection.cross_val_score(gbm, x_titanic, y_titanic, cv=3) # cv=3は3分割の意
print('各正解率', score)
print('正解率', score.mean())

[出力]

デフォルト・パラメータの正解率が74.41%、チューニングしたパラメータでの正解率が82.49%となっており、だいぶ正解率が向上しています。

学習・推論

チューニングしたパラメータを使って、学習・推論を行います。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

# 訓練データをtrainとvalidに分割
train_x, valid_x, train_y, valid_y = train_test_split(x_titanic, y_titanic, test_size=0.33, random_state=0)

# 学習
gbm.fit(train_x, train_y)

# 推論
pre = gbm.predict(valid_x, num_iteration=gbm.best_iteration_)
print('score', round(accuracy_score(valid_y, pre) * 100, 2))

[出力]

Kaggleに提出

検証データを読み込み、推論・提出用のCSVの出力を行い、Kaggleに提出してみます。

[ソース]

1
2
3
4
5
6
7
8
9
# 検証データの読み込み
df_test = pd.read_csv('/kaggle/input/titanic/test.csv')
df_test = preprocessing(df_test)

pre = gbm.predict(df_test)

result = pd.DataFrame(df_test['PassengerId'])
result['Survived'] = pre
result.to_csv('result0306.csv', index=False)

[提出結果]

正解率77.51%と、チューニング前よりも成績が悪くなってしまいました。

reg_lambdaの指定リストがよくなかったような気がします。

グリッドサーチ機能は便利なのですが、最適なパラメータ候補を指定するのがなかなかに難しいです。

ハイパーパラメータを自動最適化するOptunaというツールがあるらしいので今度使ってみたいと思います。

Kaggle(27) - Sickit-learn interfaceを使う - LightGBM編4

Sickit-learn wapper interfaceというラッパーを使うと、LightGBMをSickit-learnを介して使うことができます。

端的にいうとSickit-learnでお馴染みの、fitで学習しpredictで推論することができるようになります。

データの読み込み

タイタニックのデータセットを読み込み、データの前処理を行って、正解ラベルとそれ以外にデータを分割します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
import pandas as pd

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')

# データ前処理
def preprocessing(df):
# 不要な列の削除
df.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)

# カテゴリ変数の型変換
df['Sex'] = df['Sex'].astype('category')
df['Embarked'] = df['Embarked'].astype('category')

return df

x_titanic = preprocessing(df_train.drop(['Survived'], axis=1))
y_titanic = df_train['Survived']

Sickit-learn interfaceを使う

LightGBMをSickit-learn interfaceでラップし(12行目)、学習・推論を行います。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import lightgbm as lgb
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

# 訓練データをtrainとvalidに分割
train_x, valid_x, train_y, valid_y = train_test_split(x_titanic, y_titanic, test_size=0.33, random_state=0)

# パラメータを定義
lgbm_params = {'objective': 'binary'}

# LightGBMの分類器をインスタンス化
gbm = lgb.LGBMClassifier(objective='binary')

# 学習
gbm.fit(train_x, train_y, eval_set=[(train_x, train_y), (valid_x, valid_y)],
early_stopping_rounds=20, # 20回連続でlossが下がらなかったら終了
verbose=10) # 10roundごとにlossを表示
# gbm.fit(train_x, train_y) # 単純学習

# 推論
pre = gbm.predict(valid_x, num_iteration=gbm.best_iteration_)
print('score', round(accuracy_score(valid_y, pre) * 100, 2))

[出力]

正解率は83.05%となりました。なかなかの結果です。

検証データを読み込み、推論・提出用のCSVの出力を行い、Kaggleに提出してみます。

[ソース]

1
2
3
4
5
6
7
8
9
# 検証データの読み込み
df_test = pd.read_csv('/kaggle/input/titanic/test.csv')
df_test = preprocessing(df_test)

pre = gbm.predict(df_test)

result = pd.DataFrame(df_test['PassengerId'])
result['Survived'] = pre
result.to_csv('result0305.csv', index=False)

[提出結果]

正解率78.82%と、もう少しで8割に到達できそうです。

便利な交差分割検証関数

LightGBMをSickit-learnと同じように扱えるようになると、cross_val_scoreというとても便利な関数を使うことができるようになります。

この関数を使うと、たった1行で分割交差検証ができてしまいます。

[ソース]

1
2
3
4
from sklearn import model_selection
score = model_selection.cross_val_score(gbm, x_titanic, y_titanic, cv=3) # cv=3は3分割の意
print('各正解率', score)
print('正解率', score.mean())

[出力]

便利すぎます。

このような便利関数があるのでSickit-learnは広く普及しているんだと思います。

Kaggle(26) - 重要度の表示 - LightGBM編3

LightGBMでは、学習済みモデルのfeature_importanceメソッドを使うと特徴の重要度を表示することができます。

重要度の表示

前回記事で学習したモデルについての重要度を表示します。

[ソース]

1
gbm.feature_importance()

[出力]

feature_importanceメソッドは、デフォルトの引数としてimportance_type=’split’が指定されており、重要度としては「特徴が使用された回数」が表示されます。

引数にimportance_type=’gain’を指定した場合は、「その特徴によりどれくらいトレーニングデータの損失を小さくできたか」を表示します。

[ソース]

1
gbm.feature_importance(importance_type='gain')

[出力]


2種類の重要度を比較しやすいように、1つのデータフレームにして表示します。

トレーニングデータの損失(gain)の方は、intに丸めてみました。

[ソース]

1
2
3
pd.DataFrame({'特徴':x_titanic.columns,
'重要度(split)':gbm.feature_importance(),
'重要度(gain)':gbm.feature_importance(importance_type='gain').astype(int)})

[出力]

重要度(split)では年齢(Age)の比重が大きく、重要度(gain)では性別(Sex)が最重要であることが把握できます。

Kaggle(25) - タイタニック(Titanic)コンペ - LightGBM編2

これまでデータの前処理を行うときに、カテゴリー変数についてはOne-Hotエンコーディングを行っていました。

しかし【LightGBMのドキュメント】によると、カテゴリー変数はOne-Hotエンコーディングするよりも0から始まる連続した整数に変換するほうが優れたパフォーマンスを発揮するとのことでした。

今回はOne-Hotエンコーディングの代わりに、カテゴリー変数を整数に変換してタイタニック・コンペに提出してみます。

データ読み込みと前処理

まずはいつも通りタイタニックのデータセットを読み込みます。

[ソース]

1
2
3
4
import numpy as np
import pandas as pd

df_train = pd.read_csv('/kaggle/input/titanic/train.csv')

今回のポイントであるデータ前処理ですが、LightGBMで学習・推測するためには下記の5変数を変換する必要があります。
  • Name
  • Sex
  • Ticket
  • Cabin
  • Embarked

このうち生存率に関係のなさそうなNameとTicketとCabinは単純に削除します。

そしてSexとEmbarkedを整数に変換したいのですが、一番カンタンそうな方法はデータ型をcategory型に変えることでした。

こうすることによって、自動でカテゴリーを変換した整数として扱ってくれるので、自分で0や1などの整数に置き換える必要がなくとてもラクチンでした。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# データ前処理
def preprocessing(df):
# 不要な列の削除
df.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)

# カテゴリ変数の変換
# df = pd.get_dummies(df, columns=['Sex', 'Embarked'])
df['Sex'] = df['Sex'].astype('category')
df['Embarked'] = df['Embarked'].astype('category')

return df

x_titanic = preprocessing(df_train.drop(['Survived'], axis=1))
y_titanic = df_train['Survived']

データの前処理が終わりましたら、以前実行した時と同じようにLightGBMを使って学習を行います。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import lightgbm as lgb
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold

# 3分割交差検証を指定しインスタンス化する
kf = KFold(n_splits=3, shuffle=True)

# スコアとモデルを格納するリスト
score_list = []
models = []

for fold_, (train_index, valid_index) in enumerate(kf.split(x_titanic, y_titanic)):
print(f'fold{fold_ + 1}start')
train_x = x_titanic.iloc[train_index]
valid_x = x_titanic.iloc[valid_index]
train_y = y_titanic.iloc[train_index]
valid_y = y_titanic.iloc[valid_index]

# lab.Datasetを使って、trainとvalidを作っておく
lgb_train = lgb.Dataset(train_x, train_y)
lgb_valid = lgb.Dataset(valid_x, valid_y)

# パラメータを定義
lgbm_params = {'objective': 'binary'}

# lgb.trainで学習
evals_result = {}
gbm = lgb.train(params=lgbm_params,
train_set=lgb_train,
valid_sets=[lgb_train, lgb_valid],
early_stopping_rounds=20,
evals_result=evals_result,
verbose_eval=-1) # 学習の状態を表示しない
# valid_xについて推論
oof = (gbm.predict(valid_x) > 0.5).astype(int)
score_list.append(round(accuracy_score(valid_y, oof) * 100, 2))
# 学習が終わったモデルをリストに入れておく
models.append(gbm)
print(f'fold_{fold_ + 1} end\n')

print(score_list, '平均score', round(np.mean(score_list), 2))

[出力]

平均正解率は81.6%とそこそこの結果というところでしょうか。

最後に、学習したモデルを使って、生存の推測を行い提出用のCSVファイルを出力します。

検証データに対してもデータの前処理(3行目)を行うことをお忘れなく。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 検証データの読み込み
df_test = pd.read_csv('/kaggle/input/titanic/test.csv')
df_test = preprocessing(df_test)

# テストデータの予測を格納する行列を作成
test_pred = np.zeros((len(df_test), 3))

for fold, gbm in enumerate(models):
test_pred[:, fold] = gbm.predict(df_test)

result = pd.DataFrame(df_test['PassengerId'])
# 平均が0.5より大きい場合,1(生存)とする
result['Survived'] = (np.mean(test_pred, axis=1) > 0.5).astype(int)
result
result.to_csv('result.csv', index=False)

予測結果を提出します。

[提出結果]

提出結果は77.99%という正解率になりました。

カテゴリ変数をOne-Hotエンコーディングしときの結果は75.59%でしたので2.4%ほど正解率が上がりました。

Kaggle(21) - 分割交差検証での学習・推論(LightGBM)

前回試したホールドアウト法は、簡単にモデルの性能評価ができるので使いやすいのですがvalidセットを学習に使えていないという欠点があります。

精度が必要な場合には、この問題を解決したk分割交差検証を使うのが一般的です。

データの読み込みと前処理

前回と同様に、タイタニックのデータセットを読み込み、前処理(不要列の削除・欠損値処理・カテゴリ変数の変換)を行っておきます。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import pandas as pd
import seaborn as sns
titanic = sns.load_dataset('titanic')

# 不要な列の削除
titanic.drop(['class', 'who', 'adult_male', 'deck', 'embark_town', 'alive', 'alone'], axis=1, inplace=True)

# 欠損値処理
#titanic.isnull().sum()
titanic['age'] = titanic['age'].fillna(titanic['age'].median())
titanic['embarked'] = titanic['embarked'].fillna('S')

# カテゴリ変数の変換
titanic = pd.get_dummies(titanic, columns=['sex', 'embarked'])

x_titanic = titanic.drop(['survived'], axis=1)
y_titanic = titanic['survived']

x_titanic

[出力結果]

分割交差検証

k分割交差検証のk=3の場合の3分割交差検証を行います。

fold1、fold2、fold3の合計3回の学習・検証を行います。

3分割したデータはtrainセット(訓練用)に2回、validセット(検証用)に1回ずつ使われます。

学習を3回行うので学習済みモデルも3つできます。3つの予測結果ができますので、最後にそれらを1つにまとめています。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import lightgbm as lgb
import numpy as np
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold

# 3分割交差検証を指定しインスタンス化する
kf = KFold(n_splits=3, shuffle=True)

# スコアとモデルを格納するリスト
score_list = []
models = []

for fold_, (train_index, valid_index) in enumerate(kf.split(x_titanic, y_titanic)):
print(f'fold{fold_ + 1}start')
train_x = x_titanic.iloc[train_index]
valid_x = x_titanic.iloc[valid_index]
trains_y = y_titanic.iloc[train_index]
valid_y = y_titanic.iloc[valid_index]

# lab.Datasetを使って、trainとvalidを作っておく
lgb_train = lgb.Dataset(train_x, train_y)
lgb_valid = lgb.Dataset(valid_x, valid_y)

# パラメータを定義
lgbm_params = {'objective': 'binary'}

# lgb.trainで学習
evals_result = {}
gbm = lgb.train(params=lgbm_params,
train_set=lgb_train,
valid_sets=[lgb_train, lgb_valid],
early_stopping_rounds=20,
evals_result=evals_result,
verbose_eval=-1) # 学習の状態を表示しない
# valid_xについて推論
oof = (gbm.predict(valid_x) > 0.5).astype(int)
score_list.append(round(accuracy_score(valid_y, oof) * 100, 2))
# 学習が終わったモデルをリストに入れておく
models.append(gbm)
print(f'fold_{fold_ + 1} end\n')

print(score_list, '平均score', round(np.mean(score_list), 2))

[出力結果]

正解率は90.35%となりました。

なかなかの好成績になったのではないでしょうか。

(実行環境としてGoogleさんのColaboratoryを使用ています。)


Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×