LightGBM のランク学習における正規化の効果
はじめに
前回の記事では LightGBM の lambdarank と rank_xendcg を比較しました。理論上は NDCG を直接最適化する xENDCG が有利なはずなのに、なぜ、lambdarank の方が一定して優位なのか?この疑問からパラメータを調べたところ、lambdarank_norm が目に留まりました。ソースコードを追ってみたところ、この正規化は xENDCG には適用されていないことに気づきました。
そこで、本記事では以下の 2 つの観点から正規化の効果を検証しました。
-
lambdarank_norm の効果: lambdarank における
lambdarank_norm=Truevslambdarank_norm=Falseの比較 - xENDCG への正規化適用: rank_xendcg vs custom_xendcg(xENDCG + 正規化)の比較
TL;DR(3 行まとめ)
- 正規化は学習の安定性と精度に大きく寄与: lambdarank_norm=True は False に比べて安定した高い性能
- xENDCG にも正規化は効果的なケースあり: custom_xendcg(xENDCG + lambdarank_norm 相当)は特にクエリ間のグループサイズ不均一時に効果的
- 候補数や条件によって最適な objective が異なる: 候補数が小〜中規模では lambdarank、大規模では xendcg 系が安定
本記事の構成
- 背景: lambdarank_norm とは何か、なぜ正規化が重要なのか
- 実験設計: 比較する objective と候補抽出方法
- 実験 1: 全ユーザー同一グループサイズでの比較
- 実験 2: ユーザーごとに異なるグループサイズでの比較
- 考察: なぜグループサイズ不均一で効果が大きいか
- 推奨 objective: 状況別の設定ガイド
背景: なぜ正規化が重要か
lambdarank_norm とは
lambdarank には lambdarank_norm パラメータがあります(デフォルト: True)。
LightGBM 公式ドキュメントでは下記のような説明がされています。
- set this to
trueto normalize the lambdas for different queries, and improve the performance for unbalanced data- set this to
falseto enforce the original lambdarank algorithm
正規化の仕組み
lambdarank_norm=True では、以下の正規化がクエリ(グループ)ごとに適用されます:
ここで
この正規化により
-
勾配スケールの統一: グループサイズに関わらず、勾配の大きさが揃う(分母の
が大きいほど norm_factor は小さくなる)\sum|\lambda| - 不均衡データへの対応: 小さなグループも大きなグループも同程度の影響力を持つ
-
学習の安定化:
による対数スケールで勾配の急激な変化を抑制\log_2
rank_xendcg には正規化がない
一方、rank_xendcg の実装にはこのような正規化処理が見当たりませんでした。これが lambdarank と rank_xendcg の性能差の一因と考えました。
そこで本記事では、xENDCG に lambdarank_norm と同等の正規化を追加した custom_xendcg を実装し、正規化の効果を検証します。
実験設計
比較する objective
以下の 4 つの objective を比較します:
| objective | 正規化 | 説明 |
|---|---|---|
lambdarank (norm=True) |
あり | lambdarank + lambdarank_norm=True(デフォルト) |
lambdarank (norm=False) |
なし | lambdarank + lambdarank_norm=False(オリジナル LambdaMART) |
rank_xendcg |
なし | xENDCG(LightGBM 組み込み) |
custom_xendcg |
あり | xENDCG + lambdarank_norm と同等の正規化 |
Note: custom_xendcg の実装では 1 次 Taylor 近似を採用しました。LightGBM の
rank_xendcgは 3 次 Taylor 近似を使用していますが、事前実験で 1 次近似と 3 次近似の精度差がほぼ無視できる(NDCG@10 で ±0.001 程度)ことを確認したため、計算がシンプルな 1 次近似を採用しました。
候補抽出の方法
正規化の効果は「グループサイズの不均一性」で顕著に現れると予想されます。そこで 2 種類の候補抽出方法を検証しました
| 方法 | 説明 |
|---|---|
| 全ユーザー同一(前回の記事と同じ) | 人気順 Top-N |
| ユーザーごとに異なる | 人気順 + ユーザーの好みジャンル |
「ユーザーごとに異なる」方法では、各ユーザーの評価履歴から好みジャンルを特定し、そのジャンルの人気アイテムを候補に追加します。これにより、ユーザーごとに候補数が変わるようにしました。
custom_xendcg の実装
rank_xendcg に lambdarank_norm 相当の正規化を追加したカスタム objective を実装しました。
import numpy as np
def xendcg_normalized_objective(
y_true: np.ndarray,
y_pred: np.ndarray,
group: np.ndarray,
) -> tuple[np.ndarray, np.ndarray]:
"""xENDCG + lambdarank_norm 相当の正規化."""
n_samples = len(y_true)
n_groups = len(group)
# グループの開始インデックス
group_starts = np.concatenate([[0], np.cumsum(group[:-1])])
group_indices = np.repeat(np.arange(n_groups), group)
# 乱数は一括生成
rng = np.random.default_rng(42)
gamma = rng.uniform(0, 1, n_samples)
# --- Softmax のベクトル化 ---
# グループごとの max を reduceat で計算
y_pred_max = np.maximum.reduceat(y_pred, group_starts)
y_pred_max_expanded = y_pred_max[group_indices]
# exp(x - max)
exp_pred = np.exp(y_pred - y_pred_max_expanded)
# グループごとの sum
sum_exp = np.add.reduceat(exp_pred, group_starts)
sum_exp_expanded = sum_exp[group_indices]
# Softmax
rho = exp_pred / sum_exp_expanded
# --- Phi ---
phi_values = np.power(2.0, y_true) - gamma
sum_phi = np.add.reduceat(phi_values, group_starts)
sum_phi = np.maximum(sum_phi, 1.0)
sum_phi_expanded = sum_phi[group_indices]
phi_normalized = phi_values / sum_phi_expanded
# --- Gradient & Hessian ---
grad = rho - phi_normalized
hess = np.maximum(rho * (1.0 - rho), 1e-6)
# --- lambdarank_norm 正規化(ここがポイント)---
sum_abs_grad = np.add.reduceat(np.abs(grad), group_starts)
norm_factor = np.where(
sum_abs_grad > 0, np.log2(1 + sum_abs_grad) / sum_abs_grad, 1.0
)
norm_factor_expanded = norm_factor[group_indices]
grad = grad * norm_factor_expanded
return grad, hess
実験条件
前回の記事と同じ実験設定を使用しています。詳細は前回の記事を参照してください。
主要パラメータ(再掲):
- データセット: MovieLens Latest Small(10 万件の評価データ、600 ユーザー、9000 アイテム)
- 分割: 時系列で Train 80% / Valid 10% / Test 10%
- ラベル: rating 値(0.5〜5.0)
- 正例定義: rating >= 4.0(Recall@k 等の計算時)
LightGBM パラメータ:
- num_leaves = 31
- learning_rate = 0.05
- n_estimators = 2000
- early_stopping_rounds = 50
本記事での追加条件:
- seed: 5 つのシードで実行し、平均 ± 標準偏差を計算
- colsample_bytree = 0.25: seed の影響を与えるために追加
実験 1: 全ユーザー同一グループサイズ
まず、前回の記事と同じ条件(人気順 Top-N)で検証します。5 シードで実行し、平均 ± 標準偏差を計算しました。
実験 1 の結果
- lambdarank: norm=True が安定して優位。大規模候補で norm=False の分散が大きい
- xENDCG 系: rank_xendcg と custom_xendcg の差は小さい(±0.001〜0.003)
- 全体傾向: グループサイズが揃っている条件では、正規化の有無による差は限定的
Recall
小規模では lambdarank が優位
| top_n | Recall@10 | Recall@50 | Recall@100 | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | |
| 30 | 0.067±0.001 | 0.068±0.001 | 0.067±0.002 | 0.065±0.001 | 0.105 | 0.105 | 0.105 | 0.105 | 0.105 | 0.105 | 0.105 | 0.105 |
| 100 | 0.067±0.001 | 0.067±0.002 | 0.064±0.001 | 0.064±0.001 | 0.161±0.007 | 0.161±0.004 | 0.161±0.008 | 0.157±0.005 | 0.208 | 0.208 | 0.208 | 0.208 |
| 500 | 0.068±0.001 | 0.067±0.004 | 0.065±0.002 | 0.065±0.002 | 0.142±0.006 | 0.144±0.009 | 0.151±0.002 | 0.151±0.003 | 0.206±0.005 | 0.203±0.011 | 0.211±0.017 | 0.208±0.007 |
| 1000 | 0.067±0.004 | 0.066±0.003 | 0.065±0.002 | 0.065±0.001 | 0.145±0.008 | 0.144±0.002 | 0.149±0.002 | 0.148±0.003 | 0.200±0.007 | 0.193±0.008 | 0.196±0.004 | 0.196±0.004 |
| 3000 | 0.065±0.003 | 0.063±0.004 | 0.063±0.002 | 0.063±0.001 | 0.141±0.009 | 0.141±0.005 | 0.148±0.002 | 0.147±0.002 | 0.191±0.010 | 0.194±0.011 | 0.197±0.005 | 0.196±0.002 |
| 5000 | 0.062±0.004 | 0.065±0.003 | 0.061±0.001 | 0.062±0.002 | 0.139±0.009 | 0.145±0.009 | 0.147±0.003 | 0.146±0.002 | 0.192±0.006 | 0.201±0.010 | 0.198±0.003 | 0.199±0.005 |
| 7000 | 0.056±0.009 | 0.065±0.002 | 0.061±0.003 | 0.061±0.003 | 0.139±0.013 | 0.144±0.010 | 0.147±0.004 | 0.146±0.003 | 0.185±0.019 | 0.198±0.010 | 0.201±0.005 | 0.200±0.003 |
| 9000 | 0.065±0.002 | 0.056±0.021 | 0.062±0.003 | 0.062±0.002 | 0.138±0.014 | 0.124±0.033 | 0.145±0.002 | 0.145±0.002 | 0.200±0.005 | 0.173±0.043 | 0.199±0.005 | 0.196±0.002 |
MAP
大きな差はないが、lamrabdarankの方が標準偏差が大きい傾向
| top_n | MAP@10 | MAP@50 | MAP@100 | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | |
| 30 | 0.045±0.002 | 0.047±0.001 | 0.047±0.001 | 0.047±0.002 | 0.062±0.002 | 0.062±0.001 | 0.063±0.001 | 0.063±0.001 | 0.062±0.002 | 0.062±0.001 | 0.063±0.001 | 0.063±0.001 |
| 100 | 0.047±0.000 | 0.048±0.002 | 0.046±0.002 | 0.046±0.001 | 0.076±0.003 | 0.076±0.002 | 0.076±0.005 | 0.075±0.003 | 0.084±0.002 | 0.084±0.002 | 0.084±0.004 | 0.084±0.002 |
| 500 | 0.048±0.002 | 0.046±0.004 | 0.046±0.001 | 0.046±0.001 | 0.072±0.002 | 0.070±0.002 | 0.071±0.003 | 0.072±0.002 | 0.083±0.002 | 0.079±0.002 | 0.081±0.001 | 0.081±0.002 |
| 1000 | 0.047±0.002 | 0.046±0.002 | 0.045±0.000 | 0.047±0.001 | 0.072±0.003 | 0.070±0.002 | 0.070±0.001 | 0.071±0.001 | 0.081±0.003 | 0.077±0.001 | 0.078±0.001 | 0.079±0.001 |
| 3000 | 0.045±0.003 | 0.042±0.003 | 0.043±0.001 | 0.043±0.001 | 0.069±0.005 | 0.066±0.004 | 0.067±0.002 | 0.067±0.002 | 0.077±0.004 | 0.074±0.004 | 0.075±0.002 | 0.075±0.002 |
| 5000 | 0.041±0.005 | 0.041±0.005 | 0.042±0.003 | 0.042±0.003 | 0.065±0.004 | 0.066±0.006 | 0.067±0.003 | 0.067±0.003 | 0.073±0.003 | 0.075±0.006 | 0.076±0.003 | 0.076±0.003 |
| 7000 | 0.035±0.009 | 0.043±0.005 | 0.040±0.002 | 0.040±0.003 | 0.060±0.014 | 0.068±0.005 | 0.065±0.002 | 0.065±0.003 | 0.068±0.015 | 0.076±0.006 | 0.074±0.002 | 0.074±0.003 |
| 9000 | 0.043±0.004 | 0.036±0.017 | 0.041±0.003 | 0.042±0.003 | 0.067±0.006 | 0.056±0.024 | 0.066±0.002 | 0.066±0.003 | 0.076±0.005 | 0.063±0.027 | 0.075±0.002 | 0.074±0.003 |
NDCG
top_n=9000 で norm=False の標準偏差が極端に大きく、学習が不安定
| top_n | NDCG@10 | NDCG@50 | NDCG@100 | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | |
| 30 | 0.217±0.004 | 0.221±0.002 | 0.222±0.003 | 0.223±0.002 | 0.154±0.003 | 0.155±0.001 | 0.157±0.001 | 0.158±0.001 | 0.141±0.003 | 0.142±0.001 | 0.144±0.001 | 0.145±0.001 |
| 100 | 0.219±0.003 | 0.217±0.006 | 0.211±0.005 | 0.214±0.002 | 0.197±0.006 | 0.198±0.004 | 0.198±0.008 | 0.197±0.005 | 0.206±0.002 | 0.207±0.002 | 0.207±0.004 | 0.209±0.002 |
| 500 | 0.223±0.006 | 0.211±0.011 | 0.213±0.003 | 0.212±0.004 | 0.185±0.005 | 0.181±0.005 | 0.188±0.004 | 0.189±0.003 | 0.206±0.003 | 0.200±0.007 | 0.207±0.007 | 0.206±0.004 |
| 1000 | 0.218±0.008 | 0.211±0.006 | 0.209±0.003 | 0.212±0.002 | 0.186±0.006 | 0.182±0.003 | 0.185±0.002 | 0.187±0.002 | 0.202±0.005 | 0.194±0.003 | 0.198±0.003 | 0.199±0.003 |
| 3000 | 0.214±0.010 | 0.205±0.006 | 0.204±0.005 | 0.203±0.005 | 0.181±0.010 | 0.178±0.005 | 0.182±0.003 | 0.182±0.003 | 0.193±0.008 | 0.191±0.006 | 0.195±0.005 | 0.195±0.004 |
| 5000 | 0.199±0.014 | 0.204±0.011 | 0.203±0.004 | 0.205±0.009 | 0.173±0.006 | 0.178±0.010 | 0.182±0.005 | 0.182±0.005 | 0.189±0.004 | 0.194±0.010 | 0.197±0.004 | 0.198±0.006 |
| 7000 | 0.188±0.026 | 0.208±0.011 | 0.196±0.007 | 0.198±0.009 | 0.170±0.022 | 0.179±0.009 | 0.179±0.005 | 0.179±0.006 | 0.181±0.023 | 0.194±0.010 | 0.195±0.004 | 0.195±0.004 |
| 9000 | 0.208±0.011 | 0.175±0.074 | 0.201±0.009 | 0.200±0.009 | 0.176±0.013 | 0.153±0.052 | 0.180±0.004 | 0.179±0.003 | 0.196±0.007 | 0.168±0.053 | 0.196±0.004 | 0.195±0.004 |
多様性メトリクスの評価
同じようなアイテムばかり推薦すると、ユーザー体験が単調になってしまうため、多様性も評価しました。
多様性メトリクスの知見
- custom_xendcg は ILD と Coverage で優位: 精度だけでなく多様性も向上
- lambdarank_norm_false は Novelty が高い: ただし標準偏差が非常に大きい、学習がうまくいっていない結果とも考えられる
- 正規化は多様性にも好影響: 安定した学習が多様性にも寄与
ILD (Intra-List Diversity)
推薦リスト内のアイテム間の多様性を測定。ジャンル特徴量のコサイン距離の平均値。高いほど多様。
xENDCG 系が lambdarank より高い傾向
norm=False は標準偏差が大きい
| top_n | ILD@10 | ILD@50 | ILD@100 | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | |
| 100 | 0.703±0.023 | 0.686±0.029 | 0.691±0.010 | 0.705±0.026 | 0.734±0.010 | 0.730±0.008 | 0.743±0.002 | 0.741±0.004 | 0.712±0.000 | 0.712±0.000 | 0.712±0.000 | 0.712±0.000 |
| 500 | 0.638±0.060 | 0.613±0.095 | 0.695±0.025 | 0.707±0.005 | 0.731±0.013 | 0.687±0.087 | 0.726±0.016 | 0.733±0.011 | 0.739±0.017 | 0.704±0.072 | 0.727±0.006 | 0.735±0.008 |
| 1000 | 0.663±0.045 | 0.602±0.099 | 0.722±0.005 | 0.718±0.010 | 0.722±0.018 | 0.700±0.043 | 0.734±0.009 | 0.732±0.017 | 0.730±0.015 | 0.705±0.042 | 0.727±0.007 | 0.725±0.007 |
| 3000 | 0.652±0.088 | 0.665±0.050 | 0.724±0.010 | 0.728±0.008 | 0.703±0.039 | 0.703±0.047 | 0.739±0.010 | 0.732±0.006 | 0.715±0.035 | 0.717±0.018 | 0.724±0.003 | 0.728±0.005 |
| 5000 | 0.650±0.092 | 0.618±0.129 | 0.729±0.013 | 0.731±0.007 | 0.684±0.082 | 0.662±0.112 | 0.729±0.013 | 0.725±0.005 | 0.683±0.071 | 0.670±0.108 | 0.723±0.003 | 0.726±0.006 |
Novelty
推薦アイテムの「珍しさ」。人気の低いアイテムを推薦するほど高い。
norm=False が最も高いが、標準偏差が極端に大きい(±1.34 など)
学習の不安定さにより偶然マイナーなアイテムを推薦している可能性
| top_n | Novelty@10 | Novelty@50 | Novelty@100 | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | |
| 100 | 1.30±0.03 | 1.28±0.01 | 1.31±0.01 | 1.31±0.02 | 1.75±0.01 | 1.76±0.01 | 1.75±0.01 | 1.76±0.02 | 1.98±0.00 | 1.98±0.00 | 1.98±0.00 | 1.98±0.00 |
| 500 | 1.35±0.08 | 1.40±0.22 | 1.29±0.03 | 1.28±0.01 | 1.87±0.15 | 1.99±0.30 | 1.85±0.27 | 1.76±0.05 | 2.26±0.13 | 2.29±0.27 | 2.13±0.13 | 2.13±0.10 |
| 1000 | 1.31±0.05 | 1.43±0.19 | 1.29±0.07 | 1.26±0.02 | 1.86±0.15 | 2.08±0.37 | 1.76±0.07 | 1.73±0.01 | 2.28±0.12 | 2.50±0.39 | 2.10±0.14 | 2.03±0.01 |
| 3000 | 1.36±0.16 | 2.13±1.34 | 1.27±0.02 | 1.27±0.01 | 1.99±0.29 | 2.29±0.73 | 1.72±0.01 | 1.73±0.01 | 2.53±0.49 | 2.76±0.90 | 2.04±0.02 | 2.04±0.02 |
| 5000 | 1.47±0.32 | 1.67±0.63 | 1.28±0.01 | 1.28±0.01 | 2.13±0.58 | 2.27±0.80 | 1.73±0.03 | 1.73±0.02 | 2.54±0.63 | 2.67±0.80 | 2.02±0.02 | 2.03±0.02 |
Coverage
全アイテムに対して推薦されるアイテムの割合。高いほど多くのアイテムが推薦される。
custom_xendcg が一貫して高い傾向
lambdarank は候補数増加に伴い Coverage が低下しやすそう?
| top_n | Coverage@10 | Coverage@50 | Coverage@100 | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | |
| 100 | 0.569±0.021 | 0.569±0.023 | 0.589±0.014 | 0.602±0.047 | 0.889±0.011 | 0.894±0.001 | 0.895±0.000 | 0.884±0.024 | 0.895±0.000 | 0.895±0.000 | 0.895±0.000 | 0.895±0.000 |
| 500 | 0.554±0.051 | 0.531±0.058 | 0.576±0.029 | 0.597±0.011 | 0.913±0.030 | 0.839±0.101 | 0.887±0.056 | 0.921±0.018 | 0.943±0.007 | 0.923±0.055 | 0.947±0.000 | 0.947±0.000 |
| 1000 | 0.562±0.022 | 0.520±0.080 | 0.574±0.016 | 0.580±0.010 | 0.915±0.046 | 0.877±0.039 | 0.907±0.021 | 0.920±0.023 | 0.945±0.007 | 0.930±0.040 | 0.947±0.000 | 0.947±0.000 |
| 3000 | 0.526±0.050 | 0.546±0.027 | 0.583±0.024 | 0.602±0.034 | 0.857±0.059 | 0.885±0.045 | 0.909±0.017 | 0.934±0.011 | 0.920±0.044 | 0.965±0.032 | 0.947±0.000 | 0.948±0.000 |
| 5000 | 0.535±0.029 | 0.551±0.043 | 0.599±0.043 | 0.613±0.026 | 0.860±0.055 | 0.859±0.053 | 0.899±0.007 | 0.911±0.009 | 0.908±0.040 | 0.928±0.042 | 0.947±0.000 | 0.947±0.000 |
実験 2: ユーザーごとに異なるグループサイズ
次に、より実践的な条件で検証します。人気順 Top-N に加えて、ユーザーの好みジャンルに基づく候補を追加するため、グループサイズがユーザーによって異なります。
仮説
実験 1 では「グループサイズが揃っている条件では正規化の効果が限定的」でした。では、グループサイズが不均一な場合はどうか?
正規化の理論的な効果(大きなグループの勾配支配を防ぐ)から、下記の結果になると予想しました
- lambdarank: norm=True と norm=False の差が拡大する
- xENDCG 系: custom_xendcg(正規化あり)が rank_xendcg(正規化なし)を上回る
候補数 1000 以上では、ほとんどのユーザーで人気順に好みのジャンルに基づいた候補の全映画が候補に含まれる結果になりました。「ユーザーごとに異なるグループサイズ」の検証という目的を達成できないため、候補数 500 以下のみを実験結果として掲載しています。
| 候補数ベース | グループサイズ (平均 ± 標準偏差) |
|---|---|
| 30 | 241±17 |
| 100 | 257±18 |
| 500 | 505±15 |
Recall
custom_xendcg が高く安定
norm=False は標準偏差が大きく、グループサイズ不均一の影響が顕著
| top_n | Recall@10 | Recall@50 | Recall@100 | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | |
| 30 | 0.057±0.008 | 0.054±0.011 | 0.062±0.005 | 0.062±0.004 | 0.115±0.002 | 0.114±0.002 | 0.114±0.002 | 0.115±0.001 | 0.132±0.003 | 0.128±0.004 | 0.128±0.003 | 0.132±0.003 |
| 100 | 0.064±0.005 | 0.056±0.013 | 0.060±0.008 | 0.064±0.001 | 0.159±0.009 | 0.148±0.010 | 0.151±0.010 | 0.161±0.004 | 0.231±0.002 | 0.223±0.002 | 0.224±0.004 | 0.225±0.004 |
| 500 | 0.055±0.005 | 0.046±0.009 | 0.058±0.006 | 0.064±0.003 | 0.142±0.004 | 0.130±0.012 | 0.150±0.002 | 0.151±0.003 | 0.220±0.017 | 0.192±0.016 | 0.203±0.011 | 0.212±0.008 |
MAP
custom_xendcg が候補数 500 で最大 +0.010 の改善(MAP@ALL: 0.105 vs 0.095)
norm=False は全条件で最下位かつ標準偏差が高い
| top_n | MAP@10 | MAP@50 | MAP@100 | MAP@ALL | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | |
| 30 | 0.039±0.007 | 0.032±0.010 | 0.044±0.003 | 0.043±0.003 | 0.058±0.007 | 0.050±0.010 | 0.061±0.002 | 0.061±0.003 | 0.060±0.007 | 0.052±0.010 | 0.062±0.003 | 0.063±0.003 | 0.062±0.007 | 0.054±0.010 | 0.064±0.003 | 0.064±0.004 |
| 100 | 0.044±0.005 | 0.035±0.010 | 0.043±0.005 | 0.045±0.001 | 0.072±0.003 | 0.061±0.012 | 0.068±0.008 | 0.073±0.001 | 0.084±0.004 | 0.073±0.011 | 0.080±0.007 | 0.084±0.002 | 0.087±0.004 | 0.076±0.011 | 0.082±0.007 | 0.087±0.002 |
| 500 | 0.037±0.006 | 0.027±0.010 | 0.041±0.005 | 0.046±0.003 | 0.060±0.005 | 0.049±0.012 | 0.067±0.004 | 0.071±0.002 | 0.073±0.005 | 0.058±0.013 | 0.076±0.004 | 0.082±0.002 | 0.095±0.004 | 0.080±0.013 | 0.099±0.004 | 0.105±0.001 |
NDCG
custom_xendcg が全条件で最高または同等
norm=True と norm=False の差が実験 1 より拡大(候補数 500 で ndcg@10が 0.032 差)しており、正規化の効果が明確に現れていそう
| top_n | NDCG@10 | NDCG@50 | NDCG@100 | NDCG@ALL | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | λ(T) | λ(F) | xendcg | custom | |
| 30 | 0.192±0.022 | 0.167±0.035 | 0.209±0.011 | 0.209±0.011 | 0.156±0.009 | 0.145±0.015 | 0.161±0.003 | 0.161±0.003 | 0.152±0.008 | 0.139±0.015 | 0.155±0.003 | 0.156±0.003 | 0.192±0.009 | 0.179±0.016 | 0.197±0.003 | 0.196±0.005 |
| 100 | 0.210±0.014 | 0.175±0.035 | 0.202±0.017 | 0.212±0.005 | 0.195±0.004 | 0.173±0.020 | 0.185±0.013 | 0.195±0.002 | 0.217±0.003 | 0.200±0.014 | 0.210±0.008 | 0.215±0.003 | 0.227±0.003 | 0.212±0.014 | 0.222±0.007 | 0.226±0.002 |
| 500 | 0.182±0.015 | 0.150±0.033 | 0.196±0.015 | 0.209±0.010 | 0.171±0.003 | 0.150±0.024 | 0.183±0.005 | 0.188±0.004 | 0.199±0.008 | 0.172±0.022 | 0.198±0.007 | 0.207±0.006 | 0.292±0.003 | 0.276±0.018 | 0.297±0.004 | 0.303±0.000 |
仮説の検証結果
仮説 1: lambdarank の norm=True vs norm=False の差が拡大する → 予想通り
- 差が拡大: 候補数 500 で NDCG@10 の差が 0.032(0.182 vs 0.150)。実験 1 では同条件で 0.012 だった
- norm=False の劣化が顕著: 標準偏差も ±0.033 と大きく、学習が不安定に
仮説 2: custom_xendcg が rank_xendcg を上回る → 予想通り
- 明確な差: NDCG@10 で +0.010〜0.013 の改善(実験 1 では ±0.001〜0.003)
- 安定性も向上: rank_xendcg は ±0.015〜0.017 だが、custom_xendcg は ±0.005〜0.011 と安定
- @ALL でも同傾向: 上位だけでなく全体のランキング品質でも custom_xendcg が優位
推奨 objective
実験結果からケースごとに推奨 objective を整理してみました。
| 状況 | 推奨 objective | 理由 |
|---|---|---|
| 小〜中規模候補(30〜1000) | lambdarank (norm=True) |
安定した高性能 |
| 大規模候補(3000 以上) |
rank_xendcg または custom_xendcg
|
分散が小さく安定 |
| グループサイズが不均一 | custom_xendcg |
正規化によって安定性が向上 |
| 学習速度重視 | rank_xendcg |
カスタム objective より高速 |
| 多様性も重視 | custom_xendcg |
ILD、Coverage が高い傾向 |
まとめ
正規化の効果
本記事では、LightGBM のランク学習における正規化の効果を 2 つの観点から検証しました。
1. lambdarank_norm の効果
-
lambdarank_norm=TrueはFalseに比べて、特に中規模候補(100〜1000)で +0.007〜0.012 の NDCG 改善 - 大規模候補(7000〜9000)では
Falseの標準偏差が ±0.074 と非常に大きく不安定 - デフォルトの True を変更する理由は基本的にない
2. xENDCG への正規化適用
- グループサイズ不均一時に custom_xendcg が rank_xendcg を +0.010〜0.013(NDCG@10)上回る
- 安定性も向上(複数シードでの標準偏差が減少)
- グループサイズが不均一な実務環境では custom_xendcg を推奨
正規化が効く条件
- グループサイズが不均一な場合: 正規化の効果が顕著
- 大規模候補の場合: 学習の安定性が向上
副次的な知見: 1 次近似 vs 3 次近似(事前実験)
本記事の custom_xendcg は 1 次 Taylor 近似を採用していますが、LightGBM の rank_xendcg は 3 次近似を使用しています。
事前実験で両者を比較した結果、精度差がほぼ無視できるため、計算がシンプルかつ高速な 1 次近似を採用しました。
- 精度差: 全条件で NDCG@10 の差が ±0.001〜0.002 程度
- 学習時間: 3 次近似は 1 次近似の約 1.3 倍
補足: カスタム評価指標の実装
LightGBM でカスタム objective を使用する際、Early Stopping 用の評価指標もカスタム実装が必要になります。本実験では NDCG@K のカスタム評価指標を実装しました。
NDCG@K の実装
import numpy as np
from typing import Callable
import lightgbm as lgb
def create_ndcg_eval(k: int = 10) -> Callable:
"""NDCG@k 評価関数を作成.
グループごとに NDCG@k を計算し、平均を返す。
Args:
k: NDCG の截断位置
Returns:
LightGBM のカスタム評価関数
"""
def ndcg_eval(preds: np.ndarray, data: lgb.Dataset) -> tuple[str, float, bool]:
labels = data.get_label()
groups = data.get_group()
if groups is None:
groups = np.array([len(labels)])
ndcg_scores = []
start_idx = 0
for group_size in groups:
end_idx = start_idx + group_size
group_labels = labels[start_idx:end_idx]
group_preds = preds[start_idx:end_idx]
# 予測スコアで降順ソート
sorted_indices = np.argsort(-group_preds)
sorted_labels = group_labels[sorted_indices]
# DCG@k
positions = np.arange(1, min(k, len(sorted_labels)) + 1)
gains = np.power(2.0, sorted_labels[:len(positions)]) - 1
discounts = np.log2(positions + 1)
dcg = np.sum(gains / discounts)
# IDCG@k(理想的な順位)
ideal_sorted_labels = np.sort(group_labels)[::-1]
ideal_gains = np.power(2.0, ideal_sorted_labels[:len(positions)]) - 1
idcg = np.sum(ideal_gains / discounts)
if idcg > 0:
ndcg = dcg / idcg
ndcg_scores.append(ndcg)
start_idx = end_idx
mean_ndcg = np.mean(ndcg_scores) if ndcg_scores else 0.0
return f"ndcg@{k}", float(mean_ndcg), True
return ndcg_eval
注意点
-
計算コスト: カスタム評価指標は Python で実装されるため、C++ 組み込みの
ndcgより遅くなります。大規模データでは注意が必要です。
実装例
カスタム objective とカスタム評価指標を LightGBM で使用する方法を紹介します。
import lightgbm as lgb
import numpy as np
# 上記の xendcg_normalized_objective を定義済みとする
def custom_obj(preds: np.ndarray, data: lgb.Dataset) -> tuple[np.ndarray, np.ndarray]:
"""LightGBM カスタム objective のラッパー."""
labels = data.get_label()
groups = data.get_group()
if groups is None:
groups = np.array([len(labels)])
return xendcg_normalized_objective(labels, preds, groups)
# カスタム評価指標を生成
ndcg_10 = create_ndcg_eval(k=10)
# LightGBM で使用
train_data = lgb.Dataset(X_train, label=y_train, group=group_train)
valid_data = lgb.Dataset(X_valid, label=y_valid, group=group_valid, reference=train_data)
params = {
"objective": custom_obj,
"num_leaves": 31,
"learning_rate": 0.05,
}
model = lgb.train(
params,
train_data,
num_boost_round=2000,
valid_sets=[valid_data],
feval=ndcg_10, # カスタム評価指標で Early Stopping
callbacks=[lgb.early_stopping(100, verbose=False)],
)
おわりに
本記事では、LightGBM のランク学習における正規化の効果を検証しました。主な発見は以下の通りです。
- lambdarank_norm=True は効果的: デフォルトで True になっている理由がよくわかった
- xENDCG にも正規化が有効: 特にグループサイズ不均一時
- 正規化は精度と多様性の両方に好影響: 安定した学習のキーになっている
正直なところ、LightGBM の正規化ロジックを参考にカスタム objective を実装してみたものの、「本当にうまくいくのかな」と半信半疑でした。実際に実験して効果が確認できて嬉しかったです。
ぜひみなさんの環境でも試してみていただけたら嬉しいです。
この実験で得られた知見が、皆さんのランキングモデル改善の一助になれば幸いです。
Discussion