目指せ"Another" バトル優勝!
cha_kabu
初投稿となります。不備や分かりにくい点等あればコメントでご指摘いただけますと幸いです。通常、Target Encodingは一列に対して行われますが、本コンペはA1-B4のプレイヤーはランダムにマッチングされます(A1は厳密にはランダムではありませんが、簡素化のため実装含め他プレイヤーと同等に扱います)。例えば極端な話、同じブキでも、A2に割り振られたあるブキを使用するプレイヤーの勝率はたまたま100%であるゆえに"1"に置き換えられるかも知れませんが、A3の同じブキでは"0"になる可能性もあります。A2プレイヤーとA3プレイヤーはランダムに決まるため「A2プレイヤーがあるブキを使うと強いが、A3が使うと弱い」などということはないはずなのに、該当ブキに対して本来以上/以下の評価となってしまう可能性があります。このトピックでは、(冗長なコードかも知れませんが…)A1-B4の全ての列を使用したTarget Encodingを行います。
lobby-mode+ブキ毎の勝率を求めることを目標に、一度A1~B4の列を縦長にし、プレイヤーに関わらないlobby-mode+ブキ毎の勝率を一度別のDataFrameにまとめ、trainデータとtestデータに結合します。最後に、おまけとしてモデルを使わずにこの統計データだけで予測を作成し提出します。なお、今回の実装においてtrainデータに勝率を結合することは意味がありませんが、testと同様に結合してしまうとモデルに組み込む際にはリークが発生してしまうので、参考までにご紹介します。
# ライブラリのインポート import warnings import pandas as pd from sklearn.model_selection import KFold warnings.filterwarnings('ignore')
# データの読み込み train = pd.read_csv("train_data .csv") test = pd.read_csv("test_data .csv")
# lobby-mode毎/ブキ毎の勝率をまとめたDataFrameを返す関数の定義 def make_win_rate(df): # 縦に繋げる準備 A1_df = pd.DataFrame(df[["lobby-mode", "A1-weapon", "y"]]) A2_df = pd.DataFrame(df[["lobby-mode", "A2-weapon", "y"]]) A3_df = pd.DataFrame(df[["lobby-mode", "A3-weapon", "y"]]) A4_df = pd.DataFrame(df[["lobby-mode", "A4-weapon", "y"]]) B1_df = pd.DataFrame(df[["lobby-mode", "B1-weapon", "y"]]) B2_df = pd.DataFrame(df[["lobby-mode", "B2-weapon", "y"]]) B3_df = pd.DataFrame(df[["lobby-mode", "B3-weapon", "y"]]) B4_df = pd.DataFrame(df[["lobby-mode", "B4-weapon", "y"]]) # A/Bに関わらず勝利を1、負けを0にするためにBの勝敗を入れ替える(0→1、1→0へ) B1_df["y"] = (B1_df["y"]+1) % 2 B2_df["y"] = (B2_df["y"]+1) % 2 B3_df["y"] = (B3_df["y"]+1) % 2 B4_df["y"] = (B4_df["y"]+1) % 2 # 列のリネーム A1_df.rename(columns={'A1-weapon': "weapon"}, inplace=True) A2_df.rename(columns={'A2-weapon': "weapon"}, inplace=True) A3_df.rename(columns={'A3-weapon': "weapon"}, inplace=True) A4_df.rename(columns={'A4-weapon': "weapon"}, inplace=True) B1_df.rename(columns={'B1-weapon': "weapon"}, inplace=True) B2_df.rename(columns={'B2-weapon': "weapon"}, inplace=True) B3_df.rename(columns={'B3-weapon': "weapon"}, inplace=True) B4_df.rename(columns={'B4-weapon': "weapon"}, inplace=True) # 縦に連結 df = pd.concat([A1_df, A2_df, A3_df, A4_df, B1_df, B2_df, B3_df, B4_df]).reset_index(drop=True) # nanの勝率も知りたいのでfillna df.fillna("nan", inplace=True) # 後でmapするためにlobby-mode名とブキ名をまとめた列の作成 df["lobby-mode_weapon"] = df["lobby-mode"] + "+" + df["weapon"] # lobby-mode名とブキ名をまとめた列の名称毎に勝率を求める target_mean = df.groupby(["lobby-mode_weapon"])["y"].mean() df["lobby-mode_weapon_win_rate"] = df["lobby-mode_weapon"].map(target_mean) # 必要部分だけをDataFrame化 lobbymode_weapon_win_rate = pd.DataFrame(df.groupby(["lobby-mode_weapon"])[ "lobby-mode_weapon_win_rate"].mean()).reset_index().rename(columns={"lobby-mode_weapon_win_rate": "y"}) return lobbymode_weapon_win_rate
# 定義した関数で作成されるDataFrameの確認 # ブキ140種(nan含む)×lobby-mode2種=280行のDataFrame lobbymode_weapon_win_rate = make_win_rate(train) print(lobbymode_weapon_win_rate.shape) lobbymode_weapon_win_rate.head()
(280, 2)
# trainとtestにもlobby-mode名とブキ名をまとめた列を作成しておく players = ["A1-", "A2-", "A3-", "A4-", "B1-", "B2-", "B3-", "B4-"] for player in players: train["lobby-mode_" + player + "weapon"] = train["lobby-mode"] + "+" + train[player + "weapon"] test["lobby-mode_" + player + "weapon"] = test["lobby-mode"] + "+" + test[player + "weapon"]
# testにmap # testは全trainデータから算出した勝率を使用 lobbymode_weapon_win_rate = make_win_rate(train) lobbymode_weapon_cols = ['lobby-mode_A1-weapon', 'lobby-mode_A2-weapon', 'lobby-mode_A3-weapon', 'lobby-mode_A4-weapon', 'lobby-mode_B1-weapon', 'lobby-mode_B2-weapon', 'lobby-mode_B3-weapon', 'lobby-mode_B4-weapon'] for c in lobbymode_weapon_cols: target_mean = lobbymode_weapon_win_rate.groupby(["lobby-mode_weapon"])["y"].mean() test[c + "_win_rate"] = test[c].map(target_mean)
# trainにmap # trainは全データを使用した勝率を結合してしまうとモデルに組み込んだ際にリークしてしまうのでFoldで区切る # この実装上は必要のない処理 # Foldで区切りながら都度勝率のDataFrameを作成しFoldのindex毎に値を書き換えたいので、先に結果を格納する列を作成しておく rate_cols = ['lobby-mode_A1-weapon_win_rate', 'lobby-mode_A2-weapon_win_rate', 'lobby-mode_A3-weapon_win_rate', 'lobby-mode_A4-weapon_win_rate', 'lobby-mode_B1-weapon_win_rate', 'lobby-mode_B2-weapon_win_rate', 'lobby-mode_B3-weapon_win_rate', 'lobby-mode_B4-weapon_win_rate'] for c in rate_cols: train[c] = np.nan # Foldで区切りながら都度勝率のDaraFrameを作成し、リークしないようにmap # モデルを学習する際の分割数、seedも同じものにする kf = KFold(n_splits=5, shuffle=True, random_state=0) for idx_1, idx_2 in kf.split(train): lobbymode_weapon_win_rate = make_win_rate(train.iloc[idx_1]) for c in lobbymode_weapon_cols: target_mean = lobbymode_weapon_win_rate.groupby(["lobby-mode_weapon"])["y"].mean() train[c + "_win_rate"][idx_2] = train[c].iloc[idx_2].map(target_mean)
# testに結合した勝率を使用して予測の作成 # A1-A4の勝率とB1-B4の勝率の逆数をすべて掛けてAチームの勝率を求める test["pred"] = test[rate_cols[0]] * test[rate_cols[1]] * test[rate_cols[2]] * test[rate_cols[3]]\ * (1-test[rate_cols[4]]) * (1-test[rate_cols[5]]) * (1-test[rate_cols[6]]) * (1-test[rate_cols[7]]) # predが基準となる0.5**8以上であれば勝ち予測、そうで無ければ負け予測 test["y"] = 0 test["y"][test["pred"] >= 0.5**8] = 1
# 提出用ファイルの作成 # LB:0.534792 pd.DataFrame({"id": range(len(test)), "y": test["y"]}).to_csv("submission.csv", index=False)