[2024.11.08] 필수 온라인 강의 Part16 Machine Learning Advanced CH08 실전 프로젝트
캐글 대회에서 사용하는 팁 (이론)
실험하기 전 고려할 사항들
- 디버깅
- 프로그래밍에서 오류를 찾아내고 수정하는 과정
- 머신러닝 프로세스의 경우 한번 실행시 오래 걸리는 경우가 많아서 돌린 후 에러를 확인해서 수정하면 시간 소요가 큼
디버깅모드
- 실험 환경이 잘 설정되었는지 체크하기 위한 과정
- 다양한 방식으로 프로세스를 축소하여 체크
- 방법 1. 샘플 데이터
- 방법 2. 작은 모델
- 방법 3. 하이퍼파라미터 축소 - 시드 고정
- 시드 : 난수 생성기의 초기값으로 사용되는 값
- 시드가 고정이 안된 경우 : 같은 코드에서 다른 점수가 발생 (재현에 실패)
- 논문 및 모든 실험에서는 공정한 비교를 위해 재현이 되어야함
- 고정 방법 : 필요한 모든 부분에 seed (=42)를 고정 - 실험 기록
- Notion, Google Spreadsheet 등 다양한 툴을 활용하여 성능을 기록
- 남들이 보여주기 위한 : 팀원들과 동일하게 진행X, 서로 인사이트를 얻기
- 포트폴리오 만들기
- 하나의 요소 실험 vs 여러 요소 동시에 실험
- 일반적으로 조건을 하나씩 변경해가면서 실험하는 것을 추천
- 성능과 자원의 트레이드 오프 관계가 존재하지만, 어떤 조건이 성능에 영향을 주었는지 파악하기 용이
앙상블(Ensemble) 기법
- 여러 모델을 결합하는 방법
- 예시 KFold Ensemble
- 모델의 조건(하이퍼파라미터)은 동일하지만 학습 데이터를 다르게 해서 학습한 K개의 모델들의 결과를 평균
- 같은 모델링을 쓰지만, 데이터를 다르게 함 - Model측면의 Ensemble -> 거의 필수적, 성능을 높이는데 가장 중요함
- 모델을 다르게 해서 결합
- 1. 서로 달라야한다(적용후에는 상관관계로 서로 다른 것을 사용하는 게 좋음
- 2. 기본 베이스 성능 보장
- 3. w1값을 검증을 통해서 찾기도 함(hyperparameter)
- 모델 측면이 떨어지면, 서로 장단점을 합치는 게 좋음.
- 서로가 잘하는 영역을 뽑아내서 사용하는 게 좋음 ->
- e.g. LightGBM + Catboost / Random Forest = Decision Tree + Feature Selection / Linear Regression+Boosting 등등 - Stacking
- 모델의 예측 결과를 피처로 활용하는 방법
- 대회에서의 솔루션으로는 정말 큰 역할을 함
- 각 모델 별로 khold를 구해서, 각각의 결과에 얼마나 나왔는지 new feature로 활용.
- 배포에는 어려움이 있지만, 성능을 높이는 방법이긴 하다
- Seed Ensemble
- 시드만 동일하게 해서 앙상블
- 시드를 의도적으로 바꾸기도 함
- 랜덤화 노후에 오는 위험성을 피하는데도 많이 사용을 함.
- Private 모르는 경우 (미래 데이터 안정성)
추가 데이터
- Pseudo Labeling
- 데이터 셋에 가짜 (Pseudo) 레이블을 부여하는 방법
- 캐글 및 대회에서는 평가 데이터셋에 레이블을 부여해서 학습에 활용하는 기법
학습과정
- 1. 일반적인 모델의 학습 진행
- 2. 위의 모델로 평가 데이터셋에 대해 예측
- 3. 예측한 평가 데이터셋과 학습 데이터셋을 결합 ***
- 4. 3의 결과를 기반으로 새로운 모델 학습 - 외부 데이터
- Pseudo Labeling을 주로 활용
- https://www.kaggle.com/competitions/hubmap-kidney-segmentation/discussion/238198
분석 도구
- EDA 자동화 도구
- Dataprep.eda : 데이터의 분석을 자동화 해주는 도구 - 시각화 도구
- Weights & Biases (WanDB) : 기록 & 분석 시각화 도구
- Adversarial Validation : 기록 & 분석 시각화 도구
캐글 대회에서 사용하는 팁 (실전)
- Yahoo Finance OHLCV 데이터셋 사용
디버깅 및 재현성 확보 과정
# .sample을 통해서 데이터의 1% 만큼만 샘플링을 합니다.
if DEBUG_MODE:
display(f"샘플링 전 데이터의 비율 : {X_train.shape}")
X_train = X_train.sample(frac=0.01) # 원래는 _X_train 대신 X_train 으로 선언해야 해당 쉘 이후 코드가 문제 없이 돌아갑니다. 다만, 실습 자료에서는 이후 원할한 실습을 위해서 샘플링 전 원본 데이터로 진행하겠습니다.
display(f"샘플링 후 데이터의 비율 : {_X_train.shape}")
- 재현성 확보
# 확보 전 : 돌릴때마다 달라지는 것을 확인할 수 있음
for i in range(5):
display(f"시도 {i}번째")
display(np.random.choice([1,2,3,4,5]))
display(np.random.choice([1,2,3,4,5]))
display(np.random.choice([1,2,3,4,5]))
# 확보 후 : [4,5,3] 계속 반복되는 것을 확인할 수 있음
for i in range(5):
display(f"시도 {i}번째")
np.random.seed(42)
display(np.random.choice([1,2,3,4,5]))
display(np.random.choice([1,2,3,4,5]))
display(np.random.choice([1,2,3,4,5]))
# 일반적으로 대표적으로 고정하는 부분은 아래와 같습니다.
seed = 42
random.seed(seed)
np.random.seed(seed)
os.environ["PYTHONHASHSEED"] = str(seed)
# 만약 파이토치를 사용하는 경우
# torch.manual_seed(seed)
# torch.cuda.manual_seed(seed)
# torch.backends.cudnn.deterministic = True
# torch.backends.cudnn.benchmark = True
다양한 모델 앙상블 과정
1. 데이터 측면
- Kfold를 이용
- 앙상블 하기 전
# 베이스라인 용 데이터 셋 생성
holdout_X_train = X_train.copy()
holdout_Y_train = Y_train.copy()
# 날짜를 기준으로 최근 한 달의 데이터를 검증 데이터로 설정
holdout_X_train['Date'] = pd.to_datetime(holdout_X_train['Date'])
validation_start_date = holdout_X_train['Date'].max() - pd.Timedelta(days=30)
validation_indices = holdout_X_train[holdout_X_train['Date'] >= validation_start_date].index
# 위에서 얻은 인덱스를 사용하여 holdout_X_train 및 holdout_Y_train을 훈련 및 검증 데이터로 분리
holdout_X_valid = holdout_X_train.loc[validation_indices]
# 검증기간의 인덱스는 제외
holdout_X_train.drop(validation_indices, inplace=True)
holdout_Y_valid = holdout_Y_train[validation_indices]
# 검증기간의 인덱스는 제외
holdout_Y_train.drop(validation_indices, inplace=True)
display(f"수정된 학습 데이터의 기간: {holdout_X_train['Date'].min()} {holdout_X_train['Date'].max()}")
display(f"수정된 검증 데이터의 기간: {holdout_X_valid['Date'].min()} {holdout_X_valid['Date'].max()}")
# Gradient Boosting Machine을 선언
# 학습은 총 1,000번을 반복
gbm = lgb.LGBMRegressor(n_estimators=1000, random_state=42, subsample=0.7, subsample_freq=1)
# 학습을 진행
# 비교를 위해 %%time을 이용해 학습시간을 측정
# 다만, date변수의 경우 바로 학습에 사용할 수 없으니 제외
%%time
features = [c for c in holdout_X_train.columns if c not in ["Date"]]
gbm.fit(
holdout_X_train[features],
holdout_Y_train, # 학습 데이터를 입력
eval_set=[
(holdout_X_train[features], holdout_Y_train),
(holdout_X_valid[features], holdout_Y_valid)
], # 평가셋을 지정합니다.
eval_metric ='rmse', # 평가과정에서 사용할 평가함수를 지정
callbacks=[
lgb.early_stopping(stopping_rounds=10), # 10번의 성능향상이 없을 경우, 학습중지.
lgb.log_evaluation(period=10, show_stdv=True)
] # 매 iteration마다 학습결과를 출력
)
# Test set에 대한 rmse를 측정
predicts = gbm.predict(X_test[features])
RMSE = mean_squared_error(Y_test, predicts)**0.5
display(f"Test rmse : {RMSE}")
- khold 앙상블 과정
# 학습 데이터를 StratifiedKFoldSplit로 나눕니다.
%%time
n_folds = 5
kf = StratifiedKFold(n_splits=5)
cut_Y_train = pd.cut(Y_train,
1000, # 데이터를 최소 최대 구간으로 1000등분 합니다.
labels=False)
train_folds = kf.split(X_train, cut_Y_train)
display(train_folds)
total_predicts = np.zeros(len(X_test))
total_oofs = np.zeros(len(X_train)) # array. [0, 0, ...]
for fold_idx, (train_idx, valid_idx) in enumerate(train_folds):
display(f"--------{fold_idx}번째 fold의 학습을 시작합니다.--------")
# index를 통해 fold의 학습세트를 가져옵니다.
kfold_X_train = X_train.iloc[train_idx, :][features]
kfold_Y_train = Y_train[train_idx]
# index를 통해 fold의 평가세트를 가져옵니다.
kfold_X_valid = X_train.iloc[valid_idx, :][features]
kfold_Y_valid = Y_train[valid_idx]
# fold의 데이터로 학습을 진행합니다.
gbm = lgb.LGBMRegressor(n_estimators=1000, random_state=42, subsample=0.7)
gbm.fit(
kfold_X_train,
kfold_Y_train, # 학습 데이터를 입력합니다.
eval_set=[
(kfold_X_train, kfold_Y_train),
(kfold_X_valid, kfold_Y_valid)
], # 평가셋을 지정합니다.
eval_metric ='rmse', # 평가과정에서 사용할 평가함수를 지정합니다.
callbacks=[
lgb.early_stopping(stopping_rounds=10), # 10번의 성능향상이 없을 경우, 학습을 멈춥니다.
lgb.log_evaluation(period=10, show_stdv=True)
] # 매 iteration마다 학습결과를 출력합니다.
)
fold_predicts = gbm.predict(X_test[features])
total_oofs[valid_idx] = gbm.predict(kfold_X_valid)
# 각 fold의 rmse를 측정합니다.
RMSE = mean_squared_error(Y_test, fold_predicts)**0.5
display(f"Fold {fold_idx} - Test rmse : {RMSE}")
total_predicts += fold_predicts / n_folds
RMSE = mean_squared_error(Y_train, total_oofs)**0.5
display(f"최종 Valid rmse : {RMSE}")
RMSE = mean_squared_error(Y_test, total_predicts)**0.5
display(f"최종 Test rmse : {RMSE}")
앙상블 전후에 따른 성능을 비교
Training Time : 13.7s ---> 104s ( 속도는 오래 걸림)
Valid RMSE : 3658
Test RMSE : 3457.82 ---> 3327 (성능면에서는 개선)
- Filter methods
def get_highly_correlated_features(df, threshold=0.8):
"""
높은 상관계수를 가진 변수 쌍 중에서 한 변수를 제외한 나머지 변수들의 이름을 반환합니다.
Parameters:
- df: 데이터프레임
- threshold: 상관계수 임계값
Returns:
- 선택된 변수명 리스트
"""
# 상관계수 행렬 계산
# [[1, 0.5, 0.3], [0.5, 1, ..], [...]]
correlation_matrix = df.corr()
# 제거 대상 변수들을 저장할 집합
to_remove = set()
for i in range(correlation_matrix.shape[0]):
for j in range(i + 1, correlation_matrix.shape[1]):
if abs(correlation_matrix.iloc[i, j]) > threshold:
to_remove.add(correlation_matrix.columns[j])
# 제거 대상 변수를 제외하고 남은 변수들의 리스트 반환
selected_features = [col for col in df.columns if col not in to_remove]
return selected_features
features1 = get_highly_correlated_features(X_train[features], threshold=0.7)
display("Filter Method : ", features1)
- Feature Importance
def get_important_features(model, X_train, Y_train, threshold=0.8, upper=True):
"""
Feature Importance를 사용하여 변수 중요도 상위 80%의 변수명을 반환합니다.
Parameters:
- model: 중요도를 추출한 모델
- X_train: 학습 데이터의 독립변수
- Y_train: 학습 데이터의 종속변수
- threshold: 중요도 비율 (0.8은 상위 80%를 의미)
Returns:
- 선택된 변수명 리스트
"""
# 변수 중요도와 변수명을 함께 저장
feature_importances = list(zip(X_train.columns, model.feature_importances_))
# 변수 중요도를 기준으로 정렬
sorted_features = sorted(feature_importances, key=lambda x: x[1], reverse=True if upper else False)
# 상위 threshold(예: 80%) 비율에 해당하는 변수의 수
num_features_to_keep = int(threshold * len(sorted_features))
# 상위 threshold(예: 80%)의 변수명 선택
selected_features = [feature[0] for feature in sorted_features[:num_features_to_keep]]
return selected_features
%%time
# Forward Feature Selection
forest_rf = RandomForestRegressor(
n_estimators=50,
criterion='squared_error',
random_state=seed,
n_jobs=-1
) # 지표는 squared_error로 설정합니다.
forest_rf.fit(X_train[features], Y_train)
features2 = get_important_features(forest_rf, X_train[features], Y_train, threshold=0.7)
display("Feature Importance Method : ", features2)
- Adversarial Validation
# train은 0, test는 1로 라벨 변수를 설정합니다.
adv_X_train = holdout_X_train.copy()
adv_X_valid = holdout_X_valid.copy()
adv_X_train['AV_label'] = 0
adv_X_valid['AV_label'] = 1
# 위 두 데이터를 합치고, 셔플합니다.
adv_data = pd.concat([adv_X_train, adv_X_valid], axis=0, ignore_index=True)
adv_data_shuffled = adv_data.sample(frac=1)
adv_X = adv_data_shuffled.drop(['AV_label'], axis=1)
adv_y = adv_data_shuffled['AV_label']
%%time
# Forward Feature Selection
forest_rf = RandomForestClassifier(
n_estimators=50,
criterion='log_loss', # log_loss
random_state=seed,
n_jobs=-1
)
forest_rf.fit(adv_X[features], adv_y)
features3 = get_important_features(forest_rf, adv_X[features], adv_y, threshold=0.7, upper=False)
display(f"Adversarial Method : {features3}")
# 학습 및 예측을 위한 함수
def train_and_predict(X_train, Y_train, X_valid, Y_valid, X_test, features, random_state):
gbm = lgb.LGBMRegressor(
n_estimators=1000,
random_state=random_state,
subsample=0.7,
subsample_freq=1
)
gbm.fit(X_train[features], Y_train,
eval_set=[(X_train[features], Y_train), (X_valid[features], Y_valid)],
eval_metric='rmse',
callbacks=[lgb.early_stopping(stopping_rounds=10),
lgb.log_evaluation(period=10, show_stdv=True)]
)
return gbm.predict(X_test[features])
all_preds = []
for selected_features in [features1, features2, features3]:
preds = train_and_predict(
holdout_X_train,
holdout_Y_train,
holdout_X_valid,
holdout_Y_valid,
X_test,
selected_features,
42
)
RMSE = mean_squared_error(Y_test, preds)**0.5
display(f"Test rmse : {RMSE}")
all_preds.append(preds)
# 모든 예측값의 평균을 계산
ensemble_preds = np.mean(all_preds, axis=0)
# 앙상블의 RMSE를 측정
RMSE = mean_squared_error(Y_test, ensemble_preds)**0.5
display(f"Feature Selection Ensemble Test RMSE : {RMSE
# 모든 예측값의 평균을 계산
ensemble_preds = np.mean(all_preds[1:], axis=0)
# 앙상블의 RMSE를 측정
RMSE = mean_squared_error(Y_test, ensemble_preds)**0.5
display(f"Feature Selection Ensemble Test RMSE : {RMSE}")
피처 셀력선 전후에 따른 성능을 비교.
Valid RMSE : 4166, 2839, 2801
Test RMSE : 4410, 3417, 3377 ---> 3435
Test RMSE : 3417, 3377 ---> 3384
모델 측면의 앙상블 기법
# 1. oofs 생성 부분 추가
# 2. 10개의 다른 seed 사용
oofs = np.zeros(len(X_train))
seeds = [i for i in range(10)] # [0~9]
all_oofs = []
all_total_predicts = []
RMSES = []
for seed in seeds:
kf = StratifiedKFold(n_splits=5)
cut_Y_train = pd.cut(Y_train,
1000, # 데이터를 최소 최대 구간으로 1000등분 합니다.
labels=False)
train_folds = kf.split(X_train, cut_Y_train)
seed_oofs = np.zeros(len(X_train))
seed_predicts = np.zeros(len(X_test))
for fold_idx, (train_idx, valid_idx) in enumerate(train_folds):
display(f"-------- Seed {seed}, Fold {fold_idx} --------")
kfold_X_train = X_train.iloc[train_idx, :][features]
kfold_Y_train = Y_train[train_idx]
kfold_X_valid = X_train.iloc[valid_idx, :][features]
kfold_Y_valid = Y_train[valid_idx]
gbm = lgb.LGBMRegressor(
n_estimators=1000,
random_state=seed,
subsample=0.7,
subsample_freq=1
)
gbm.fit(kfold_X_train, kfold_Y_train,
eval_set=[(kfold_X_train, kfold_Y_train), (kfold_X_valid, kfold_Y_valid)],
eval_metric='rmse',
callbacks=[lgb.early_stopping(stopping_rounds=10),
lgb.log_evaluation(period=10, show_stdv=True)]
)
fold_predicts = gbm.predict(X_test[features])
seed_oofs[valid_idx] = gbm.predict(kfold_X_valid)
seed_predicts += fold_predicts / n_folds
# RMSE를 측정
RMSE = mean_squared_error(Y_test, seed_predicts)**0.5
display(f"Seed {seed} Ensemble Test RMSE : {RMSE}")
all_oofs.append(seed_oofs)
all_total_predicts.append(seed_predicts)
RMSES += [RMSE]
display("RMSE")
display(RMSES)
kf = StratifiedKFold(n_splits=5)
cut_Y_train = pd.cut(Y_train,
1000, # 데이터를 최소 최대 구간으로 1000등분 합니다.
labels=False)
train_folds = kf.split(X_train, cut_Y_train)
# 3. Stacking 모델 구현
stacking_train = np.column_stack(all_oofs)
stacking_test = np.column_stack(all_total_predicts)
final_predicts = np.zeros(len(X_test))
for fold_idx, (train_idx, valid_idx) in enumerate(train_folds):
kfold_X_train = stacking_train[train_idx, :]
kfold_Y_train = Y_train[train_idx]
kfold_X_valid = stacking_train[valid_idx, :]
kfold_Y_valid = Y_train[valid_idx]
gbm = lgb.LGBMRegressor(
n_estimators=1000,
random_state=seed,
subsample=0.7,
subsample_freq=1
)
gbm.fit(kfold_X_train,
kfold_Y_train,
eval_set=[
(kfold_X_train, kfold_Y_train),
(kfold_X_valid, kfold_Y_valid)
],
eval_metric='rmse',
callbacks=[
lgb.early_stopping(stopping_rounds=10),
lgb.log_evaluation(period=10, show_stdv=True)
]
)
fold_predicts = gbm.predict(stacking_test)
final_predicts += fold_predicts
final_predicts = final_predicts / n_folds
RMSE = mean_squared_error(Y_test, final_predicts)**0.5
display(f"Stacking Model Test RMSE: {RMSE}")
Holdout 학습의 최종결과.
Training Time : 13.7s
Valid RMSE : 2966.63
Test RMSE : 3457.82
Stacking 학습의 최종결과
Seed별 RMSE : 3274, 3342, 3347, 3244, 3232, 3337, 3293, 3293, 3249, 3294
Test RMSE : 3869.9008310979557
kf = StratifiedKFold(n_splits=5)
cut_Y_train = pd.cut(Y_train,
1000, # 데이터를 최소 최대 구간으로 1000등분 합니다.
labels=False)
train_folds = kf.split(X_train, cut_Y_train)
# 3. Stacking 모델 구현
stacking_train = np.column_stack(all_oofs)
stacking_train = pd.concat([pd.DataFrame(stacking_train), X_train[features]], axis=1).astype(float)
stacking_test = np.column_stack(all_total_predicts)
stacking_test = pd.concat([pd.DataFrame(stacking_test), X_test[features]], axis=1).astype(float)
final_predicts = np.zeros(len(X_test))
for fold_idx, (train_idx, valid_idx) in enumerate(train_folds):
kfold_X_train = stacking_train.iloc[train_idx, :]
kfold_Y_train = Y_train[train_idx]
kfold_X_valid = stacking_train.iloc[valid_idx, :]
kfold_Y_valid = Y_train[valid_idx]
gbm = lgb.LGBMRegressor(
n_estimators=1000,
random_state=seed,
subsample=0.7,
subsample_freq=1
)
gbm.fit(kfold_X_train,
kfold_Y_train,
eval_set=[
(kfold_X_train, kfold_Y_train),
(kfold_X_valid, kfold_Y_valid)
],
eval_metric='rmse',
callbacks=[
lgb.early_stopping(stopping_rounds=10),
lgb.log_evaluation(period=10, show_stdv=True)
]
)
fold_predicts = gbm.predict(stacking_test)
final_predicts += fold_predicts
final_predicts = final_predicts / n_folds
RMSE = mean_squared_error(Y_test, final_predicts)**0.5
display(f"Stacking Model Test RMSE: {RMSE}")
Holdout 학습의 최종결과
Training Time : 13.7s
Valid RMSE : 2966.63
Test RMSE : 3457.82
Stacking 학습의 최종결과
Seed별 RMSE : 3274, 3342, 3347, 3244, 3232, 3337, 3293, 3293, 3249, 3294
Test RMSE : 3869.9008310979557 ---> 3285.084274013325
랜덤성 측면
# random_state 목록
random_states = list(range(10))
# 각 random_state로 모델을 학습시키고 예측값을 저장
all_preds = []
RMSES = []
for rs in random_states:
preds = train_and_predict(holdout_X_train, holdout_Y_train, holdout_X_valid, holdout_Y_valid, X_test, features, rs)
RMSE = mean_squared_error(Y_test, preds)**0.5
display(f"SEED {rs} Test rmse : {RMSE}")
all_preds.append(preds)
RMSES += [RMSE]
# 모든 예측값의 평균을 계산
ensemble_preds = np.mean(all_preds, axis=0)
# 앙상블의 RMSE를 측정
RMSE = mean_squared_error(Y_test, ensemble_preds)**0.5
display(RMSES)
display(f"Seed Ensemble Test RMSE : {RMSE}")
Holdout 학습의 최종결과
Training Time : 13.7s
Valid RMSE : 2966.63
Test RMSE : 3457.82
Seed Ensemble 학습의 최종결과
Training Time : 13.7s * 10
Seed RMSE : 3428, 3438, 3396, 3418, 3388, 3381, 3474, 3523, 3429, 3509
Test RMSE : 3262
'Study > 머신러닝' 카테고리의 다른 글
Deep Learning 모델 학습법 (0) | 2024.12.13 |
---|---|
Deep Learning 기본 개념 (5) | 2024.12.10 |
머신러닝 Advanced_ 데이터 셋 분할 (1) | 2024.11.08 |
머신러닝 Advanced_ 심화 ML 모델 (3) | 2024.11.06 |
머신러닝 Advanced_ 기본 ML 모델 (3) | 2024.11.06 |