LightGBM ~ feature_fraction と feature_fraction_bynode どっちが正義? ~
背景
もはや古典になりつつある勾配ブースティング(既に古典...?)ですが、その汎用性の高さから未だに Kaggle などのコンペティションでも現役と聞きます。
テーブルデータ用ベンチマーク
2025年11月に TabPFN と呼ばれるモデルの最新バージョン TabPFN-2.5 が出ました。モデルの解説については、別の機会で試みたいのですが、今回の記事の動機は以下の図です。

こちらは TabArena (-lite) と呼ばれるデータセット群で評価されたEloレーティングのグラフです。様々なモデルで評価されており、非常に詳細な検証内容が論文には記載されています。
今回気になったのは、NNでテーブルデータって本当に強いの? LightGBM / XGBoost / CatBoost って本当はどれが良いの? という事です。いや、論文に書いてるよ、ってのはそうなのですが、なんか色々気になって自分で試してみたくなりました。
という訳で、LightGBM / XGBoost / CatBoost って本当はどれが良いの? という検証を行う前段階として、今回の記事「feature_fraction と feature_fraction_bynode どっちが正義?」を書く事にしました。
CatBoost に feature_fraction は無い
Kaggle や X などでも度々話題になりますが、実は CatBoost の性能が良いというのは前々から知っていました。私はほとんど LightGBM しか使ってこなかったのですが、今回の論文の図を見て CatBoost が気になりパラメータを眺めていたところ、feature_fraction にあたる、決定木を作る際の木単位の特徴量ランダム選択比率、のパラメータが無い事に気づきました。CatBoost は代わり?に RSM という、木の階層レベル単位の特徴量ランダム選択比率、のパラメータを持ちます。
私は何故か、ずっと feature_fraction が重要と思っていたので、CatBoostの性能の良さって実はノード単位(or 階層レベル単位)の特徴量ランダムサンプルと関係があるのでは? と気になりました。
※しかし LightGBM には rsm 相当のパラメータがありません。なので ノード単位のパラメータである feature_fraction_bynode を比較します。
それが今回の調査の動機で、feature_fraction と feature_fraction_bynode を比較しようと思い至りました。
注意事項
今回は主にパラメータチューニングについての記事になります。
チューニングするハイパーパラメータの数は、少ないに越した事はありません。が、NNに比べると、勾配ブースティングの各パッケージは、非常に多い数のハイパーパラメータが搭載されています。
勿論全てをチューニングする時間があれば良いですが、一部のパラメータに絞ってチューニングするのが現実的です。さらに、テーブルデータは画像や言語のドメインと異なり、そのデータセットによって最適なパラメータがそれぞれ異なる、と認識しています。
機械学習というかデータ分析全般に言える事ですが、その検証や評価の方針は、ある種の信仰が伴います。え、信仰? となるかもしれませんが、例えば私は分類においては logloss 信仰です(昔は ROC-AUC を信仰していました)。 ある人は F値 信仰かもしれません。そしてパラメータチューニングにも、私の信仰があります。
要は何が言いたいかというと、データ分析にはその選択肢が多すぎて、何か方針を固定(つまり信仰)しないとキリが無いのです。こっちの指標で試すと? 他のデータセットでは違う結果にならないか? こちらとあちらの適用順番を逆にすると? などオプションが無限です。
なので以下の検証方法や評価で「何故そうしているのか」について、特別な理由が無い事の方が多いです。もしそういった疑問が浮かべば、それはこの人の信仰なんだろうな、と寛大な気持ちで読んでいただけると幸いです。
目的
前置きが長くなりましたが、本記事の目的は
です。ちなみにこの記事執筆時は、どちらか一方のチューニングが、他方を包括していると思っているため、両方を合わせてチューニングしませんでした。しかし、結果が良く分からなかったので両方チューニングするバージョンも比較に加えました。さらに事前予想に関しては、feature_fraction_bynode だけチューニングすればいいと思っていました。
検証方法
私は多クラス分類のみに関心があります。経験則として、回帰タスクが役に立ったケースがほとんどないからです。なので今回は多クラス分類に絞って検証を行います。
どちらをパラメータチューニングすべきか、について
比較方法
まず、共通にチューニングすべきハイパーパラメータ群(common parameter) があります(後述)。その上で
- A:
common parameter+feature_fractionをチューニングし best model で評価 - B:
common parameter+feature_fraction_bynodeをチューニングし best model で評価 - C:
common parameter+feature_fraction&feature_fraction_bynodeをチューニングし best model で評価
A のチューニングでは feature_fraction_bynode=1 で固定し、B では逆に feature_fraction=1 で固定します。
この評価値を以下のデータセットに対して算出し、A/B どちらが優れているかを評価します。
データセット
なるべくデータセットの種類は多い方が良いと思い、以下を用意しました。全て openml で提供されているデータセットです。
| name | n_data | n_classes | n_features |
|---|---|---|---|
| SDSS17 | 78053 | 3 | 11 |
| covertype | 581012 | 7 | 54 |
| gas-drift | 13910 | 6 | 128 |
| helena | 65196 | 100 | 27 |
| hiva_agnostic | 3845 | 3 | 1617 |
| nursery | 12958 | 4 | 8 |
| shuttle | 58000 | 7 | 9 |
| splice | 3190 | 3 | 60 |
| students_dropout_and_academic_success | 4424 | 3 | 36 |
| tamilnadu-electricity | 45781 | 20 | 2 |
| walking-activity | 149332 | 22 | 4 |
※nursery について、一部ごく少数のサンプルがあったため、全データ数の 0.01% 以下もしくはサンプル数3以下のクラスは除外しています
評価手順
まず、各データセットでパラメータチューニングを行います(後述)。そして得られた best-parameter を用いて、モデルを訓練しデータセットを評価します。
データセットの評価値は、以下の手順により作成します。
バリデーションやテストなどの分割方法は TabArena を参考にしました。TabArena では、Outer loop -> Inner loop の方法で分割し評価しています。下の絵では、Outer loop で 3-fold、inner loop で 4-fold CV を行い、訓練された 4 model をアンサンブルして test を評価し、それを outer loop の fold 回数分繰り返して評価します。

便宜上、outer loop -> inner loop の1セット自体の繰り返し回数(テストデータセットのランダムネスの軽減目的)を nloop, outer loop の fold 回数を nfold, inner loop の n-fold CV を ncv と呼びます。
今回は nloop=2, nfold=3, ncv=5 で評価します。分割は全て from sklearn.model_selection import StratifiedKFold を用います。
評価指標
from sklearn.metrics import log_loss を使用します。normalize=True
logloss を使って、以下の2つの A/B 比較用評価値を用意します。
- 標準化
(logloss - mean) / stdで標準化します - 比率
log(A/B),log(A/C),log(B/C)の値を計算します.logとしているのはA/BとB/Aを値の大きさとして同等と扱いたいからです
標準化に関して
mean は A,B,C のそれぞれで nfold x nloop の数の評価値を得られます。データセット毎に、その全ての値の平均を計算します。
次に std です。mean と同様それらの値で std を計算しても良かったのですが、A,B,C の良し悪しで大きく値が離れる事を考慮し、別の方法で測定しました。何のモデルで測っても良かったのですが、今回は extra_trees=True として LightGBM で測定しました。これを nloop=5, nfold=3, ncv=5 で回して、評価値を各データセットで 5 x 3 = 15 個得ます。この値の std を採用します。
パラメータチューニング
パラメータチューニングには optuna を用います。
common parameter は以下です。
- min_sum_hessian_in_leaf
あるノードにおいて、そのノードにあるサンプルが持つ必要な hessian の合計値. その合計値以下になるように分割はできない - lambda_l2
Leaf に付く値の l2 正則化 - bagging_fraction
各決定木における sample のランダムサンプリング比率.bagging_freq=1とする事に注意
これ以外は固定のパラメータとします。何故それを固定するかについての気持ちを解説してみます。
- iteration
early stopping で学習を止めるため iteration は適当に大きい数字で良いです - learning_rate
基本的に小さくすれば精度が上がり木の数が増え(訓練時間が伸び)ます。なので、訓練時間を許す範囲内の値で固定すれば良いです。このデータセットにはこの learning rate が良い! みたいなものは無い認識です( NNは除きます!) - num_leaves と max_depth
LightGBM はそのコンセプトとして Leaf-wise-growth なので、max_depth=-1で固定します。num_leavesはメモリの許す限り多くすれば良いです。ノードの分割ストップはmin_sum_hessian_in_leafで決めます - min_child_samples
こちらも同様固定で良いです。min_sum_hessian_in_leafにノード分割を委ねます - min_split_gain
min_sum_hessian_in_leafの loss の値版といったパラメータですが、経験的にmin_sum_hessian_in_leafの方が重要度が高いケースが多いので、min_split_gain=0で固定します - lambda_l1
CatBoost に実はこのパラメータが無いので、思い切ってlambda_l2に委ねてこちらは固定します。個人的にも、L1は木の数に対する正則化なので、0固定で良いのではと思います - max_bin
histgrum を作る際の bin の数です。こちらも固定で良いです。
以下の値に設定します。
iteration=1000
learning_rate=0.1
num_leaves=256 # depth = 8 相当
max_depth=-1
min_child_samples=5
min_split_gain=0
lambda_l1=0
max_bin=128
次に optuna で探索するcommon parameterですが、以下の範囲で探索します。
trial.suggest_float("min_sum_hessian_in_leaf", 1e-4, 1e3, log=True)
trial.suggest_float("lambda_l2", 1e-4, 1e3, log=True)
trial.suggest_float("bagging_fraction", 0.1, 1.0, log=False)
これに以下の A or B のどちらかを加えて同時に探索させます。探索回数は各データセットで50回です。
trial.suggest_float("feature_fraction", 0.1, 1.0, log=False) # A
or
trial.suggest_float("feature_fraction_bynode", 0.1, 1.0, log=False) # B
結果
ひとまずデータセット全体の平均を出します。分かりずらいですが、標準化は値が小さい方が良く、log比では log(A/B) の値が正のとき B の方が評価が良いという事です。
標準化 bytree -0.298569
標準化 bynode -0.804466
標準化 bytree & bynode 1.103035
log比 bynode / bytree 0.038951
log比 bynode / (bynode & bytree) 0.047077
log比 bytree / (bynode & bytree) 0.008125
つまり、標準化基準では bynode のみが最もよく、log比基準では bynode & bytree が最も良いです。細かい数値とプロットを以下に載せます。


| dataset | 平均_A | 平均_B | 平均_C | ABC平均 | extra_tree評価値分散 | 標準化_A | 標準化_B | 標準化_C | log(B/A) | log(B/C) | log(A/C) |
|---|---|---|---|---|---|---|---|---|---|---|---|
| SDSS17 | 0.0740802 | 0.0744852 | 0.0746289 | 0.0743981 | 0.00237945 | -0.133603 | 0.0365945 | 0.0970089 | 0.00545186 | -0.0019281 | -0.00737996 |
| covertype | 0.0840811 | 0.0817237 | 0.0839117 | 0.0832388 | 0.00503774 | 0.167192 | -0.300758 | 0.133566 | -0.0284379 | -0.0264211 | 0.00201673 |
| gas-drift | 0.0291754 | 0.0291252 | 0.0276443 | 0.0286483 | 0.00476336 | 0.11065 | 0.100131 | -0.210781 | -0.00171901 | 0.0521871 | 0.0539061 |
| helena | 2.63883 | 2.62758 | 2.63218 | 2.63286 | 0.0150716 | 0.395712 | -0.3503 | -0.0454124 | -0.00426993 | -0.00174728 | 0.00252265 |
| hiva_agnostic | 0.18721 | 0.186659 | 0.18941 | 0.18776 | 0.00521162 | -0.105413 | -0.211297 | 0.316711 | -0.00295198 | -0.0146347 | -0.0116827 |
| nursery | 0.00133429 | 0.00174878 | 0.0011652 | 0.00141609 | 0.00715337 | -0.0114349 | 0.0465081 | -0.0350731 | 0.270518 | 0.406026 | 0.135509 |
| shuttle | 0.000402254 | 0.000404696 | 0.000405506 | 0.000404152 | 0.000424648 | -0.00446887 | 0.00128098 | 0.00318789 | 0.0060516 | -0.00199893 | -0.00805053 |
| splice | 0.102394 | 0.122131 | 0.108331 | 0.110952 | 0.0156164 | -0.54799 | 0.715838 | -0.167847 | 0.176261 | 0.119903 | -0.0563581 |
| students_dropout_and_academic_success | 0.568858 | 0.570679 | 0.570596 | 0.570044 | 0.0209764 | -0.0565532 | 0.0302459 | 0.0263072 | 0.00319558 | 0.000144786 | -0.00305079 |
| tamilnadu-electricity | 2.99454 | 2.994 | 2.9957 | 2.99474 | 8.66048e-05 | -2.39791 | -8.64491 | 11.0428 | -0.000180685 | -0.000569328 | -0.000388643 |
| walking-activity | 0.998832 | 1.00338 | 1.01663 | 1.00628 | 0.0106384 | -0.70044 | -0.272459 | 0.972898 | 0.00454802 | -0.0131176 | -0.0176656 |
結論と考察
bynode & bytree では nursery がその log比で大きく目立ち、bynode では tamilnadu-electricity がその標準化指標で大きく影響しています。
今回、各データセットに対する重要度などは考慮しておらず、全て同じ比重で評価しています。というのも、少なくとも標準化ではそのデータセットの分割などに関わる分散が考慮されているので、大雑把には同じ比重で良いはずです。
なので、正直、良く分からない というのが結論になります。少なくとも現状では、割とケースバイケースという事で、bynode & bytree でチューニングしようかなと思っています。
Discussion