次の一投の行方を予測! プロ野球データ分析チャレンジ

ストライク? ヒット? それともホームラン!?

賞金: 100,000 参加ユーザー数: 383 3年以上前に終了

前処理追加版-LightGBM Base line−コメント付き (CV: 0.14017 / LB 0.13157 ) by Oregin

次の一投の行方を予測! プロ野球データ分析チャレンジ

簡単な前処理(inning数、裏表列の追加、game_infoの結合,カテゴリカル変数のエンコーディング)を行ったサンプルコードです。ご参考までご活用ください。

※Google Colabで実行可能です。

CV= 0.14017 LB= 0.13157 でした。

ディレクトリ構成

  • ./notebook : このファイルを入れておくディレクトリ(カレントディレクトリをこのディレクトリに移動して実行してください。)
  • ./features : 前処理した特徴量を入れておくディレクトリ
  • ./data : test_data.csv,train_data.csv,game_info.csvを入れておくディレクトリ
# xfeatのインストール
!pip install git+https://github.com/pfnet-research/xfeat.git
# ------------------------------------------------------------------------------
# 各種ライブラリのインポート
# ------------------------------------------------------------------------------
import pandas as pd
import numpy as np
import json
import os
import random
import string
import re

from pathlib import Path
from tqdm import tqdm

import lightgbm as lgb
from sklearn.model_selection import KFold
from sklearn.metrics import f1_score
from sklearn import preprocessing
# データ読み込み
#####################################
###### train ########################
#####################################

train = pd.read_csv('../data/train_data.csv')
target = train['y']
train = train.drop(['id','y'],axis=1)

#####################################
#### test ###########################
#####################################

test = pd.read_csv('../data/test_data.csv')
test = test.drop('id',axis=1)

#####################################
#### test ###########################
#####################################

game = pd.read_csv('../data/game_info.csv')
game = game.drop('Unnamed: 0',axis=1)

前処理

# 訓練データのみにある列名(テストデータにはない列名)のリストを作成
delcollist = []
for col in train.columns:
  if not col in test.columns:
    delcollist.append(col)
# 訓練データのみにある列名を削除
train = train.drop(delcollist,axis=1)
# inning を 数値に変換
train['inning_num'] =  train['inning'].apply(lambda x: re.sub("\\D", "", x))
test['inning_num'] =  test['inning'].apply(lambda x: re.sub("\\D", "", x))
# 表裏を判定する関数
def omote_ura(x):
  if '表' in x:
    return 0
  else:
    return 1
# 表裏の列を追加
train['inning_ForB'] =  train['inning'].apply(lambda x: omote_ura(x))
test['inning_ForB'] =  test['inning'].apply(lambda x: omote_ura(x))
# game_infoの追加
train = pd.merge(train, game, how='left')
test = pd.merge(test, game, how='left')
# inningの削除
train = train.drop('inning',axis=1)
test = test.drop('inning',axis=1)
# カテゴリカル変数のカラムを抽出
categorical_columns = [x for x in train.columns if train[x].dtypes == 'object']
# カテゴリカル変数をカウントエンコードする
from xfeat import CountEncoder

encoder = CountEncoder(input_cols=categorical_columns)
train = encoder.fit_transform(train)
test = encoder.transform(test)
# 訓練データにターゲット列を追加する
train['target'] = target
# カテゴリカル変数をターゲットエンコーディングする
from sklearn.model_selection import KFold
from xfeat import TargetEncoder

fold = KFold(n_splits=5, shuffle=True, random_state=42)
encoder = TargetEncoder(input_cols=categorical_columns,
                        target_col='target',
                        fold=fold)
train = encoder.fit_transform(train)
test = encoder.transform(test)
# エンコーディング前の列を削除する
train = train.drop(categorical_columns,axis=1)
test = test.drop(categorical_columns,axis=1)
train = train.drop('target',axis=1)

学習と予測

SEED = 42
NFOLDS = 5
#####################################################3
### LGBで学習、予測する関数の定義
########################################################
def Train_and_Pred(train,target,test):
    # --------------------------------------
    # パラメータ定義
    # --------------------------------------
    lgb_params = {
                    'objective': 'multiclass',
                    'boosting_type': 'gbdt',
                    'n_estimators': 50000,
                    'colsample_bytree': 0.5,
                    'subsample': 0.5,
                    'subsample_freq': 3,
                    'reg_alpha': 8,
                    'reg_lambda': 2,
                    'random_state': SEED,
                    "bagging_fraction": 0.5520399476847848,
                    "bagging_freq": 1,
                    "feature_fraction": 0.4436319472771827,
                    "lambda_l1": 0.01113869595673112,
                    "lambda_l2": 8.706009358617911e-07,
                    "learning_rate": 0.012307412937706345,
                    "min_child_samples": 18,
                    "num_leaves": 8,        
                  }

    # --------------------------------------
    # 学習と予測
    # --------------------------------------
    kf = KFold(n_splits=NFOLDS, shuffle=True, random_state=SEED)
    lgb_oof = np.zeros(train.shape[0])
    lgb_pred = 0

    for fold, (trn_idx, val_idx) in enumerate(kf.split(X=train)):
        X_train, y_train = train.iloc[trn_idx], target.iloc[trn_idx]
        X_valid, y_valid = train.iloc[val_idx], target.iloc[val_idx]
        X_test = test

        # LightGBM
        model = lgb.LGBMClassifier(**lgb_params)
        model.fit(X_train, y_train,
                  eval_set=(X_valid, y_valid),
                  eval_metric='logloss',
                  verbose=False,
                  early_stopping_rounds=500
                  )

        lgb_oof[val_idx] = model.predict(X_valid)
        lgb_pred += model.predict(X_test) / NFOLDS
        f1_macro = f1_score(y_valid, lgb_oof[val_idx], average='macro')
        print(f"fold {fold} lgb score: {f1_macro}")

    f1_macro = f1_score(target, lgb_oof, average='macro')
    print("+-" * 40)
    print(f"score: {f1_macro}")
    print(f"model score: {model.score(train, target)}")

    return f1_macro,lgb_pred
#学習と予測の実行
score,pred = Train_and_Pred(train,target,test)
# ------------------------------------------------------------------------------
# 提出ファイルの作成
# ------------------------------------------------------------------------------

#テスト結果の出力
submit_df = pd.DataFrame({'y': pred.round().astype(int)})
submit_df.index.name = 'id'
submit_df.to_csv('submission.csv')

以上

添付データ

  • BaseLine_BaseBall.ipynb?X-Amz-Expires=10800&X-Amz-Date=20241118T124847Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIP7GCBGMWPMZ42PQ
  • Icon0
    退会したユーザー

    皆さんの知見をいただきたいのですが...

    lgb_pred += model.predict(X_test) / NFOLDS

    この箇所ではfold毎の平均予測値を算出していますので、例えばfold毎の予測値が6, 1, 1, 1, 1だったとすると2が算出されます。今回の場合は他クラス分類なので、最頻値の1を予測値として採用するのが適当かと。そうすると、関連部分の抜粋コードは以下のようになるのではないかと思っているのですが認識に相違はあるでしょうか。ただ、このコードにはまだカウント数が同値の複数の最頻値が出現した場合の対処コードを追加する必要があります。

    from scipy.stats import mode
    ...
    
    def Train_and_Pred(train,target,test):
        ...
        lgb_pred = np.zeros(test.shape[0]*NFOLDS).reshape(-1, NFOLDS)
        ...
        lgb_pred[:, fold] = model.predict(X_test)
        ...
        score,pred = Train_and_Pred(train,target,test)
    
    ...
    #テスト結果の出力
    value, count = mode(pred, axis=1)
    submit_df = pd.DataFrame({'y': value.ravel().astype(int)})

    ところがこのコードを適用すると、LB0.13090となり、@Oreginさんの結果(LB 0.13157)より悪化してしまったという...^^;

    Aws4 request&x amz signedheaders=host&x amz signature=58f3e7d120a13b729c9c66e3c9986a8ed1ad5a9ded526091966f4ac9b36da40e
    Oregin

    ありがとうございます。 ご指摘の通り大小関係のない他クラス分類で、予測値の平均は処理として意味をなさないですね・・・。 Scoreが良かったのは、たまたまPublic評価用のデータに適合していただけですね。 最頻値を採用することが適切ですね。

    Icon20
    genki_H

    BIZENさんの「最頻値」を取るほうが筋は通っている気がします。
    予測targetが、0:ボール, 1:ストライク, 2:ファウル…
    となっていて、0,0,2,2の「平均」は1となりますが、
    <ボール>と<ファウル>の間をとって、<ストライク>というのは
    理屈として変ですよね。

    「平均」を取るLBの方が良いのは、予測targetが投球結果で
    ↓これなので、0~3に偏るので偶然だと考えました。
    0:ボール, 1:ストライク, 2:ファウル, 3:アウト, 4:シングルヒット, 5:二塁打, 6:三塁打, 7:ホームラン

    Icon0
    退会したユーザー

    @genki_Hさん、ありがとうございます。

    Aws4 request&x amz signedheaders=host&x amz signature=58f3e7d120a13b729c9c66e3c9986a8ed1ad5a9ded526091966f4ac9b36da40e
    Oregin

    ご指摘ありがとうございました。 予測値の最頻値を取るように修正したバージョンを投稿いたしました。 https://prob.space/competitions/npb/discussions/Oregin-Postb9d508bafe7dfccde206 少しだけ、LBが向上しました。

    Favicon
    new user
    コメントするには 新規登録 もしくは ログイン が必要です。