😛

signate: 初心者が2013年の4大テニストーナメントの試合詳細情報から、試合の結果を予測するモデルを作成

に公開2

目的

  • 前回のタイタニック二値分類に引き続き、二値分類問題をもう一度やることで知識を定着させる。また、精度改善にも取り組む。
  • 前回はコードが見にくくなってしまったので、クラスを使ってみる。

コンペ概要

  • 今回参加したコンペ

    https://signate.jp/competitions/118

  • データ概要
    課題種別:分類
    データ種別:多変量
    学習データサンプル数:471
    説明変数の数:45
    欠損値:あり

まずは提出パート

仮説立案

特徴量の説明

No. カラム名 データ型 説明
0 id int インデックスとして使用
1 Tournament varchar 大会名(テニス四大大会)
2 Sex varchar 性別
3 Year int 年度(yyyy)
4 Player 1 varchar プレーヤー1の名前
5 Player 2 varchar プレーヤー2の名前
6 Result int 試合の結果(Player1勝利=1, Player2勝利=0)
7 FSP.1 int プレーヤー1のファーストサーブの割合(%)
8 FSW.1 int プレーヤー1がファーストサーブで勝った割合(%)
9 SSP.1 int プレーヤー1のセカンドサーブの割合(%)
10 SSW.1 int プレーヤー1がセカンドサーブで勝った割合(%)
11 ACE.1 int プレーヤー1のサービスエース数
12 DBF.1 int プレーヤー1のダブルフォルト数
13 WNR.1 int プレーヤー1が獲得したウィナー数
14 UFE.1 int プレーヤー1のアンフォーストエラー数
15 BPC.1 int プレーヤー1のブレークポイント創出数
16 BPW.1 int プレーヤー1のブレーク成功数
17 NPA.1 int プレーヤー1のネットポイント試行数
18 NPW.1 int プレーヤー1のネットポイント獲得数
19 TPW.1 int プレーヤー1の総獲得ポイント数
20 ST1.1 int プレーヤー1の第1セットゲーム数
21 ST2.1 int プレーヤー1の第2セットゲーム数
22 ST3.1 int プレーヤー1の第3セットゲーム数
23 ST4.1 int プレーヤー1の第4セットゲーム数
24 ST5.1 int プレーヤー1の第5セットゲーム数
25 FNL.1 int プレーヤー1の最終セット数
26 FSP.2 int プレーヤー2のファーストサーブの割合(%)
27 FSW.2 int プレーヤー2がファーストサーブで勝った割合(%)
28 SSP.2 int プレーヤー2のセカンドサーブの割合(%)
29 SSW.2 int プレーヤー2がセカンドサーブで勝った割合(%)
30 ACE.2 int プレーヤー2のサービスエース数
31 DBF.2 int プレーヤー2のダブルフォルト数
32 WNR.2 int プレーヤー2が獲得したウィナー数
33 UFE.2 int プレーヤー2のアンフォーストエラー数
34 BPC.2 int プレーヤー2のブレークポイント創出数
35 BPW.2 int プレーヤー2のブレーク成功数
36 NPA.2 int プレーヤー2のネットポイント試行数
37 NPW.2 int プレーヤー2のネットポイント獲得数
38 TPW.2 int プレーヤー2の総獲得ポイント数
39 ST1.2 int プレーヤー2の第1セットゲーム数
40 ST2.2 int プレーヤー2の第2セットゲーム数
41 ST3.2 int プレーヤー2の第3セットゲーム数
42 ST4.2 int プレーヤー2の第4セットゲーム数
43 ST5.2 int プレーヤー2の第5セットゲーム数
44 FNL.2 int プレーヤー2の最終セット数
45 Round int 試合が行われたトーナメントラウンド

勝敗に影響しそうな特徴量をリストアップ

  1. サーブ成功率(FSP, FSW, SSP, SSW)、エース数(ACE)、ダブルフォルト(DBF)、ウィナー(WNR)、アンフォーストエラー(UFE)、ブレークポイント(BPC, BPW)、ネットポイント(NPA, NPW)、総獲得ポイント(TPW)などは、テニスの勝敗に直結しやすい。

  2. セットごとのゲーム数や最終セット数(ST1〜ST5, FNL)は、試合展開を反映するが、試合結果と強く相関するため、リーク(情報漏洩)になる。

    → 予測モデルには「試合前に分かる特徴量」や「試合中の途中経過」だけを使う。

一連の流れを先に確認(初期)

クラスについて

疎結合にしているが、基本的にはメソッドをインスタンス化して順番通りに実行していけば問題ない。
例:
preprocess():データの前処理(欠損値補完など)
split_data():訓練・テストデータに分割
train_model():ロジスティック回帰モデルの学習
evaluate_model():モデル評価
create_submission():提出ファイル作成

import pandas as pd 
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

class TennisMatchData:
    def __init__(self, train_file, test_file):
        self.train = pd.read_csv(train_file, delimiter='\t')
        self.test = pd.read_csv(test_file, delimiter='\t')
        self.df = pd.concat([self.train, self.test], axis=0, ignore_index=True)
        self.features = [
            'FSP.1', 'FSW.1', 'SSP.1', 'SSW.1', "FSP.2", "FSW.2", "SSP.2", "SSW.2",
            'ACE.1', 'ACE.2',
            'DBF.1', 'DBF.2',
            'WNR.1', 'WNR.2',
            'UFE.1', 'UFE.2',
            'BPC.1', 'BPC.2', 'BPW.1', 'BPW.2',
            'NPA.1', 'NPA.2', 'NPW.1', 'NPW.2',
            'TPW.1', 'TPW.2'
        ]
        self.target = 'Result'
        self.X = None
        self.y = None
    # 前処理全般
    def preprocess(self):
        self.df = self.df.drop(columns=['id'])
        self.X = self.df[self.features]    
        # ターゲット変数の設定
        self.y = self.df[self.target]

        # 欠損値処理
        self.X.fillna(self.X.mean(), inplace=True)
        self.y.fillna(self.y.mean(), inplace=True)
        
        # 交差項を作成
        from sklearn.preprocessing import PolynomialFeatures
        poly = PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
        self.X = poly.fit_transform(self.X)
        self.X = pd.DataFrame(self.X, columns=poly.get_feature_names_out(self.features))

        # スケーリング
        from sklearn.preprocessing import StandardScaler
        scaler = StandardScaler()
        self.X = scaler.fit_transform(self.X)
        self.X = pd.DataFrame(self.X, columns=poly.get_feature_names_out(self.features))
        

    def split_data(self):
        X_train = self.X.iloc[:len(self.train), :]
        X_test = self.X.iloc[len(self.train):, :]
        y_train = self.y.iloc[:len(self.train)]
        y_test = self.y.iloc[len(self.train):]
        
        return X_train, X_test, y_train, y_test
    # trainデータのみを学習用・検証用に分割
    def split_train_valid(self, X_train, y_train):
        from sklearn.model_selection import train_test_split
        return train_test_split(X_train, y_train, test_size=0.2, random_state=42)

    # ロジスティック回帰モデルの学習

    def train_model(self, X_train, y_train):
        from sklearn.linear_model import LogisticRegression
        model = LogisticRegression()
        model.fit(X_train, y_train)
        return model
    # LightGBMモデルの学習
    def train_lightgbm_model(self, X_train, y_train):
        import lightgbm as lgb
        model = lgb.LGBMClassifier()
        model.fit(X_train, y_train)
        return model
    
    # アンサンブル
    def ensemble_models(self, models, X_valid):
        from sklearn.ensemble import VotingClassifier
        voting_model = VotingClassifier(estimators=[('lr', models[0]), ('lgbm', models[1])], voting='soft')
        voting_model.fit(X_valid, y_valid)
        return voting_model
    # 評価用データでモデルを評価
    def evaluate_model(self, model, X_valid, y_valid):
        from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
        y_pred = model.predict(X_valid)
        
        display("Accuracy:", accuracy_score(y_valid, y_pred))
        display("Classification Report:\n", classification_report(y_valid, y_pred))
        display("Confusion Matrix:\n", confusion_matrix(y_valid, y_pred))

    def create_submission(self, model, X_test, test_ids):
        y_test_pred = model.predict(X_test)
        submit = pd.DataFrame({
            'id': test_ids,
            'Result': y_test_pred.astype(int)
        })
        submit.to_csv('submit.csv', index=False, header=False)
        print("Submission file created: submit.csv")




実際に学習してみる

if __name__ == "__main__":
    tennis_data = TennisMatchData('train.csv', 'test.csv')
    tennis_data.preprocess()
    X_train, X_test, y_train, y_test = tennis_data.split_data()
    
    # 学習用・検証用データに分割
    X_train, X_valid, y_train, y_valid = tennis_data.split_train_valid(X_train, y_train)
    
    # モデルの学習
    model_lr = tennis_data.train_model(X_train, y_train)
    model_lgbm = tennis_data.train_lightgbm_model(X_train, y_train)
    
    # アンサンブル
    voting_model = tennis_data.ensemble_models([model_lr, model_lgbm], X_valid)
    
    # モデルの評価
    tennis_data.evaluate_model(voting_model, X_valid, y_valid)
    
    # 提出ファイルの作成
    tennis_data.create_submission(voting_model, X_test, tennis_data.test['id'])

精度改善パート

評価項目で問題発生

  • 'Accuracy:'1.0'
  • 'Confusion Matrix:'
    array([[42, 0],
    [ 0, 53]])
    と、評価がどの指標を見ても1.0となっており、過学習(過剰適合)が疑える結果となった。

対策

  • 「試合前に分かる特徴量」だけを使う
    TPW(総獲得ポイント)、BPW(ブレーク成功数)、NPW(ネットポイント獲得数)など「結果に直結する特徴量」は除外
  • 使用する特徴量
    サーブ成功率やサーブで勝った割合
    FSP.1, FSW.1, SSP.1, SSW.1, FSP.2, FSW.2, SSP.2, SSW.2
    エース数、ダブルフォルト数、ウィナー数、アンフォーストエラー数
    ACE.1, DBF.1, WNR.1, UFE.1, ACE.2, DBF.2, WNR.2, UFE.2
    ブレークポイント、ネットポイント、総獲得ポイント
    BPC.1, BPW.1, NPA.1, NPW.1, TPW.1, BPC.2, BPW.2, NPA.2, NPW.2, TPW.2
    セットごとのゲーム数や最終セット数
    ST1.1, ST2.1, ST3.1, ST4.1, ST5.1, FNL.1, ST1.2, ST2.2, ST3.2, ST4.2, ST5.2, FNL.2
    その他、Tournament, Sex, Year, Player1, Player2, Round などの基本情報
  • 交差項を減らす/使わない
    まずは元の特徴量だけにする
# 上記すべての流れをデータクラスを用いて疎結合にするコードを作る
class TennisMatchData:
    def __init__(self, train_file, test_file):
        self.train = pd.read_csv(train_file, delimiter='\t')
        self.test = pd.read_csv(test_file, delimiter='\t')
        self.df = pd.concat([self.train, self.test], axis=0, ignore_index=True)
        self.features = [
    'FSP.1', 'FSW.1', 'SSP.1', 'SSW.1',  # サーブ率
    'FSP.2', 'FSW.2', 'SSP.2', 'SSW.2',
    'ACE.1', 'ACE.2',                     # エース数
    'DBF.1', 'DBF.2',                     # ダブルフォルト
    'WNR.1', 'WNR.2',                     # ウィナー数
    'UFE.1', 'UFE.2',                     # アンフォーストエラー
    'BPC.1', 'BPC.2',                     # ブレークポイント創出数
    'NPA.1', 'NPA.2',                     # ネットポイント試行数
    'NPW.1', 'NPW.2',                     # ネットポイント獲得数
    # ↓以下は除外(リークの可能性が高い)
    # 'TPW.1', 'TPW.2', 'BPW.1', 'BPW.2', 'ST1.1'〜'FNL.2'
        ]
        self.target = 'Result'
        self.X = None
        self.y = None

- 以下にコードを修正
    def preprocess(self):
        self.df = self.df.drop(columns=['id'])
        self.X = self.df[self.features]    
        # ターゲット変数の設定
        self.y = self.df[self.target]


        # 数値変数のみ平均値で補完
        self.X = self.X.fillna(self.X.mean())
        self.y = self.y.fillna(self.y.mean())

       
        
        # 交差項を作成
        #from sklearn.preprocessing import PolynomialFeatures
        #poly = PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
        #self.X = poly.fit_transform(self.X)
        #self.X = pd.DataFrame(self.X, columns=poly.get_feature_names_out(self.features))

        # スケーリング
        from sklearn.preprocessing import StandardScaler
        scaler = StandardScaler()
        self.X = scaler.fit_transform(self.X)
        self.X = pd.DataFrame(self.X, columns=scaler.feature_names_in_)
        
    def split_data(self):
        X_train = self.X.iloc[:len(self.train), :]
        X_test = self.X.iloc[len(self.train):, :]
        y_train = self.y.iloc[:len(self.train)]
        y_test = self.y.iloc[len(self.train):]
        
        return X_train, X_test, y_train, y_test
    # trainデータのみを学習用・検証用に分割
    def split_train_valid(self, X_train, y_train):
        from sklearn.model_selection import train_test_split
        return train_test_split(X_train, y_train, test_size=0.2, random_state=42)

    # ロジスティック回帰モデルの学習

    def train_model(self, X_train, y_train):
        from sklearn.linear_model import LogisticRegression
        model = LogisticRegression()
        model.fit(X_train, y_train)
        return model
    # LightGBMモデルの学習
    def train_lightgbm_model(self, X_train, y_train):
        import lightgbm as lgb
        model = lgb.LGBMClassifier()
        model.fit(X_train, y_train)
        return model
    
    # アンサンブル
    def ensemble_models(self, models, X_train, y_train):
        from sklearn.ensemble import VotingClassifier
        voting_model = VotingClassifier(estimators=[('lr', models[0]), ('lgbm', models[1])], voting='soft')
        voting_model.fit(X_train, y_train)
        return voting_model

    def evaluate_model(self, model, X_valid, y_valid):
        from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
        y_pred = model.predict(X_valid)
        
        display("Accuracy:", accuracy_score(y_valid, y_pred))
        display("Classification Report:\n", classification_report(y_valid, y_pred))
        display("Confusion Matrix:\n", confusion_matrix(y_valid, y_pred))

    def create_submission(self, model, X_test, test_ids):
        y_test_pred = model.predict(X_test)
        submit = pd.DataFrame({
            'id': test_ids,
            'Result': y_test_pred.astype(int)
        })
        submit.to_csv('submit.csv', index=False, header=False)
        print("Submission file created: submit.csv")





→しかし、これらを試しても一向に治らず。。

結論

  • アンサンブル時に渡していたデータが評価用データだったので、ゴリゴリリークを起こしていた。。。

以下のように修正

# アンサンブル
    def ensemble_models(self, models, X_train, y_train):
        from sklearn.ensemble import VotingClassifier
        voting_model = VotingClassifier(estimators=[('lr', models[0]), ('lgbm', models[1])], voting='soft')
        voting_model.fit(X_train, y_train)
        return voting_model
 # アンサンブル
    voting_model = tennis_data.ensemble_models([model_lr, model_lgbm], X_train, y_train)

'Accuracy:'0.9052631578947369'
'Confusion Matrix:
([[38, 4],
[ 5, 48]])

ようやく正常の範囲内になりました!リークの恐ろしさをを身をもって体感。

特徴量エンジニアリング

特徴量エンジニアリングは、モデルがより多くの有用な情報を学習できるように新しい特徴量を作成・加工すること

  1. 差分・比率特徴量の作成
    各指標の差分
    例:FSP_diff = FSP.1 - FSP.2(ファーストサーブ率の差)
    各指標の比率
    例:ACE_ratio = ACE.1 / (ACE.2 + 1)(エース数の比率、0除算防止で+1)
  2. 選手ごとの特徴量の集約
    例:Player1やPlayer2ごとに平均サーブ率や勝率などを集計し、特徴量として追加
# 選手ごとの平均値を計算
player1_stats = train.groupby('Player 1')[['FSP.1', 'ACE.1', 'WNR.1', 'UFE.1']].mean().add_prefix('P1_avg_') 
player2_stats = train.groupby('Player 2')[['FSP.2', 'ACE.2', 'WNR.2', 'UFE.2']].mean().add_prefix('P2_avg_')
# 元データに選手ごとの平均値をマージ
self.df = self.df.merge(player1_stats, left_on='Player 1', right_index=True, how='left')
self.df = self.df.merge(player2_stats, left_on='Player 2', right_index=True, how='left')

アンサンブルの手法を変更

現状はVotingClassifierを採用しているが、tackingClassifierに変更。

  • VotingClassifier
    複数のモデルの予測結果を「投票」して最終予測を決める手法。
    voting='soft'の場合は、各モデルの「予測確率の平均」をとって一番高いクラスを選ぶ。
    重み付きアンサンブルも可能で、weights引数で各モデルの重要度を調整できる。
  • tackingClassifier
    各モデルの予測結果を「新たな特徴量」として、さらに別のモデル(メタモデル)で最終予測を行う手法。
    Votingよりも複雑な関係を学習できるため、精度が上がることがある。
# StackingClassifier(スタッキングアンサンブル)
    def ensemble_models(self, models, X_train, y_train):
        from sklearn.ensemble import StackingClassifier
        from sklearn.linear_model import LogisticRegression
        
        # メタモデルとしてロジスティック回帰を使用
        meta_model = LogisticRegression(max_iter=1000, random_state=42)
        
        # スタッキングアンサンブルの作成
        stacking_model = StackingClassifier(
            estimators=[(f'model_{i}', model) for i, model in enumerate(models)],
            final_estimator=meta_model,
            cv=5
        )

結果:Cross-Validation Accuracy (cv=5): 0.9263 ± 0.0421
一番高そう!

ハイパーパラメータチューニング

今回はなし

最終的なコードと感想

class TennisMatchData:
    def __init__(self, train_file, test_file):
        import pandas as pd 
        import numpy as np
        import matplotlib.pyplot as plt
        import seaborn as sns
        self.train = pd.read_csv(train_file, delimiter='\t')
        self.test = pd.read_csv(test_file, delimiter='\t')
        self.df = pd.concat([self.train, self.test], axis=0, ignore_index=True)
        
        ## 特徴量エンジニアリング
       
        # 選手ごとの平均値を計算し、元データにマージ 
        # # 1. 選手ごとの平均値を計算
        player1_stats = self.train.groupby('Player1')[['FSP.1', 'ACE.1', 'WNR.1', 'UFE.1']].mean().add_prefix('P1_avg_') 
        player2_stats = self.train.groupby('Player2')[['FSP.2', 'ACE.2', 'WNR.2', 'UFE.2']].mean().add_prefix('P2_avg_')
        # 元データに選手ごとの平均値をマージ
        self.df = self.df.merge(player1_stats, left_on='Player1', right_index=True, how='left')
        self.df = self.df.merge(player2_stats, left_on='Player2', right_index=True, how='left')
        
        
        # 差分特徴量
        self.df['FSP_diff'] = self.df['FSP.1'] - self.df['FSP.2'] # サーブ成功率の差
        # 比率特徴量    
        # エース数の比率
        self.df['ACE_ratio'] = self.df['ACE.1'] / (self.df['ACE.2'] + 1)  # ゼロ除算を避けるために1を加える

       
        self.features = [
        'FSP.1', 'FSW.1', 'SSP.1', 'SSW.1',  # サーブ率
        'FSP.2', 'FSW.2', 'SSP.2', 'SSW.2',
        'ACE.1', 'ACE.2',                     # エース数
        'DBF.1', 'DBF.2',                     # ダブルフォルト
        'WNR.1', 'WNR.2',                     # ウィナー数
        'UFE.1', 'UFE.2',                     # アンフォーストエラー
        'BPC.1', 'BPC.2',                     # ブレークポイント創出数
        'NPA.1', 'NPA.2',                     # ネットポイント試行数
        'NPW.1', 'NPW.2',                     # ネットポイント獲得数
        'P1_avg_FSP.1', 'P1_avg_ACE.1', 'P1_avg_WNR.1', 'P1_avg_UFE.1','P2_avg_FSP.2', 'P2_avg_ACE.2', 'P2_avg_WNR.2', 'P2_avg_UFE.2',# 選手ごとの平均値
        'FSP_diff', 'ACE_ratio',  # 差分特徴量
       
        # ↓以下は除外(リークの可能性が高い)
        # 'TPW.1', 'TPW.2', 'BPW.1', 'BPW.2', 'ST1.1'〜'FNL.2'
            ]
        self.target = 'Result'
        self.X = None
        self.y = None

    def preprocess(self):
        self.df = self.df.drop(columns=['id'])
        self.X = self.df[self.features]    
        # ターゲット変数の設定
        self.y = self.df[self.target]


        # 数値変数のみ平均値で補完
        self.X = self.X.fillna(self.X.mean())
        self.y = self.y.fillna(self.y.mean())

       
        
        # 交差項を作成
        #from sklearn.preprocessing import PolynomialFeatures
        #poly = PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
        #self.X = poly.fit_transform(self.X)
        #self.X = pd.DataFrame(self.X, columns=poly.get_feature_names_out(self.features))

        # スケーリング
        from sklearn.preprocessing import StandardScaler
        scaler = StandardScaler()
        self.X = scaler.fit_transform(self.X)
        self.X = pd.DataFrame(self.X, columns=scaler.feature_names_in_)
        
    def split_data(self):
        X_train = self.X.iloc[:len(self.train), :]
        X_test = self.X.iloc[len(self.train):, :]
        y_train = self.y.iloc[:len(self.train)]
        y_test = self.y.iloc[len(self.train):]
        
        return X_train, X_test, y_train, y_test
    # trainデータのみを学習用・検証用に分割
    def split_train_valid(self, X_train, y_train):
        from sklearn.model_selection import train_test_split
        return train_test_split(X_train, y_train, test_size=0.2, random_state=42)

    # XGBoostモデルの学習
    def train_xgboost_model(self, X_train, y_train):
        import xgboost as xgb
        model = xgb.XGBClassifier(n_estimators=100, random_state=42)
        model.fit(X_train, y_train)
        return model

    # ロジスティック回帰モデルの学習
    def train_logistic_regression_model(self, X_train, y_train):
        from sklearn.linear_model import LogisticRegression
        model = LogisticRegression(max_iter=1000, random_state=42)
        model.fit(X_train, y_train)
        return model
    
    # ランダムフォレストモデルの学習
    def train_random_forest_model(self, X_train, y_train):
        from sklearn.ensemble import RandomForestClassifier
        model = RandomForestClassifier(n_estimators=100, random_state=42)
        model.fit(X_train, y_train)
        return model
    # LightGBMモデルの学習
    def train_lightgbm_model(self, X_train, y_train):
        import lightgbm as lgb
        model = lgb.LGBMClassifier(n_estimators=100, random_state=42)
        model.fit(X_train, y_train)
        return model
    
    #  StackingClassifier(スタッキングアンサンブル)
    def ensemble_models(self, models, X_train, y_train):
        from sklearn.ensemble import StackingClassifier
        from sklearn.linear_model import LogisticRegression
        
        # メタモデルとしてロジスティック回帰を使用
        meta_model = LogisticRegression(max_iter=1000, random_state=42)
        
        # スタッキングアンサンブルの作成
        stacking_model = StackingClassifier(
            estimators=[(f'model_{i}', model) for i, model in enumerate(models)],
            final_estimator=meta_model,
            cv=5
        )
        
        stacking_model.fit(X_train, y_train)
        return stacking_model
    # クロスバリデーションでモデルの評価
    def evaluate_model_cv(self, model, X, y, cv=5):
        from sklearn.model_selection import cross_val_score
        scores = cross_val_score(model, X, y, cv=cv, scoring='accuracy')
        print(f"Cross-Validation Accuracy (cv={cv}): {scores.mean():.4f} ± {scores.std():.4f}")

    def create_submission(self, model, X_test, test_ids):
        y_test_pred = model.predict(X_test)
        submit = pd.DataFrame({
            'id': test_ids,
            'Result': y_test_pred.astype(int)
        })
        submit.to_csv('submit.csv', index=False, header=False)
        print("Submission file created: submit.csv")

感想

  • 今回は様々な手法で、ミニマムな提出から精度改善につなげることを目的にした。そこは達成できたのでよかった。
  • 一方で、一番重要な部分である特徴量エンジニアリングへの理解はふわふわしたままなので、書籍などから手法を学びたい。
  • データ分析は手段でしかないので、仮説を立ててそれを証明する流れをもっと大切にするべきだった。行き当たりばったりの修正が多くなってしまったように思う。

今後の目標・課題

ここまで二値分類のコンペのみなので、時系列データ予測などの別のコンペに挑戦したい。また、テーブルデータが複数あるコンペもあるときいたのでSQLの勉強もしたい。

参考記事・書籍

https://qiita.com/hidenari_uoi/items/5ca118df37fee0e72574
https://www.oreilly.co.jp/books/9784873117980/

ヘッドウォータース

Discussion

せとぅせとぅ

お話してからの行動力とスピードが圧巻でした。お疲れ様でした☕️

ひろゆーひろゆー

ありがとうございます!zenn記事出したいとはずっと思っていたのですが、もくもく会で後押しされたのが大きかったです。
今後も勉強内容などの記事を継続して出していきたいと思います!