0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

35_LightGBMのfeature_importanceをCSVに出力して管理する

0
Posted at

はじめに

「どの特徴量がモデルに効いているか」は特徴量エンジニアリングの改善に不可欠です。 

LightGBMには feature_importance() メソッドで特徴量重要度を取得できますが、gainsplit の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')

まとめ

  1. feature_importance(importance_type='gain')'split' の両方を取得する
  2. 正規化(合計1.0)してモデル間の比較をしやすくする
  3. CSVに保存してモデルの更新ごとに変化を追跡する
  4. gain も split も低い特徴量は削除を検討する
  5. SHAP との比較で「本当に効いている特徴量」を確認する

詳細な実装について

競馬AIの170種類以上の特徴量をSHAPとfeature_importanceで評価した結果や、不要な特徴量を削除してAUCがどう変化したかに興味がある方へ。

詳細はnoteの有料シリーズで解説しています:


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?