はじめに
今KaggleのMLB Player Digital Engagement Forecastingというコンペに参加してて、ベースとなるモデルを見つけたのでその知見について共有していこうと思っています。具体的な内容は試合の詳細やプロ野球選手の特性を持つデータセットから将来のファンエンゲージメントを予測するものです。エンゲージメントって言葉がマーケティングでは「企業/商品/サービス」と「ユーザー」とのつながりの深さを表す用語なので、このコンペにおけるファンエンゲージメントはプロ野球選手に対する愛着度や信頼度、人気度といった感じでとらえてます。具体的には、最近だと現メジャーリーグで活躍している大谷翔平選手が連続でHR?を打って話題になってた気がしますけど、そういうHRの数といった特徴量から、人気度とか話題度、注目度(やや広く意味をとっています)はどのくらい広がった(大きくなった)のか予測する感じです。もちろん、デッドボールやバッドプレイ等を行うとファンエンゲージメントは下がってしまいます。
今回参考したコンペのcodeは以下のものになります
LightGBM + ANN weight with LOVE
直近ではこのコードを用いて各自が性能を上げるスタンスがみられているので取り扱うことにしました。ローカル環境でデータを確認しています。(Kaggleでノートブックを提出しないといけないけどハードル高いです...)
データセットの規模がこのコンペでは莫大なため、すべてを可視化等することはしていないので、興味がある人は本コンペに参加してみるといいです。
コード
モジュールのインポート
import numpy as np
import pandas as pd
from pathlib import Path
from sklearn.metrics import mean_absolute_error
from datetime import timedelta
from functools import reduce
from tqdm import tqdm
import lightgbm as lgbm
データの読み込みと、簡単なデータの可視化
BASE_DIR = Path('mlb-player-digital-engagement-forecasting')
TRAIN_DIR = Path('mlb-pdef-train-dataset')
players = pd.read_csv('players.csv')
rosters = pd.read_pickle('rosters_train.pkl')
targets = pd.read_pickle('nextDayPlayerEngagement_train.pkl')
scores = pd.read_pickle('playerBoxScores_train.pkl')
scores = scores.groupby(['playerId', 'date']).sum().reset_index()
Pathを使うと可読性がよくなるとかなんとか...(あまり気にしなくていいです。)
このコンペの本来のコードはjson形式でコードが提供されていたり、特徴量(カラム数)が200近く?あったので、自分で初めからスタートするのではなく、他の参加者のコードを最初から活用することにしました。またデータの提出形式が初心者には複雑だったため、頼ってしまいました。
特徴量の説明と簡単な考察
・plaerID プレーヤーの一意の識別子
・gamePK ゲームの一意の識別子
・teamId プレーヤーがその日にいるteamId
・home ホームチーム→1 離れている→0、 ホームだと影響力大きかったりしそう
・gamePK ゲームの一意の識別子
・teamId チームの一意の識別子
・battingOrder 打順スポット。あまり解説読んでてもよくわかってない(3, 4桁ある)
・gamesPlayedBatting ゲームに参加した場合1
・flyOuts 合計フライアウト、負の相関がありそう
・groundOuts 内野ゴロによるアウト、負の相関がありそう
・runsScored 合計ラン
・doubles ゲームの合計の2倍?? 0, 1, 2, 3. 4の計5つの種類あるそうだけどよくわかってない
・triples ゲームの合計の3倍
・homeRuns ゲームの総本塁打。正の因果が強そう
・strikeOuts ゲームの合計三振。正の因果が強そう
・baseOnBalls ゲームの歩いた数?走塁数のことぽいけど、二桁以上のものが多く存在しているためよくわからず
・intentionalWalks 敬遠。負の因果がありそう
・hits 総ヒット数
・hitByPitch ピッチによるゲームの合計ヒット
・atBats 打席数
・caughtStealing 走塁を防いだ回数。正の因果がありそう
・stolenBases 盗塁総数
・groundIntoDoublePlay ダブルプレイの合計
・groundIntoTriplePlay トリプルプレイの合計
・plateAppearances ゲームの総打席
・totalBases 塁に到達した数
・rbi ゲームの合計打点
・leftOnBase 総ランナーがベースに残った
・sacBunts ゲームの合計ギセイバント
・sacFlies 総犠牲フライ
・catchersInterference キャッチャーの関与回数。ピッチャーの状況が悪いと、多くなりそう。他の変数と共変量になってそう。
・pickoffs ゲームの合計回数がベースから外れる。
・gamesPlayedPitching プレーヤーが投手としてゲームに参加した場合は 1
・gamesStartedPitching バイナリ、プレーヤーがゲームの先発投手だった場合は1
・completeGamesPitching バイナリ、完投でクレジットされている場合は1、正の因果が強そう
・shutoutsPitching バイナリ、完封(無失点)でクレジットされている場合は1
・winsPitching バイナリ、勝利でクレジットされている場合は 1
・lossesPitching バイナリ、損失がクレジットされている場合は1
・flyOutsPitching 許可されたフライアウトのゲーム合計
・airOutsPitching エアアウト(フライアウト+ポップアウト)のゲーム合計が許可
・groundOutsPitching ゲームの合計グラウンドアウトが許可
・runsPitching ゲームの合計実行が許可
・doublesPitching ゲームの合計は2倍
・triplesPitching ゲームの合計トリプルが許可されます。
・homeRunsPitching ゲームの合計ホームランが許可されます。
・strikeOutsPitching ゲームの合計三振が許可されます。
・baseOnBallsPitching ゲームの合計歩行が許可されます。
・intentionalWalksPitching ゲームの故意四球の合計が許可されます。
・hitsPitching 許可されるゲームの合計ヒット数。
・hitByPitchPitching 許可されたピッチによるゲームの合計ヒット。
・atBatsPitching 打席数でのゲーム合計
・caughtStealingPitching 盗塁を防いだ回数?。
・stolenBasesPitching : ゲームの盗塁の合計は許可されます。
・inningsPitched : ゲームの総投球回。
・saveOpportunities : バイナリ、保存の機会がある場合は1。
・earnedRuns ゲームの合計自責点は許可されています。
・battersFaced 直面したゲームの総打者。
・outsPitching ゲームの合計アウトが記録。
・pitchesThrown 投げられた投球のゲーム総数。
・balls 投げられたゲームの合計ボール数。
・strikes スローされたゲームの合計ストライク。
・hitBatsmen ゲームの総死球打者。負の因果が強そう
・balks ボーク合計、ピッチャーの投球や塁への送球における反則行為、
・wildPitches 投げられた暴投のゲーム総数。
・pickoffsPitching ゲームのピックオフの総数。
・rbiPitching 打点のゲーム総数は許可されています。
・gamesFinishedPitching
・inheritedRunners 継承されたランナーのゲーム合計を想定。
・inheritedRunnersScored 得点した継承されたランナーのゲーム合計。
・catchersInterferencePitching キャッチャーの干渉のゲーム合計はバッテリーによって発生しました。
・sacBuntsPitching ゲームの犠牲バントの合計。
・sacFliesPitching ゲームの犠牲フライ。
・saves バイナリ、保存でクレジットされている場合は1。
・holds バイナリ、保留がクレジットされている場合は1。
・blownSaves バイナリ、ブローセーブでクレジットされている場合は1。
・assists ゲームのアシスト総数。
・putOuts ゲームの刺殺の総数。
・errors ゲームのエラーの総数。
・chances ゲームのトータルフィールディングチャンス。
sacBuntsとsacBuntsPitchingの違いがわからなかったりして途中から説明は放棄してます...ただ、実務の上ではしっかりどういう特徴量なのかはしっかり抑えときたいです。後日特徴量エンジニアリングに挑戦したいと思っています。
必要な特徴量の抽出
targets_cols = ['playerId', 'target1', 'target2', 'target3', 'target4', 'date']
players_cols = ['playerId', 'primaryPositionName']
rosters_cols = ['playerId', 'teamId', 'status', 'date']
scores_cols = ['playerId', 'battingOrder', 'gamesPlayedBatting', 'flyOuts',
'groundOuts', 'runsScored', 'doubles', 'triples', 'homeRuns',
'strikeOuts', 'baseOnBalls', 'intentionalWalks', 'hits', 'hitByPitch',
'atBats', 'caughtStealing', 'stolenBases', 'groundIntoDoublePlay',
'groundIntoTriplePlay', 'plateAppearances', 'totalBases', 'rbi',
'leftOnBase', 'sacBunts', 'sacFlies', 'catchersInterference',
'pickoffs', 'gamesPlayedPitching', 'gamesStartedPitching',
'completeGamesPitching', 'shutoutsPitching', 'winsPitching',
'lossesPitching', 'flyOutsPitching', 'airOutsPitching',
'groundOutsPitching', 'runsPitching', 'doublesPitching',
'triplesPitching', 'homeRunsPitching', 'strikeOutsPitching',
'baseOnBallsPitching', 'intentionalWalksPitching', 'hitsPitching',
'hitByPitchPitching', 'atBatsPitching', 'caughtStealingPitching',
'stolenBasesPitching', 'inningsPitched', 'saveOpportunities',
'earnedRuns', 'battersFaced', 'outsPitching', 'pitchesThrown', 'balls',
'strikes', 'hitBatsmen', 'balks', 'wildPitches', 'pickoffsPitching',
'rbiPitching', 'gamesFinishedPitching', 'inheritedRunners',
'inheritedRunnersScored', 'catchersInterferencePitching',
'sacBuntsPitching', 'sacFliesPitching', 'saves', 'holds', 'blownSaves',
'assists', 'putOuts', 'errors', 'chances', 'date']
feature_cols = ['label_playerId', 'label_primaryPositionName', 'label_teamId',
'label_status', 'battingOrder', 'gamesPlayedBatting', 'flyOuts',
'groundOuts', 'runsScored', 'doubles', 'triples', 'homeRuns',
'strikeOuts', 'baseOnBalls', 'intentionalWalks', 'hits', 'hitByPitch',
'atBats', 'caughtStealing', 'stolenBases', 'groundIntoDoublePlay',
'groundIntoTriplePlay', 'plateAppearances', 'totalBases', 'rbi',
'leftOnBase', 'sacBunts', 'sacFlies', 'catchersInterference',
'pickoffs', 'gamesPlayedPitching', 'gamesStartedPitching',
'completeGamesPitching', 'shutoutsPitching', 'winsPitching',
'lossesPitching', 'flyOutsPitching', 'airOutsPitching',
'groundOutsPitching', 'runsPitching', 'doublesPitching',
'triplesPitching', 'homeRunsPitching', 'strikeOutsPitching',
'baseOnBallsPitching', 'intentionalWalksPitching', 'hitsPitching',
'hitByPitchPitching', 'atBatsPitching', 'caughtStealingPitching',
'stolenBasesPitching', 'inningsPitched', 'saveOpportunities',
'earnedRuns', 'battersFaced', 'outsPitching', 'pitchesThrown', 'balls',
'strikes', 'hitBatsmen', 'balks', 'wildPitches', 'pickoffsPitching',
'rbiPitching', 'gamesFinishedPitching', 'inheritedRunners',
'inheritedRunnersScored', 'catchersInterferencePitching',
'sacBuntsPitching', 'sacFliesPitching', 'saves', 'holds', 'blownSaves',
'assists', 'putOuts', 'errors', 'chances','target1_mean',
'target1_median', 'target1_std', 'target1_min', 'target1_max', 'target1_prob',
'target2_mean', 'target2_median', 'target2_std', 'target2_min', 'target2_max',
'target2_prob', 'target3_mean', 'target3_median', 'target3_std', 'target3_min',
'target3_max', 'target3_prob', 'target4_mean', 'target4_median', 'target4_std',
'target4_min', 'target4_max', 'target4_prob']
主に用いるデータセット
player_target_stats = pd.read_csv("player_target_stats.csv")
data_names=player_target_stats.columns.values.tolist()
data_names
ここの特徴量が今回の分析コンペの中枢になります。(結果への影響力が高い)
訓練用のデータセットの作成
# creat dataset
train = targets[targets_cols].merge(players[players_cols], on=['playerId'], how='left')
train = train.merge(rosters[rosters_cols], on=['playerId', 'date'], how='left')
train = train.merge(scores[scores_cols], on=['playerId', 'date'], how='left')
train = train.merge(player_target_stats, how='inner', left_on=["playerId"],right_on=["playerId"])
# label encoding
# 辞書形式でplayerIdとprimaryPositionNameとteamIdとstatusのそれぞれの特徴量を保存
player2num = {c: i for i, c in enumerate(train['playerId'].unique())}
position2num = {c: i for i, c in enumerate(train['primaryPositionName'].unique())}
teamid2num = {c: i for i, c in enumerate(train['teamId'].unique())}
status2num = {c: i for i, c in enumerate(train['status'].unique())}
train['label_playerId'] = train['playerId'].map(player2num)
train['label_primaryPositionName'] = train['primaryPositionName'].map(position2num)
train['label_teamId'] = train['teamId'].map(teamid2num)
train['label_status'] = train['status'].map(status2num)
イメージとして、前半の#create datasetではplayerIdとdateをキーとして特徴量を結合し、後半の# label encodingでは適当になっているIDに対して新しくインデックスを割り振っています。
目標変数の確認
先ほどの各特徴量から目標変数target1~4を予測する感じです。
訓練データと検証用データに分割
train_X = train[feature_cols]
train_y = train[['target1', 'target2', 'target3', 'target4']]
_index = (train['date'] < 20210401) # 条件式に合致しているものにはTrue、そうでないものにはFalseが付与されます。
x_train = train_X.loc[_index].reset_index(drop=True)
y_train = train_y.loc[_index].reset_index(drop=True)
x_valid = train_X.loc[~_index].reset_index(drop=True)
y_valid = train_y.loc[~_index].reset_index(drop=True)

こんな感じになります。indexを新しく振り直していることだけ注意です。
LightGBMのモデル構築
def fit_lgbm(x_train, y_train, x_valid, y_valid, params: dict=None, verbose=100):
# 長さy_validの[0, 0, ....を生成]
# 各target1~4に対して予測を行い、最終的にそれらのtargetを結合する
oof_pred = np.zeros(len(y_valid), dtype=np.float32)
model = lgbm.LGBMRegressor(**params)
model.fit(x_train, y_train,
eval_set=[(x_valid, y_valid)],
early_stopping_rounds=verbose,
verbose=verbose)
# 先ほど作った[0,0, ...]に値を格納していく
oof_pred = model.predict(x_valid)
score = mean_absolute_error(oof_pred, y_valid)
print('mae:', score)
return oof_pred, model, score
# training lightgbm
params1 = {'objective':'mae',
'reg_alpha': 0.14947461820098767,
'reg_lambda': 0.10185644384043743,
'n_estimators': 3633,
'learning_rate': 0.08046301304430488,
'num_leaves': 674,
'feature_fraction': 0.9101240539122566,
'bagging_fraction': 0.9884451442950513,
'bagging_freq': 8,
'min_child_samples': 51}
oof1, model1, score1 = fit_lgbm(
x_train, y_train['target1'],
x_valid, y_valid['target1'],
params1
)
params2 = {'objective':'mae',
'reg_alpha': 0.1,
'reg_lambda': 0.1,
'n_estimators': 80,
'learning_rate': 0.1,
'random_state': 42,
"num_leaves": 22}
oof2, model2, score2 = fit_lgbm(
x_train, y_train['target2'],
x_valid, y_valid['target2'],
params2
)
params3 = { 'objective':'mae',
'reg_alpha': 0.1,
'reg_lambda': 0.1,
'n_estimators': 10000,
'learning_rate': 0.1,
'random_state': 42,
"num_leaves": 100}
oof3, model3, score3 = fit_lgbm(
x_train, y_train['target3'],
x_valid, y_valid['target3'],
params3
)
params4 = {'objective':'mae',
'reg_alpha': 0.016468100279441976,
'reg_lambda': 0.09128335764019105,
'n_estimators': 9868,
'learning_rate': 0.10528150510326864,
'num_leaves': 157,
'feature_fraction': 0.5419185713426886,
'bagging_fraction': 0.2637405128936662,
'bagging_freq': 19,
'min_child_samples': 71}
oof4, model4, score4 = fit_lgbm(
x_train, y_train['target4'],
x_valid, y_valid['target4'],
params4
)
score = (score1+score2+score3+score4) / 4
print(f'score: {score}')
元々optunaでモデルを作って多っぽくて、それを適応したためにパラメータの値が複雑になっています。スタッキングによる手法を用いているため、最終的にはscoreがそれぞれの予測値を4で割っています。(勉強になります。)
実際に予測
tensorflowを用いて予測していますが、冒頭部分のコードが難しくて調べているのが大変でした。
import pandas as pd
import numpy as np
from datetime import timedelta
from tqdm import tqdm
import gc
from functools import reduce
from sklearn.model_selection import StratifiedKFold
ROOT_DIR = "mlb-player-digital-engagement-forecasting"
# =======================#
# データ整形(データを並び替える)
# add_prefexはカラム名に追加で名前を付けた感じです。
def flatten(df, col):
du = (df.pivot(index="playerId", columns="EvalDate",
values=col).add_prefix(f"{col}_").
rename_axis(None, axis=1).reset_index())
return du
# ============================#
# playerIdに基づいてleftにrightを結合
def reducer(left, right):
return left.merge(right, on="playerId")
# ========================
TGTCOLS = ["target1","target2","target3","target4"]
def train_lag(df, lag=1):
# .copy()で値渡し
# ["playerId", "EvalDate"], TGTCOLSのカラムを指定
dp = df[["playerId","EvalDate"]+TGTCOLS].copy()
dp["EvalDate"] = dp["EvalDate"] + timedelta(days=lag)
# suffixes ... カラムに接尾辞を指定してデータフレームを連結
df = df.merge(dp, on=["playerId", "EvalDate"], suffixes=["",f"_{lag}"], how="left")
return df
# =================================
def test_lag(sub):
sub["playerId"] = sub["date_playerId"].apply(lambda s: int( s.split("_")[1] ) )
assert sub.date.nunique() == 1
dte = sub["date"].unique()[0]
eval_dt = pd.to_datetime(dte, format="%Y%m%d")
dtes = [eval_dt + timedelta(days=-k) for k in LAGS]
mp_dtes = {eval_dt + timedelta(days=-k):k for k in LAGS}
sl = LAST.loc[LAST.EvalDate.between(dtes[-1], dtes[0]), ["EvalDate","playerId"]+TGTCOLS].copy()
sl["EvalDate"] = sl["EvalDate"].map(mp_dtes)
du = [flatten(sl, col) for col in TGTCOLS]
du = reduce(reducer, du)
return du, eval_dt
#
# ===============
tr = pd.read_csv("target.csv")
print(tr.shape)
gc.collect()
tr["EvalDate"] = pd.to_datetime(tr["EvalDate"])
# dateを一つ前にずらす
tr["EvalDate"] = tr["EvalDate"] + timedelta(days=-1)
# 2018-...の2018の部分だけを取り出す
tr["EvalYear"] = tr["EvalDate"].dt.year
MED_DF = tr.groupby(["playerId","EvalYear"])[TGTCOLS].median().reset_index()
MEDCOLS = ["tgt1_med","tgt2_med", "tgt3_med", "tgt4_med"]
# 中央値をとったのでカラム名を変える
MED_DF.columns = ["playerId","EvalYear"] + MEDCOLS
# FECOLSは4×20 = 80個のリスト
LAGS = list(range(1,21))
# 'target4_19', reversedはリストを逆順にする
FECOLS = [f"{col}_{lag}" for lag in reversed(LAGS) for col in TGTCOLS]
for lag in tqdm(LAGS):
tr = train_lag(tr, lag=lag)
gc.collect()
# ===========
tr = tr.sort_values(by=["playerId", "EvalDate"])
print(tr.shape)
tr = tr.dropna()
print(tr.shape)
tr = tr.merge(MED_DF, on=["playerId","EvalYear"])
gc.collect()
X = tr[FECOLS+MEDCOLS].values
y = tr[TGTCOLS].values
cl = tr["playerId"].values
NFOLDS = 6
skf = StratifiedKFold(n_splits=NFOLDS)
folds = skf.split(X, cl) # 訓練データと検証データのインデックスが入ったラベル
folds = list(folds)
import tensorflow as tf
import tensorflow.keras.layers as L
import tensorflow.keras.models as M
from sklearn.metrics import mean_absolute_error, mean_squared_error
from tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau, EarlyStopping
tf.random.set_seed(777)
# 今回はラグを20としたモデルを作ってます。
def make_model(n_in):
inp = L.Input(name="inputs", shape=(n_in,))
x = L.Dense(50, activation="relu", name="d1")(inp)
x = L.Dense(50, activation="relu", name="d2")(x)
preds = L.Dense(4, activation="linear", name="preds")(x)
model = M.Model(inp, preds, name="ANN")
model.compile(loss="mean_absolute_error", optimizer="adam")
return model
net = make_model(X.shape[1])
print(net.summary())
# callbacks 訓練中にモデル内部の状態と統計量を可視化
oof = np.zeros(y.shape)
nets = []
for idx in range(NFOLDS):
print("FOLD:", idx)
tr_idx, val_idx = folds[idx]
ckpt = ModelCheckpoint(f"w{idx}.h5", monitor='val_loss', verbose=1, save_best_only=True,mode='min')
"""
ModelCheckpoint 引数
・filepath : 重みファイル名そのものを指示
・moitor : 監視する値(acc, loss, val_acc, val_loss)
・verbose : 0 = 結果非表示, 1 = 結果表示
・save_best_only True...判定結果から保存を決定。False...periodの間隔で保存
・model...判定条件 max...「acc, val_acc」, min... [loss, val_loss]
・save_weigth_only... True= モデルの重みが保存。False= モデル全体を保存
・period... 何エポックごとに保存するか
"""
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.2,patience=3, min_lr=0.0005)
"""
ReduceLROnPlateau
https://keras.io/api/callbacks/reduce_lr_on_plateau/
監視する評価値、何エポック改善しなかったら学習率を落とすか、その変化の割合、最小値などを指定すると、
学習の進みに応じて調整
・factor : 学習率が減少する割合 new_factor = lr * factor
・patience : 学習率が向上しない回数
・min_lr : 学習率の最低ライン
"""
es = EarlyStopping(monitor='val_loss', patience=6)
reg = make_model(X.shape[1])
reg.fit(X[tr_idx], y[tr_idx], epochs=10, batch_size=35_000, validation_data=(X[val_idx], y[val_idx]),
verbose=1, callbacks=[ckpt, reduce_lr, es])
reg.load_weights(f"w{idx}.h5")
oof[val_idx] = reg.predict(X[val_idx], batch_size=50_000, verbose=1)
nets.append(reg)
gc.collect()
#
#
mae = mean_absolute_error(y, oof)
mse = mean_squared_error(y, oof, squared=False)
print("mae:", mae)
print("mse:", mse)
# Historical information to use in prediction time
bound_dt = pd.to_datetime("2021-01-01")
LAST = tr.loc[tr.EvalDate>bound_dt].copy()
LAST_MED_DF = MED_DF.loc[MED_DF.EvalYear==2021].copy()
LAST_MED_DF.drop("EvalYear", axis=1, inplace=True)
del tr
# """
# import mlb
FE = []; SUB = [];
gc.collect()はガベージコレクションという不要になったメモリ領域を自動的に開放する操作を行ってるらしいです。
trは以下のようになっています
データの提出
import copy
env = mlb.make_env() # initialize the environment
iter_test = env.iter_test() # iterator which loops over each date in test set
for (test_df, sample_prediction_df) in iter_test: # make predictions here
sub = copy.deepcopy(sample_prediction_df.reset_index())
sample_prediction_df = copy.deepcopy(sample_prediction_df.reset_index(drop=True))
# LGBM summit
# creat dataset
sample_prediction_df['playerId'] = sample_prediction_df['date_playerId']\
.map(lambda x: int(x.split('_')[1]))
# Dealing with missing values
if test_df['rosters'].iloc[0] == test_df['rosters'].iloc[0]:
test_rosters = pd.DataFrame(eval(test_df['rosters'].iloc[0]))
else:
test_rosters = pd.DataFrame({'playerId': sample_prediction_df['playerId']})
for col in rosters.columns:
if col == 'playerId': continue
test_rosters[col] = np.nan
if test_df['playerBoxScores'].iloc[0] == test_df['playerBoxScores'].iloc[0]:
test_scores = pd.DataFrame(eval(test_df['playerBoxScores'].iloc[0]))
else:
test_scores = pd.DataFrame({'playerId': sample_prediction_df['playerId']})
for col in scores.columns:
if col == 'playerId': continue
test_scores[col] = np.nan
test_scores = test_scores.groupby('playerId').sum().reset_index()
test = sample_prediction_df[['playerId']].copy()
test = test.merge(players[players_cols], on='playerId', how='left')
test = test.merge(test_rosters[rosters_cols], on='playerId', how='left')
test = test.merge(test_scores[scores_cols], on='playerId', how='left')
test = test.merge(player_target_stats, how='inner', left_on=["playerId"],right_on=["playerId"])
test['label_playerId'] = test['playerId'].map(player2num)
test['label_primaryPositionName'] = test['primaryPositionName'].map(position2num)
test['label_teamId'] = test['teamId'].map(teamid2num)
test['label_status'] = test['status'].map(status2num)
test_X = test[feature_cols]
# predict
pred1 = model1.predict(test_X)
pred2 = model2.predict(test_X)
pred3 = model3.predict(test_X)
pred4 = model4.predict(test_X)
# merge submission
sample_prediction_df['target1'] = np.clip(pred1, 0, 100)
sample_prediction_df['target2'] = np.clip(pred2, 0, 100)
sample_prediction_df['target3'] = np.clip(pred3, 0, 100)
sample_prediction_df['target4'] = np.clip(pred4, 0, 100)
sample_prediction_df = sample_prediction_df.fillna(0.)
del sample_prediction_df['playerId']
# TF summit
# Features computation at Evaluation Date
sub_fe, eval_dt = test_lag(sub)
sub_fe = sub_fe.merge(LAST_MED_DF, on="playerId", how="left")
sub_fe = sub_fe.fillna(0.)
_preds = 0.
for reg in nets:
_preds += reg.predict(sub_fe[FECOLS + MEDCOLS]) / NFOLDS
sub_fe[TGTCOLS] = np.clip(_preds, 0, 100)
sub.drop(["date"]+TGTCOLS, axis=1, inplace=True)
sub = sub.merge(sub_fe[["playerId"]+TGTCOLS], on="playerId", how="left")
sub.drop("playerId", axis=1, inplace=True)
sub = sub.fillna(0.)
# Blending
blend = pd.concat(
[sub[['date_playerId']],
(0.1*sub.drop('date_playerId', axis=1) + 0.9*sample_prediction_df.drop('date_playerId', axis=1))],
axis=1
)
env.predict(blend)
# Update Available information
sub_fe["EvalDate"] = eval_dt
#sub_fe.drop(MEDCOLS, axis=1, inplace=True)
LAST = LAST.append(sub_fe)
LAST = LAST.drop_duplicates(subset=["EvalDate","playerId"], keep="last")
Kaggle上ではmlbモジュールについての説明が簡潔になされています。
おわりに
コンペやりながら書いてたのですが、Your Notebook tried to allocate more memory than available. It has been restarted.というエラーを解決するのが難しくてしばらく席を離れようと思います....