본문 바로가기

Minding's Baseball/머신러닝으로 홈런왕 예측하기

[MLB 스탯캐스트] 머신러닝으로 MLB 타자들의 최종 홈런 성적 예측해보기 - 3. 데이터 재전처리하여 예측

728x90
반응형

https://github.com/JeongMinHyeok/Handling_MLB_Statcast

(predict_HR 폴더)

 

GitHub - JeongMinHyeok/Handling_MLB_Statcast

Contribute to JeongMinHyeok/Handling_MLB_Statcast development by creating an account on GitHub.

github.com

 

이전 발행글 (최초 전처리 및 모델링 / 예측)

MLB 스탯캐스트] 머신러닝으로 MLB 타자들의 최종 홈런 성적 예측해보기 - 1. EDA & Data Engineering

 

[MLB 스탯캐스트] 머신러닝으로 MLB 타자들의 최종 홈런 성적 예측해보기 - 1. EDA & Data Engineering

머신러닝 코드 및 데이터자료 (아래 링크의 Predict_HR 폴더) https://github.com/JeongMinHyeok/Handling_MLB_Statcast GitHub - JeongMinHyeok/Handling_MLB_Statcast Contribute to JeongMinHyeok/Handling_MLB..

minding-deep-learning.tistory.com

[MLB 스탯캐스트] 머신러닝으로 MLB 타자들의 최종 홈런 성적 예측해보기 - 2. Modeling & Prediction

 

[MLB 스탯캐스트] 머신러닝으로 MLB 타자들의 최종 홈런 성적 예측해보기 - 2. Modeling & Prediction

작성 코드 및 데이터 (Github, Predict_HR폴더) : https://github.com/JeongMinHyeok/Handling_MLB_Statcast GitHub - JeongMinHyeok/Handling_MLB_Statcast Contribute to JeongMinHyeok/Handling_MLB_Statcast d..

minding-deep-learning.tistory.com

 


저번 실험의 데이터 전처리 과정에서 이상치를 제거할 목적으로 무리하게 칼럼을 제거했더니,

오타니, 블게주, 타티스주니어 등 많은 홈런을 치고 있는 타자들의 홈런 갯수가 예상보다 훨씬 적게 예측되는 경우가 있었다.

 

이번에는 데이터 전처리로 인한 칼럼 삭제를 최소화하여 그 결과를 다시 한번 알아보려고 한다.

전처리과정은 이전 과정과 동일하지만, VIF 수치 비교를 통해 다중공산성을 해결하고자 하는 파트를 제외하였다.

사실상 타겟(홈런 수)만 로그변환 한 상태로 모델링 및 예측을 한 것이라, 모델링 부분부터 설명하도록 하겠다.

 

학습모델링

# 사용할 모델 import
from sklearn.model_selection import KFold, cross_val_score, GridSearchCV
from sklearn.metrics import mean_squared_error
from sklearn.pipeline import make_pipeline
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from xgboost import XGBRegressor
from lightgbm import LGBMRegressor
from sklearn.linear_model import ElasticNet, Lasso, LinearRegression

 

RobustScaler

  • 중앙값과 IQR 사용하여 아웃라이어의 영향 최소화
from sklearn.preprocessing import RobustScaler

rbst_scaler=RobustScaler()
X_rbst=rbst_scaler.fit_transform(x_train)
test_rbst=rbst_scaler.transform(x_test)

 

KFold 검정

  • KFold 검정 사용하여 앙상블모델에 사용할 모델 선정
kfold = KFold(n_splits=4)

random_state = 1
reg = []

reg.append(Lasso(random_state = random_state))
reg.append(ElasticNet(random_state = random_state))
reg.append(RandomForestRegressor(random_state=random_state))
reg.append(GradientBoostingRegressor(random_state=random_state))
reg.append(XGBRegressor(silent=True,random_state=random_state))
reg.append(LGBMRegressor(verbose_eval=False,random_state = random_state))

# print(reg)

reg_results = []

for regre in reg :
    reg_results.append(np.mean(np.sqrt(-cross_val_score(regre, X_rbst, y = y_data ,scoring = 'neg_mean_squared_error',
                                       cv = kfold, n_jobs=-4))))
                                       
reg_means = []
reg_std = []
for reg_result in reg_results:
    reg_means.append(reg_result.mean())
    reg_std.append(reg_result.std())
# 위에서부터 Lasso, ElasticNet, RandomForest, GradientBoosting, XGBoost, LGBM에 대한 교차검증결과
# 평균이 낮을수록 좋음

reg_re = pd.DataFrame({"CrossValMeans":reg_means,"CrossValerrors": reg_std})
reg_re

위에서부터 Lasso, ElasticNet, RandomForest, GradientBoosting, XGBoost, LGBM에 대한 교차검증결과

이전 실험과는 다르게 RandomForest 대신 XGBoost를 사용한다.

  • CrossValMeans 확인 : GradeintBoosting, XGBoost, LightGBM 모델에 대해 파라미터 튜닝 결정
# Gradient boosting 파라미터 튜닝
GBC = GradientBoostingRegressor()
gb_param_grid = {'n_estimators' : [30,50,100],
              'learning_rate': [0.1, 0.01, 0.2],
              'max_depth': [3, 4, 5],
              'min_samples_leaf': [20,30,40],
              'max_features': [0.3, 0.2, 0.15] 
              }
gsGBC = GridSearchCV(GBC,param_grid = gb_param_grid, cv=kfold, scoring="neg_mean_squared_error", n_jobs= 4, verbose = 1)
gsGBC.fit(X_rbst,y_data)
GBC_best = gsGBC.best_estimator_

# 최고 점수
gsGBC.best_score_
# XGBoost 파라미터 튜닝
XGB = XGBRegressor()
xgb_param_grid = {'learning_rate': [1,0.1,0.01],
             'n_estimators': [50, 100, 200],
             'max_depth' : [3,5,10],
             'subsample': [0.6, 0.7, 1.0],
             'colsample_bytree' : [0.3,0.5,0.7],
             'scale_pos_weight' : [0.5,1],
             'reg_alpha': [0,0.05,0.0005]
              }
gsXGB = GridSearchCV(XGB,param_grid = xgb_param_grid, cv=kfold, scoring="neg_mean_squared_error", n_jobs= 4, verbose = 1)
gsXGB.fit(X_rbst,y_data)
XGB_best = gsXGB.best_estimator_

# 최고 점수
gsXGB.best_score_

XGBoost 파라미터 튜닝에 엄청 많은 시간이 소요되었다... 5832개의 경우의 수를 돌리는데 4197분의 시간소요...

#LGBMClassifier 파라미터 튜닝
LGB = LGBMRegressor()
lgb_param_grid = {
    'n_estimators' : [30, 50, 70],
    'learning_rate': [0.1],
    'max_depth': [5, 10, 15],
    'num_leaves': [10, 30, 50],
    'min_split_gain': [0.1, 0.2, 0.3],
}
gsLGB = GridSearchCV(LGB,param_grid = lgb_param_grid, cv=kfold, scoring="neg_mean_squared_error", n_jobs= 4, verbose = 1)
gsLGB.fit(X_rbst,y_data)
LGB_best = gsLGB.best_estimator_

# 최고 점수
gsLGB.best_score_

 

예측

  • 앙상블
test_Survived_GBC = pd.Series(GBC_best.predict(test_rbst), name="GBC")
test_Survived_XGB = pd.Series(XGB_best.predict(test_rbst), name="XGB")
test_Survived_LGB = pd.Series(LGB_best.predict(test_rbst), name="LGB")

ensemble_results = pd.concat([test_Survived_XGB,test_Survived_LGB,
                              test_Survived_GBC],axis=1)
g= sns.heatmap(ensemble_results.corr(),annot=True)

ensemble = np.expm1(0.1*test_Survived_GBC + 0.8*test_Survived_XGB + 0.1*test_Survived_LGB)
prediction = pd.DataFrame({
    "player_id" :player_id,
    "name" : name,
    "HR": ensemble
})
prediction.head()

 

  • 보팅 (Voting)
from sklearn.ensemble import VotingRegressor

votingC = VotingRegressor(estimators=[('XGB', XGB_best), ('LGB', LGB_best), ('GBC',GBC_best)], n_jobs=4)
votingC = votingC.fit(X_rbst, y_data)

test_HR = pd.Series(votingC.predict(test_rbst), name="HR")

predict_voting = pd.DataFrame({
    "player_id" :player_id,
    "name" : name,
    "HR": np.expm1(test_HR)
})
predict_voting.head()

 

  • 스태킹(stacking)
from mlxtend.regressor import StackingRegressor
from sklearn.linear_model import LogisticRegression
from sklearn.utils.testing import ignore_warnings


params = {'meta_regressor__C': [0.1, 1.0, 10.0, 100.0],
          'use_features_in_secondary' : [True, False]}
clf1 = XGB_best
clf2 = LGB_best
clf3 = GBC_best

lr = LogisticRegression()
st_re= StackingRegressor(regressors=[clf1, clf2, clf3], meta_regressor=RandomForestRegressor())
st_mod = st_re.fit(X_rbst, y_data)
st_pred = st_mod.predict(test_rbst)

predict_stacking = pd.DataFrame({
    "player_id" :player_id,
    "name" : name,
    "HR": np.expm1(st_pred)
})
predict_stacking.head()

 

이전과 비교하여 타자들 간의 격차가 벌어지고, 홈런타자들의 예상 홈런 갯수가 크게 늘어난 것을 알 수 있다.

예측 홈런 갯수와 현재 홈런 갯수를 합친 표도 출력해보았다. (예측갯수별로 sorting)

 

  • 앙상블

  • 보팅

  • 스태킹

 

세 앙상블 모델 모두 오타니가 홈런 1위가 될 것이라 예측했다.

앙상블, 보팅, 스태킹 각각 약 47개, 약 45개, 약 48개의 홈런을 칠 것이라 예측하였다.

그 다음으로는 페르난도 타티스주니어, 라파엘 데버스가 뒤를 이었다. 모두 40개 이상의 홈런을 칠 것이라 예측했다.

 

블라디미르 게레로 주니어가 생각보다 낮은 순위와 갯수(약 39개)의 예측이 나왔는데,

이는 다른 타자들보다 타율 등 교타자로써의 능력에 있어서도 강점을 보이는 타자이기에 조금 낮은 예측값을 가진 것이라 생각한다.

 

이전 실험에는 오타니가 홈런 40개를 겨우 넘기는 등 아쉬운 숫자가 나오기도 했는데,

이번 실험에서는 그래도 납득할만한 결과가 나온 것 같다. (오타니는 50개 이상 칠 것이라고 보이긴 하지만...)

데이터가 좀 더 풍부하고, 홈런예측에 쓰일만한 칼럼도 더 많았으면 정확한 결과가 나오지 않을까 하는 생각과

이상치를 없애기 위해 데이터를 제외시키는 것이 양날의 검 같은 특성을 가지고 있구나... 라는 생각도 들었다.

 

이 예측치가 얼마나 정확할지는 MLB시즌이 끝나봐야 알 것이다.

그 때까지 좀 더 공부하고 노력해서 다른 예측실험에서도 좋은 결과가 있도록 해야겠다.

 

다음 실험은 아마 투수와 관련된 실험을 하지 않을까 싶다. 평균자책점, 승리 등등... 투수 또한 흥미로운 실험 재료들이 많다.

특히 승리 같은 경우는 타자들의 득점지원도 영향을 주는 요소이기에, 꽤 복잡한 실험이 될 것 같다라는 생각도 든다.

 

추가로, 한창 도쿄올림픽이 진행 중이다.

프로야구가 코로나19와 음주사태 등 여러 불미스러운 일로 팬들에게 실망만 안겨주고 있는데,

도쿄올림픽에서 좋은 모습을 보여 팬들의 마음을 조금이나마 달래줬으면 좋겠다.

(물론, 도쿄올림픽에서 금메달을 따와도 이전에 잘못한게 용서되는 것은 아니다...)

한일전이 바로 내일인데, 적어도 이 경기만큼은 이겼으면 좋겠다...!

728x90