はじめに
「どの特徴量がモデルに効いているか」は特徴量エンジニアリングの改善に不可欠です。
LightGBMには feature_importance() メソッドで特徴量重要度を取得できますが、gain と split の2種類があります。この記事では両方を取得・比較し、CSVで管理する方法を解説します。
gain と split の違い
| 指標 | 定義 | 特徴 |
|---|---|---|
split |
その特徴量が分岐点として使われた回数 | 使用頻度を表す |
gain |
その特徴量が使われた分岐での情報利得の合計 | 実際の予測への貢献度を表す |
一般的に gain の方が実際に役に立っている特徴量を示しますが、両方を確認することで特徴量の性質がわかります。
feature_importance の取得
import lightgbm as lgb
import pandas as pd
import numpy as np
def get_feature_importance_df(
model: lgb.LGBMClassifier,
feature_names: list[str],
) -> pd.DataFrame:
"""
LightGBMモデルの特徴量重要度をDataFrameで返す。
Returns:
gain と split の両方を含むDataFrame
"""
importance_gain = model.booster_.feature_importance(importance_type='gain')
importance_split = model.booster_.feature_importance(importance_type='split')
df = pd.DataFrame({
'feature': feature_names,
'gain': importance_gain,
'split': importance_split,
})
# 正規化(合計1.0)
df['gain_norm'] = df['gain'] / df['gain'].sum() if df['gain'].sum() > 0 else 0
df['split_norm'] = df['split'] / df['split'].sum() if df['split'].sum() > 0 else 0
# gain の降順でソート
df = df.sort_values('gain', ascending=False).reset_index(drop=True)
df['rank_gain'] = df.index + 1
df['rank_split'] = df['split'].rank(ascending=False, method='min').astype(int)
return df
# 使い方
model = lgb.LGBMClassifier(n_estimators=500, verbose=-1)
model.fit(X_train, y_train)
importance_df = get_feature_importance_df(model, feature_names)
print(importance_df.head(20).to_string(index=False))
CSVへの保存と管理
from datetime import datetime
from pathlib import Path
def save_feature_importance(
model: lgb.LGBMClassifier,
feature_names: list[str],
save_dir: str = 'feature_importance',
model_name: str = 'model',
target: str = 'win',
) -> pd.DataFrame:
"""
特徴量重要度をCSVに保存する。
ファイル名にモデル名・ターゲット・日時を含める。
"""
df = get_feature_importance_df(model, feature_names)
save_path = Path(save_dir)
save_path.mkdir(exist_ok=True)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f'{model_name}_{target}_{timestamp}.csv'
filepath = save_path / filename
df.to_csv(filepath, index=False, encoding='utf-8-sig')
print(f"特徴量重要度を保存: {filepath}")
return df
# 3モデル体制での一括保存
for target, model in [('win', model_win), ('place2', model_place2), ('place3', model_place3)]:
save_feature_importance(model, feature_names, target=target)
重要度のランク変化を追跡する
モデルの更新前後で特徴量のランクがどう変わったか比較します。
def compare_importance(
df_before: pd.DataFrame,
df_after: pd.DataFrame,
top_n: int = 30,
) -> pd.DataFrame:
"""
2つの重要度DataFrameを比較してランク変化を表示する。
"""
merged = df_before[['feature', 'gain', 'rank_gain']].merge(
df_after[['feature', 'gain', 'rank_gain']],
on='feature',
suffixes=('_before', '_after'),
)
merged['rank_change'] = merged['rank_gain_before'] - merged['rank_gain_after']
# プラス = ランクが上がった(before=10位 → after=5位 → diff=5)
merged = merged.sort_values('rank_gain_after').head(top_n)
print(f"\n上位{top_n}特徴量のランク変化:")
for _, row in merged.iterrows():
change = row['rank_change']
if change > 0:
arrow = f"↑{int(change)}"
elif change < 0:
arrow = f"↓{int(abs(change))}"
else:
arrow = "→"
print(f" {row['rank_gain_after']:3d}位 {arrow:5s} {row['feature']}")
return merged
重要度が低い特徴量を削除する
def remove_low_importance_features(
df: pd.DataFrame,
model: lgb.LGBMClassifier,
feature_names: list[str],
threshold_gain: float = 0.0001,
threshold_split: int = 0,
) -> list[str]:
"""
重要度が低い特徴量を除外した新しい特徴量リストを返す。
Args:
threshold_gain: gain の正規化値(この値以下を除外)
threshold_split: split 回数(この回数以下を除外)
"""
importance_df = get_feature_importance_df(model, feature_names)
# 重要度が低い特徴量を特定
low_gain = importance_df[importance_df['gain_norm'] <= threshold_gain]['feature']
low_split = importance_df[importance_df['split'] <= threshold_split]['feature']
# 両方の基準で低い特徴量を除外
to_remove = set(low_gain) & set(low_split)
print(f"除外候補: {len(to_remove)}個")
for feat in sorted(to_remove):
print(f" - {feat}")
kept = [f for f in feature_names if f not in to_remove]
print(f"\n残存特徴量: {len(kept)}個 / {len(feature_names)}個")
return kept
# 使い方
feature_names_pruned = remove_low_importance_features(
df, model, feature_names,
threshold_gain=0.0001,
threshold_split=5,
)
可視化:上位特徴量のバーチャート
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.family'] = 'MS Gothic' # 日本語フォント(Windows)
def plot_feature_importance(
importance_df: pd.DataFrame,
top_n: int = 30,
save_path: str = 'feature_importance.png',
importance_type: str = 'gain',
):
"""上位N特徴量の重要度を横棒グラフで表示する"""
top = importance_df.head(top_n).copy()
fig, ax = plt.subplots(figsize=(10, top_n * 0.4))
bars = ax.barh(
top['feature'][::-1],
top[importance_type][::-1],
color='steelblue',
)
ax.set_xlabel(f'Feature Importance ({importance_type})')
ax.set_title(f'Top {top_n} Features ({importance_type})')
plt.tight_layout()
plt.savefig(save_path, dpi=100, bbox_inches='tight')
plt.show()
print(f"保存: {save_path}")
# gain と split の両方を可視化
plot_feature_importance(importance_df, top_n=30, importance_type='gain',
save_path='importance_gain.png')
plot_feature_importance(importance_df, top_n=30, importance_type='split',
save_path='importance_split.png')
まとめ
-
feature_importance(importance_type='gain')と'split'の両方を取得する - 正規化(合計1.0)してモデル間の比較をしやすくする
- CSVに保存してモデルの更新ごとに変化を追跡する
- gain も split も低い特徴量は削除を検討する
- SHAP との比較で「本当に効いている特徴量」を確認する
詳細な実装について
競馬AIの170種類以上の特徴量をSHAPとfeature_importanceで評価した結果や、不要な特徴量を削除してAUCがどう変化したかに興味がある方へ。
詳細はnoteの有料シリーズで解説しています: