반응형

대회가 끝이 났다. 사실 끝이 난지는 좀 됐는데, 차일피일 미루다가 코드 리뷰를 이제야 쓰게 되었다. 

총 699팀 참가 신청, 315팀 제출, 그 중에서 17위. 처음으로 Top 10% 안에 들어서 랭킹 포인트...를 받았다!

결론을 말하자면, 내가 처음으로 랭킹 포인트를 받은 대회라서 기억에 오래 남을 것 같다. 캐글에 도전하면 세계의 고수들한테 매일같이 얻어터지고(?) 돌아오던 나였는데, 처음으로 상금은 아니지만 대회 보상으로 무언가를 받았다! 대회 끝나고, Private 리더보드가 공개되자마자 인스타에 기쁨의 절규글을 올리던 기억이 아직도 난다. 

 

생체 광학 데이터 분석 AI 경진대회

출처 : DACON - Data Science Competition

dacon.io

데이콘에서 무슨 이벤트? 라고 해서 후드집업이었나 를 준다 해서 바로 올렸지만, 블로그에 사적으로 코드를 올리는 것은 (내 코드여도) 뭔가 문제 될 수도 있을 것 같아서 혼자 겁먹고 기다리다가 드디어 쓴다. (약관 안 읽어보고 회원가입하다가 뒤통수 맞는 그런 느낌적인 느낌..?)

나름 도메인 지식을 찾아본다고 하긴 했는데, 다른 사람들의 코드를 보니까 좀 더 공부를 많이 해 볼걸 그랬다. 더이상 떠오르는 게 없어서 다짜고짜 스태킹 앙상블부터 시작했는데, 돌아보니까 이게 최고의 시간낭비이며, 점수를 더 올리지 못했던 원인인 것 같다. 'Good models means Good Features!'라는 캐글 짤방을 지나가다가 어디서 본 적이 있는 것 같은데, 이 대회로 피쳐 엔지니어링의 중요성을 다시 제대로 느낀 것 같다. 다음엔 더 잘해봐야지. 포기하지 말고...

대회 데이터에 대한 설명으로는...

 

생체 광학 데이터 분석 AI 경진대회

출처 : DACON - Data Science Competition

dacon.io

여기에 내가 생각한 대로 적어놓았다. 적외선 분광분석법을 이용해서, 뇌 내 성분(hhb, hbo2, ca, na)의 농도를 파악하는 것이다. 


 

1. Load Data

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

import time
import warnings ; warnings.filterwarnings('ignore')
from tqdm import tqdm

from sklearn.preprocessing import Normalizer
from sklearn.multioutput import MultiOutputRegressor
from scipy.stats import norm

from sklearn.model_selection import cross_val_score, train_test_split, cross_val_predict
from sklearn.metrics import mean_absolute_error
import random
import shap
import optuna

from lightgbm import LGBMRegressor
from xgboost import XGBRegressor
from catboost import CatBoostRegressor

from sklearn.tree import ExtraTreeRegressor

패키지를 불러온다. 

train = pd.read_csv('data/train.csv', index_col='id')
test = pd.read_csv('data/test.csv', index_col='id')
submission = pd.read_csv('data/sample_submission.csv', index_col='id')

feature_names=list(test)
target_names=list(submission)

Xtrain = train[feature_names]
Xtest = test[feature_names]

Ytrain=train[target_names]
Ytrain1=Ytrain['hhb']
Ytrain2=Ytrain['hbo2']
Ytrain3=Ytrain['ca']
Ytrain4=Ytrain['na']

처음에 대회 데이터를 보고 바로 신경망으로 접근해야겠다고 생각하고 MLP 모델을 만들기 시작했었다. 그런데 아무리 해도 점수가 1.6~1.7 이하로 잘 나오지가 않아서 Tree 모델 4개를 만들어야겠다고 생각했다. (그리고 이 생각이 들고 얼마 후에 데이콘에서 Baseline 코드가 올라왔는데, xgboost로 4번 fit 해서 제출하는데 내 신경망 점수를 바로 짓밟아버렸다...) 난 정말 신경망을 못 만드나 보다. 

아무튼 타겟변수가 4개라서 코드 쓰다가 꼬이거나 헷갈리지 않게 조심해야 할 것 같았다. 

 


 

2. Validation Strategy

base_params = {'num_leaves': 102, 'min_data_in_leaf': 82, 'n_estimators': 1998, 
               'learning_rate': 0.1070227129787253, 'colsample_bytree': 0.6692544288719608, 
               'subsample': 0.7459504136217274, 'reg_alpha': 0.0002735563924007907, 
               'reg_lambda': 0.0029420097457405594}

base_model = LGBMRegressor(n_jobs=-1, objective='l1', silent=False, subsample_freq=1, random_state=18, 
                           **base_params)

multi_model = MultiOutputRegressor(base_model)

기본 모델로 LightGBM과 sklearn의 MultiOutputRegressor 를 사용했다. sklearn 의 MultiOutputRegressor는 타겟 값이 여러 개일때 인자로 받은 한개의 모델을 여러 번 fit 시켜서 여러개 column 들을 뱉는 구조이다. 여기서는 타겟변수가 4개니까, 4번 학습을 한다. 파라미터를 대충 튜닝해서 살짝 underfitting 상태가 될 수 있도록 가볍게 튜닝을 하고, EDA 과정 중에서 반복적으로 사용했다. 이 'multi_model' 의 점수가 좋아지면 채택하고, 좋아지지 않으면 폐기하고 하는 식으로 반복했다. 

def model_scoring_cv(model, x, y, cv=10, verbose=False, n_jobs=None):
    start=time.time()
    score=-cross_val_score(model, x, y, cv=cv, scoring='neg_mean_absolute_error', verbose=verbose, 
                          n_jobs=n_jobs).mean()
    stop=time.time()
    print(f"Validation Time : {round(stop-start, 3)} sec")
    return score

model_scoring_cv 함수를 만들어서 10 fold 교차 검증을 할 수 있도록 했다. 여러번 제출한 결과, 10 fold 교차 검증 점수가 리더보드 점수와 크게 차이가 나지 않아서 이 점수를 믿고 사용하기로 결정했다. 

def train_test_identify(train_df, test_df, calculate_importance=True):
    model=LGBMClassifier(importance_type='gain')
    train_data=train_df.copy()
    test_data=test_df.copy()
    
    train_data['isTrain']=1
    test_data['isTrain']=0
    
    all_data=pd.concat((train_data, test_data))
    target=all_data['isTrain']
    all_data=all_data.drop(columns='isTrain')
    pred=cross_val_predict(model, all_data, target, cv=10)
    if calculate_importance == None:
        pass

    elif calculate_importance==False:
        print(classification_report(target, pred))
    
    else:
        print(classification_report(target, pred))
        model.fit(all_data, target)
        explainer=shap.TreeExplainer(model)
        shap_values=explainer.shap_values(all_data)
        shap.summary_plot(shap_values, all_data)
    return f1_score(target, pred)

Train 데이터와 Test 데이터의 차이를 보고자 만든 Adversial validation 함수이다. Train 데이터에 대해서는 1을 타겟값으로 가지고, Test 데이터는 0을 타겟값으로 가지고, 이를 머신러닝으로 예측한다. F1 Score와 AUC를 구해서, 이 값이 0.5에서 크게 벗어나면, 내가 방금 한 데이터 전처리가 Target Leakage를 가져왔구나 생각하고 수정했다. 


 

3. EDA

3.1 ) Fill NA

src_list=['650_src', '660_src', '670_src', '680_src', '690_src', '700_src', '710_src', '720_src', '730_src', 
          '740_src', '750_src', '760_src', '770_src', '780_src', '790_src', '800_src', '810_src', '820_src', 
          '830_src', '840_src', '850_src', '860_src', '870_src', '880_src', '890_src', '900_src', '910_src', 
          '920_src', '930_src', '940_src', '950_src', '960_src', '970_src', '980_src', '990_src']

dst_list=['650_dst', '660_dst', '670_dst', '680_dst', '690_dst', '700_dst', '710_dst', '720_dst', '730_dst', 
          '740_dst', '750_dst', '760_dst', '770_dst', '780_dst', '790_dst', '800_dst', '810_dst', '820_dst', 
          '830_dst', '840_dst', '850_dst', '860_dst', '870_dst', '880_dst', '890_dst', '900_dst', '910_dst', 
          '920_dst', '930_dst', '940_dst', '950_dst', '960_dst', '970_dst', '980_dst', '990_dst']
          
alpha=Xtrain[dst_list]
beta=Xtest[dst_list]

for i in tqdm(Xtrain.index):
    alpha.loc[i] = alpha.loc[i].interpolate()
    
for i in tqdm(Xtest.index):
    beta.loc[i] = beta.loc[i].interpolate()
    
alpha.loc[alpha['700_dst'].isnull(),'700_dst']=alpha.loc[alpha['700_dst'].isnull(),'710_dst']
alpha.loc[alpha['690_dst'].isnull(),'690_dst']=alpha.loc[alpha['690_dst'].isnull(),'700_dst']
alpha.loc[alpha['680_dst'].isnull(),'680_dst']=alpha.loc[alpha['680_dst'].isnull(),'690_dst']
alpha.loc[alpha['670_dst'].isnull(),'670_dst']=alpha.loc[alpha['670_dst'].isnull(),'680_dst']
alpha.loc[alpha['660_dst'].isnull(),'660_dst']=alpha.loc[alpha['660_dst'].isnull(),'670_dst']
alpha.loc[alpha['650_dst'].isnull(),'650_dst']=alpha.loc[alpha['650_dst'].isnull(),'660_dst']

beta.loc[beta['700_dst'].isnull(),'700_dst']=beta.loc[beta['700_dst'].isnull(),'710_dst']
beta.loc[beta['690_dst'].isnull(),'690_dst']=beta.loc[beta['690_dst'].isnull(),'700_dst']
beta.loc[beta['680_dst'].isnull(),'680_dst']=beta.loc[beta['680_dst'].isnull(),'690_dst']
beta.loc[beta['670_dst'].isnull(),'670_dst']=beta.loc[beta['670_dst'].isnull(),'680_dst']
beta.loc[beta['660_dst'].isnull(),'660_dst']=beta.loc[beta['660_dst'].isnull(),'670_dst']
beta.loc[beta['650_dst'].isnull(),'650_dst']=beta.loc[beta['650_dst'].isnull(),'660_dst']

Xtrain[dst_list] = np.array(alpha)
Xtest[dst_list] = np.array(beta)

가만히 보면 dst column 들에 결측치가 있었다. 이 결측치가 수상한 것이, 650nm의 값은 있고 660nm 은 비어있고, 670nm 은 있고, 하는 식이었다. 중간에도 텅 비어있었다. 그래서 행 별로 결측치를 interpolate 해서 사용했다. 

3.2 ) rho

for col in dst_list:
    Xtrain[col] = Xtrain[col] * (Xtrain['rho'] ** 2)
    Xtest[col] = Xtest[col] * (Xtest['rho']**2)

빛의 측정 강도는 측정 거리의 제곱에 반비례하기 때문에, 측정 거리를 제곱해서 곱해주었다. 이렇게 되면, 같은 파장대의 빛을 쏘더라도, 어떤 실험에서는 측정값이 크고, 어떤 실험에서는 측정값이 작게 나올 수 있는 상황을 보정해줄 수 있다고 생각했다.

3.3 ) ratio

epsilon=1e-10    #prevent division by zero

for dst_col, src_col in zip(dst_list, src_list):
    dst_val=Xtrain[dst_col]
    src_val=Xtrain[src_col] + epsilon
    delta_ratio = dst_val / src_val
    Xtrain[dst_col + '_' + src_col + '_ratio'] = delta_ratio
    
    dst_val=Xtest[dst_col]
    src_val=Xtest[src_col] + epsilon
    
    delta_ratio = dst_val / src_val
    Xtest[dst_col + '_' + src_col + '_ratio'] = delta_ratio
    
print(Xtrain.shape, Xtest.shape)

적외선 분광분석법 실험은 흡수 분광분석 실험이므로, 처음 쏜 빛에 비해서 특정 파장이 몇% 감소했는지를 확인하기 위해 원래 파장과 측정 파장의 비율을 변수로 추가했다. 

3.4 ) gap

from sklearn.preprocessing import Normalizer
normalizer=Normalizer()

all_data = pd.concat((Xtrain, Xtest))
data = all_data.copy()

all_data[src_list] = normalizer.fit_transform(all_data[src_list])
Xtrain[src_list] = all_data[:len(Ytrain)][src_list]
Xtest[src_list] = all_data[len(Ytrain):][src_list]

del all_data

gap_feature_names=[]
for i in range(650, 1000, 10):
    gap_feature_names.append(str(i) + '_gap')

alpha=pd.DataFrame(np.array(Xtrain[src_list]) - np.array(Xtrain[dst_list]), columns=gap_feature_names, index=train.index)
beta=pd.DataFrame(np.array(Xtest[src_list]) - np.array(Xtest[dst_list]), columns=gap_feature_names, index=test.index)

Xtrain=pd.concat((Xtrain, alpha), axis=1)
Xtest=pd.concat((Xtest, beta), axis=1)

print(Xtrain.shape, Ytrain.shape, Xtest.shape)

마찬가지로 원래 파장과 측정된 파장의 차이를 변수로 추가했다. 

실험을 하면서, 데이터의 Normalization 이 필요할 것 같다고 생각했었다. 원래 파장은 값이 잘 나와있는 반면, 측정 파장은 대부분 값이 막 1e-10까지 작아서, 비교했을 때 너무 형편없는 수준이 되지 않을까... 해서, Normalize 하고 비율을 구하고, 차이를 구해보고, 아니면 차이를 구하고 Normalize 해보고 했는데, 이렇게 Normalize 하고 차이를 구하는 게 Validation 점수가 제일 잘 나왔었다. 알고 했다기보다는, 검증 점수 잘 나오는 방향으로 계속 데이터를 수정했다고 봐야 맞을 것 같다. 

3.5 ) DFT

alpha_real=Xtrain[dst_list]
alpha_imag=Xtrain[dst_list]

beta_real=Xtest[dst_list]
beta_imag=Xtest[dst_list]

for i in tqdm(alpha_real.index):
    alpha_real.loc[i]=alpha_real.loc[i] - alpha_real.loc[i].mean()
    alpha_imag.loc[i]=alpha_imag.loc[i] - alpha_real.loc[i].mean()
    
    alpha_real.loc[i] = np.fft.fft(alpha_real.loc[i], norm='ortho').real
    alpha_imag.loc[i] = np.fft.fft(alpha_imag.loc[i], norm='ortho').imag

    
for i in tqdm(beta_real.index):
    beta_real.loc[i]=beta_real.loc[i] - beta_real.loc[i].mean()
    beta_imag.loc[i]=beta_imag.loc[i] - beta_imag.loc[i].mean()
    
    beta_real.loc[i] = np.fft.fft(beta_real.loc[i], norm='ortho').real
    beta_imag.loc[i] = np.fft.fft(beta_imag.loc[i], norm='ortho').imag
    
real_part=[]
imag_part=[]

for col in dst_list:
    real_part.append(col + '_fft_real')
    imag_part.append(col + '_fft_imag')
    
alpha_real.columns=real_part
alpha_imag.columns=imag_part
alpha = pd.concat((alpha_real, alpha_imag), axis=1)

beta_real.columns=real_part
beta_imag.columns=imag_part
beta=pd.concat((beta_real, beta_imag), axis=1)


Xtrain=pd.concat((Xtrain, alpha), axis=1)
Xtest=pd.concat((Xtest, beta), axis=1)
print(Xtrain.shape, Ytrain.shape, Xtest.shape)

이산푸리에 변환을 적용한 값을 추가해주었다. 여기서 실수를 한 것이, 푸리에 변환 이후 나오는 결과물 (복소수) 에 abs 를 씌우면 되는걸, 괜히 실수부와 허수부로 쪼개서 변수를 70개를 추가해버렸다. 물론 안해봐서 정확한 결과는 모르지만, 아무리 생각해도 실수였던 것 같다. 대회 끝나고, 다른 대회의 다른 사람이 코드 공개한 걸 보았는데, 복소수에 abs 씌우면 되는걸.... 난 왜 이렇게 했을까, 대회가 끝나고 알게돼서 어쩔 수 없었다. 

처음에 푸리에 변환을 여기서 써도 되나 싶어서 열심히 검색을 했었다. 시중에는 수많은 FTIR 장비들이 돌아다니고 있었고, 시계열 데이터가 아닌 경우에 푸리에변환을 사용하는 경우들을 확인할 수 있었고, 아 괜찮겠다 싶어서 바로 적용했다. 그리고 그 코드를 EDA 결과물이라고 코드 공유를 했는데... '왜 시계열이 아닌데 푸리에변환 썼냐, 뭔지도 모르는 데이터를 사용하는 건 개오바 아니냐, 열 별로 값을 보면 정규분포 아니냐' 하고 태클을 거시는 한 분이 있었는데, 이제 말하지만 이 분 때문에 너무 스트레스를 받았고 코드 그냥 삭제할까 싶었고, 괜히 올렸다 생각만 정말 많이 들었었다. 댓글 달면서 쓸 시간 없는데.. 코드 한 줄이라도 더 쓸 시간인데.. 

3.6 ) Remove src

Xtrain=Xtrain.drop(columns=src_list)
Xtest=Xtest.drop(columns=src_list)

print(Xtrain.shape, Ytrain.shape, Xtest.shape)

원래 파장인 src 열들을 지워주었다.

3.7 ) Target Variable 

figure, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2)
figure.set_size_inches(14, 14)
sns.distplot(Ytrain['hhb'], ax=ax1, fit=norm)
sns.distplot(Ytrain['hbo2'], ax=ax2, fit=norm)
sns.distplot(Ytrain['ca'], ax=ax3, fit=norm)
sns.distplot(Ytrain['na'], ax=ax4, fit=norm)

4개 타겟 변수 분포 확인

대부분 정규분포에 가깝게 모여있는 것을 확인했다. 로그를 취할까 말까 하다가 그냥 학습하기로 했다. 

3.8 ) Normalize

normalizer=Normalizer()
dst_features=[]
gap_features=[]
ratio_features=[]
real_features=[]
imag_features=[]

for col in list(Xtrain):
    if col[-4:] == '_dst':
        dst_features.append(col)
    elif col[-4:] == 'atio':
        ratio_features.append(col)
    elif col[-4:] == '_gap':
        gap_features.append(col)
    elif col[-4:] == 'real':
        real_features.append(col)
    elif col[-4:] == 'imag':
        imag_features.append(col)
    else:
        pass
print(len(dst_features + gap_features + ratio_features + real_features + imag_features) +1 == len(list(Xtrain)))

all_data = pd.concat((Xtrain, Xtest))
all_data[dst_features] = normalizer.fit_transform(all_data[dst_features])
all_data[imag_features] = normalizer.fit_transform(all_data[imag_features])
all_data[real_features] = normalizer.fit_transform(all_data[real_features])
all_data[gap_features] = normalizer.fit_transform(all_data[gap_features])

Xtrain = all_data[:len(Ytrain)]
Xtest = all_data[len(Ytrain):]

print(Xtrain.shape, Ytrain.shape, Xtest.shape)
#(10000, 176) (10000, 4) (10000, 176)

변수들을 구분해서 Normalize 했다. 아무래도 스케일이 맞아야 더 잘 학습이 될 것 같았다. 결과적으로 총 176개의 변수로 학습을 하기로 결정했다. 


 

4. Feature Selection

Xtrain1 = Xtrain.copy()
Xtrain2 = Xtrain.copy()
Xtrain3 = Xtrain.copy()
Xtrain4 = Xtrain.copy()

Xtest1 = Xtest.copy()
Xtest2 = Xtest.copy()
Xtest3 = Xtest.copy()
Xtest4 = Xtest.copy()

타겟변수가 4개이므로, 4개의 데이터 사본을 만들었다. 각각의 타겟 변수에 따라서 주로 사용할 파장 영역이 다를 것 같다 생각해서 Feature Selection을 다르게 진행하기 위해서였다. 

def apply_chromosome(chromosome, x_train):
    df = x_train.copy()
    for col, apply in zip(x_train.columns, chromosome):
        if apply == 0:
            df = df.drop(columns=col)
    return df


def scoring(model, x_train, y_train):
    return -cross_val_score(model, x_train, y_train, cv=5, scoring='neg_mean_absolute_error').mean()


def make_chromosome_score(chromosome, model, x_train, y_train):
    data= apply_chromosome(chromosome, x_train)
    value = scoring(model, data, y_train)
    return value


def choose_chromosomes(chromosomes_list, scored_list, factor, num_choose):
    chromosome_pool = chromosomes_list.copy()
    score_list = scored_list.copy()
    
    chosen_chromosome=[]
    
    new_score_array=np.array(score_list) + np.random.normal(0, np.array(score_list).std() * factor, size=len(score_list))
    new_score_list = list(new_score_array)
    
    
    for i in range(num_choose):
        select_index = np.argmin(new_score_list)
        selected = chromosome_pool[select_index]
        del chromosome_pool[select_index]
        del new_score_list[select_index]
        
        new_score_array = np.array(new_score_list)
        
        chosen_chromosome.append(selected)
    return chosen_chromosome
    
def crossover(chromosome_list, score_list, next_pool_size, factor, num_choose):
    child_pool = []
    while len(child_pool) < next_pool_size:
        best_parents=choose_chromosomes(chromosome_list, score_list, factor=factor, num_choose=num_choose)
        parents = random.sample(best_parents, 2)
        mom = parents[0]
        dad = parents[1]
        inherit = np.round(np.random.uniform(low=0, high=1, size=len(mom)))
        child = []

        for dna, i in zip(inherit, range(len(inherit))):
            if dna==0:
                child.append(mom[i])
            else:
                child.append(dad[i])
        child_pool.append(child)
    return child_pool

def mutant(pool, mutation_rate, mutation_size):
    to_be_mutant = np.round(np.random.uniform(low=0, high=1, size=len(pool)) - (0.5 - mutation_rate))
    which_child = np.where(to_be_mutant==1)[0]
    for child_index in which_child:
        mutation_info = np.round(np.random.uniform(low=0, high=1, size=len(to_be_mutant))-(0.5-mutation_size))
        mutation_info = np.where(mutation_info ==1)[0]

        for is_mutated in mutation_info:

            if pool[child_index][is_mutated]==0:
                pool[child_index][is_mutated] = 1
            elif pool[child_index][is_mutated]==1:
                pool[child_index][is_mutated] = 0
    return pool
    
    
def next_generation(model, x_train, y_train, first_gen_chromosome, next_pool_size, factor, num_choose_parents, 
                   mutation_rate, mutation_size):
    score_list=[]
    for chromosome in tqdm(first_gen_chromosome):
        score = make_chromosome_score(chromosome, model, x_train, y_train)
        score_list.append(score)
    print("Calculating Current Generation's Score")
        
    current_generation_best_chromosome = first_gen_chromosome[np.argmin(score_list)]
    current_generation_best_score = np.min(score_list)
    current_generation_mean_score = np.mean(score_list)


    print(current_generation_best_chromosome)
    print(f"\nBest Score : {current_generation_best_score}")

    print("Generating Child pool")
    child_pool = crossover(first_gen_chromosome, score_list, next_pool_size=next_pool_size, 
                           factor=factor, num_choose=num_choose_parents)
    print("Generating Mutation childs")
    next_generation_chromosomes = mutant(child_pool, mutation_rate=mutation_rate, mutation_size=mutation_size)
    
    print('Done!')
    return next_generation_chromosomes, current_generation_best_chromosome, \
                current_generation_best_score, current_generation_mean_score
                
def feature_select_ga(model, x_train, y_train, num_epoch, pool_size, factor=0.1, 
                      num_choose_parents=3, mutation_rate=0.05, mutation_size=0.05, plot_score = True):
    
    #factor : percentage of noise added to calculated score
    #if too large, bad scoring parents could be selected since score order changes
    
    #mutation_rate : percentage of childs to be mutated
    #mutation_size : percentage of genes to be mutated
    
    pool_size = pool_size
    pool=[]
    
    for i in range(pool_size):
        chromosome = np.round(np.random.uniform(low=0, high=1, size=x_train.shape[1]))
        pool.append(chromosome)
    
    best_features=[]
    best_scores=[]
    mean_scores=[]
    
    for i in tqdm(range(num_epoch)):
        pool, gen_best_features, gen_best_score, gen_mean_score = next_generation(model, x_train, y_train, pool, 
                                                                            next_pool_size=pool_size, 
                                                                            factor = factor, 
                                                                            num_choose_parents=num_choose_parents, 
                                                                            mutation_rate=mutation_rate, 
                                                                            mutation_size=mutation_size)
        best_features.append(gen_best_features)
        best_scores.append(gen_best_score)
        mean_scores.append(gen_mean_score)


    
    final_score_list=[]
    for chromosome in tqdm(pool):
        score = make_chromosome_score(chromosome, base_model, x_train, y_train)
        final_score_list.append(score)

    last_generation_best_chromosome = pool[np.argmin(final_score_list)]
    last_generation_best_score = np.min(final_score_list)
    last_generation_mean_score = np.mean(final_score_list)

    best_features.append(last_generation_best_chromosome)
    best_scores.append(last_generation_best_score)
    mean_scores.append(last_generation_mean_score)
    
    if plot_score==True:
        figure, ax1 = plt.subplots(nrows=1, ncols=1)
        figure.set_size_inches(10, 7)
        sns.lineplot(data=np.array(best_scores), ax=ax1, label='best')
        sns.lineplot(data=np.array(mean_scores), ax=ax1, label='mean')
        
    best_chromosome = best_features[np.argmin(best_scores)]

    df = apply_chromosome(best_chromosome, x_train)
    print(f"best_features = {list(df)}")
    return df

유전 알고리즘이란 것을 처음 들어보았다. 나무위키에 쓰여있는 유전 알고리즘 설명을 보고 대충 구현해 보았다. 사용하는 변수는 1, 사용하지 않는 변수는 0을 주고, 최고의 [1,0,0,0,1,1,1,1,1,0....]을 찾은 다음에 데이터에 적용시키면 각각의 타겟변수에 대해 필요한 변수를 잘 선택할 수 있을 거라고 생각했다. 

#Xtrain1 = feature_select_ga(base_model, Xtrain1, Ytrain1, pool_size=15, num_epoch=20,
#                           num_choose_parents=4, mutation_rate=0.1, mutation_size=0.1)
#Xtest1 = Xtest1[list(Xtrain1)]
best_features = ['rho', '650_dst', '680_dst', '770_dst', '790_dst', '810_dst', '830_dst', '840_dst', '850_dst', '860_dst', '880_dst', '890_dst', '930_dst', '960_dst', '980_dst', '650_dst_650_src_ratio', '660_dst_660_src_ratio', '690_dst_690_src_ratio', '720_dst_720_src_ratio', '760_dst_760_src_ratio', '770_dst_770_src_ratio', '800_dst_800_src_ratio', '810_dst_810_src_ratio', '830_dst_830_src_ratio', '840_dst_840_src_ratio', '850_dst_850_src_ratio', '860_dst_860_src_ratio', '870_dst_870_src_ratio', '920_dst_920_src_ratio', '930_dst_930_src_ratio', '940_dst_940_src_ratio', '990_dst_990_src_ratio', '650_gap', '700_gap', '720_gap', '790_gap', '800_gap', '820_gap', '840_gap', '860_gap', '910_gap', '920_gap', '930_gap', '940_gap', '950_gap', '960_gap', '980_gap', '660_dst_fft_real', '670_dst_fft_real', '680_dst_fft_real', '690_dst_fft_real', '720_dst_fft_real', '740_dst_fft_real', '780_dst_fft_real', '800_dst_fft_real', '810_dst_fft_real', '840_dst_fft_real', '850_dst_fft_real', '870_dst_fft_real', '890_dst_fft_real', '910_dst_fft_real', '920_dst_fft_real', '930_dst_fft_real', '950_dst_fft_real', '650_dst_fft_imag', '670_dst_fft_imag', '730_dst_fft_imag', '770_dst_fft_imag', '780_dst_fft_imag', '840_dst_fft_imag', '850_dst_fft_imag', '860_dst_fft_imag', '890_dst_fft_imag', '900_dst_fft_imag', '920_dst_fft_imag', '960_dst_fft_imag', '980_dst_fft_imag', '990_dst_fft_imag']
Xtrain1 = Xtrain1[best_features]
Xtest1 = Xtest1[best_features]
print(Xtrain1.shape, Ytrain1.shape, Xtest1.shape)


#Xtrain2 = feature_select_ga(base_model, Xtrain2, Ytrain2, pool_size=15, num_epoch=20, 
#                            num_choose_parents=4, mutation_rate=0.1, mutation_size=0.1)
#Xtest2 = Xtest2[list(Xtrain2)]
best_features = ['rho', '670_dst', '730_dst', '750_dst', '770_dst', '780_dst', '790_dst', '800_dst', '830_dst', '870_dst', '890_dst', '900_dst', '950_dst', '960_dst', '970_dst', '980_dst', '990_dst', '670_dst_670_src_ratio', '730_dst_730_src_ratio', '740_dst_740_src_ratio', '750_dst_750_src_ratio', '780_dst_780_src_ratio', '790_dst_790_src_ratio', '800_dst_800_src_ratio', '830_dst_830_src_ratio', '840_dst_840_src_ratio', '850_dst_850_src_ratio', '890_dst_890_src_ratio', '910_dst_910_src_ratio', '920_dst_920_src_ratio', '930_dst_930_src_ratio', '950_dst_950_src_ratio', '970_dst_970_src_ratio', '990_dst_990_src_ratio', '650_gap', '670_gap', '690_gap', '770_gap', '780_gap', '790_gap', '800_gap', '810_gap', '820_gap', '960_gap', '980_gap', '990_gap', '660_dst_fft_real', '670_dst_fft_real', '730_dst_fft_real', '750_dst_fft_real', '770_dst_fft_real', '800_dst_fft_real', '810_dst_fft_real', '830_dst_fft_real', '880_dst_fft_real', '930_dst_fft_real', '950_dst_fft_real', '960_dst_fft_real', '970_dst_fft_real', '980_dst_fft_real', '990_dst_fft_real', '650_dst_fft_imag', '670_dst_fft_imag', '680_dst_fft_imag', '700_dst_fft_imag', '720_dst_fft_imag', '730_dst_fft_imag', '740_dst_fft_imag', '750_dst_fft_imag', '780_dst_fft_imag', '790_dst_fft_imag', '840_dst_fft_imag', '850_dst_fft_imag', '860_dst_fft_imag', '870_dst_fft_imag', '880_dst_fft_imag', '920_dst_fft_imag', '930_dst_fft_imag', '940_dst_fft_imag', '950_dst_fft_imag', '960_dst_fft_imag']
Xtrain2 = Xtrain2[best_features]
Xtest2 = Xtest2[best_features]
print(Xtrain2.shape, Ytrain2.shape, Xtest2.shape)


#Xtrain3 = feature_select_ga(base_model, Xtrain3, Ytrain3, pool_size=15, num_epoch=20, 
#                           num_choose_parents=4, mutation_rate=0.1, mutation_size=0.1)
#Xtest3 = Xtest3[list(Xtrain3)]
best_features = ['rho', '670_dst', '680_dst', '700_dst', '750_dst', '760_dst', '780_dst', '800_dst', '810_dst', '820_dst', '840_dst', '850_dst', '860_dst', '880_dst', '910_dst', '930_dst', '940_dst', '950_dst', '960_dst', '970_dst', '980_dst', '650_dst_650_src_ratio', '670_dst_670_src_ratio', '690_dst_690_src_ratio', '710_dst_710_src_ratio', '720_dst_720_src_ratio', '740_dst_740_src_ratio', '750_dst_750_src_ratio', '770_dst_770_src_ratio', '830_dst_830_src_ratio', '840_dst_840_src_ratio', '850_dst_850_src_ratio', '860_dst_860_src_ratio', '870_dst_870_src_ratio', '900_dst_900_src_ratio', '950_dst_950_src_ratio', '960_dst_960_src_ratio', '980_dst_980_src_ratio', '680_gap', '690_gap', '740_gap', '760_gap', '770_gap', '790_gap', '800_gap', '820_gap', '830_gap', '840_gap', '860_gap', '870_gap', '890_gap', '910_gap', '920_gap', '940_gap', '960_gap', '980_gap', '990_gap', '710_dst_fft_real', '720_dst_fft_real', '730_dst_fft_real', '740_dst_fft_real', '750_dst_fft_real', '760_dst_fft_real', '770_dst_fft_real', '780_dst_fft_real', '810_dst_fft_real', '820_dst_fft_real', '830_dst_fft_real', '840_dst_fft_real', '850_dst_fft_real', '860_dst_fft_real', '870_dst_fft_real', '920_dst_fft_real', '930_dst_fft_real', '950_dst_fft_real', '970_dst_fft_real', '980_dst_fft_real', '660_dst_fft_imag', '670_dst_fft_imag', '690_dst_fft_imag', '720_dst_fft_imag', '730_dst_fft_imag', '740_dst_fft_imag', '750_dst_fft_imag', '770_dst_fft_imag', '780_dst_fft_imag', '790_dst_fft_imag', '800_dst_fft_imag', '810_dst_fft_imag', '830_dst_fft_imag', '860_dst_fft_imag', '880_dst_fft_imag', '890_dst_fft_imag', '930_dst_fft_imag', '940_dst_fft_imag', '950_dst_fft_imag', '960_dst_fft_imag', '980_dst_fft_imag']
Xtrain3 = Xtrain[best_features]
Xtest3 = Xtest[best_features]
print(Xtrain3.shape, Ytrain3.shape, Xtest3.shape)


#Xtrain4 = feature_select_ga(base_model, Xtrain4, Ytrain4, pool_size=15, num_epoch=20, 
#                           num_choose_parents=4, mutation_rate=0.1, mutation_size=0.1)
#Xtest4 = Xtest4[list(Xtrain4)]
best_features = ['rho', '670_dst', '680_dst', '690_dst', '720_dst', '740_dst', '750_dst', '760_dst', '780_dst', '790_dst', '830_dst', '840_dst', '850_dst', '870_dst', '880_dst', '890_dst', '900_dst', '910_dst', '920_dst', '950_dst', '970_dst', '980_dst', '650_dst_650_src_ratio', '700_dst_700_src_ratio', '710_dst_710_src_ratio', '720_dst_720_src_ratio', '730_dst_730_src_ratio', '740_dst_740_src_ratio', '760_dst_760_src_ratio', '770_dst_770_src_ratio', '780_dst_780_src_ratio', '800_dst_800_src_ratio', '810_dst_810_src_ratio', '830_dst_830_src_ratio', '840_dst_840_src_ratio', '850_dst_850_src_ratio', '860_dst_860_src_ratio', '870_dst_870_src_ratio', '880_dst_880_src_ratio', '890_dst_890_src_ratio', '910_dst_910_src_ratio', '920_dst_920_src_ratio', '930_dst_930_src_ratio', '980_dst_980_src_ratio', '660_gap', '680_gap', '690_gap', '700_gap', '710_gap', '770_gap', '780_gap', '850_gap', '860_gap', '880_gap', '900_gap', '910_gap', '920_gap', '930_gap', '950_gap', '650_dst_fft_real', '670_dst_fft_real', '680_dst_fft_real', '690_dst_fft_real', '710_dst_fft_real', '720_dst_fft_real', '740_dst_fft_real', '760_dst_fft_real', '810_dst_fft_real', '840_dst_fft_real', '850_dst_fft_real', '860_dst_fft_real', '890_dst_fft_real', '900_dst_fft_real', '940_dst_fft_real', '970_dst_fft_real', '980_dst_fft_real', '660_dst_fft_imag', '670_dst_fft_imag', '710_dst_fft_imag', '720_dst_fft_imag', '730_dst_fft_imag', '770_dst_fft_imag', '780_dst_fft_imag', '790_dst_fft_imag', '810_dst_fft_imag', '870_dst_fft_imag', '890_dst_fft_imag', '910_dst_fft_imag', '940_dst_fft_imag', '950_dst_fft_imag', '970_dst_fft_imag']
Xtrain4 = Xtrain4[best_features]
Xtest4 = Xtest4[best_features]
print(Xtrain4.shape, Ytrain4.shape, Xtest4.shape)

#(10000, 78) (10000,) (10000, 78)
#(10000, 81) (10000,) (10000, 81)
#(10000, 98) (10000,) (10000, 98)
#(10000, 91) (10000,) (10000, 91)

4개 데이터에 대해서 모두 적용하면 약 21시간이 걸린다. 뭐라고? 그래서 매번 실행할 수가 없어서 실행하고, 결과물을 복사해두었다. 각각의 타겟별로 총 4개의 Xtrain 들을 만들었다. 


 

5. Build Models

5.1 ) LightGBM

lgb_params1 = {'n_estimators': 2785, 'learning_rate': 0.012256313719980687, 'num_leaves': 73, 
               'colsample_bytree': 0.6424334465587705, 'subsample': 0.6084147966276774, 
               'reg_alpha': 4.334078134974237e-05, 'reg_lambda': 0.00024427748977268426, 'min_data_in_leaf': 16}
lgb1 = LGBMRegressor(subsample_freq=1, silent=False, random_state=18, **lgb_params1)
lgb_score1 = model_scoring_cv(lgb1, Xtrain1, Ytrain1)
print(f"lgb score 1 : {lgb_score1}")

#Validation Time : 312.063 sec
#lgb score 1 : 0.6450181806462878

lgb_params2 = {'n_estimators': 2994, 'learning_rate': 0.02064007127149768, 'num_leaves': 36, 
               'colsample_bytree': 0.9662203993996966, 'subsample': 0.8235831321555162, 
               'reg_alpha': 9.361670014148117e-10, 'reg_lambda': 0.014778229454777763, 'min_data_in_leaf': 69}
lgb2=LGBMRegressor(subsample_freq=1, silent=False, random_state=18, **lgb_params2)
lgb_score2 = model_scoring_cv(lgb2, Xtrain2, Ytrain2)
print(f"lgb score 2 : {lgb_score2}")

#Validation Time : 242.814 sec
#lgb score 2 : 0.47096186669621254

lgb_params3 = {'n_estimators': 2351, 'learning_rate': 0.0142123934679665, 'num_leaves': 60, 
               'colsample_bytree': 0.8200484609355502, 'subsample': 0.8682071101415381, 
               'reg_alpha': 0.019504059762055263, 'reg_lambda': 6.760777234202785e-07, 'min_data_in_leaf': 58}
lgb3=LGBMRegressor(subsample_freq=1, silent=False, random_state=18, **lgb_params3)
lgb_score3 = model_scoring_cv(lgb3, Xtrain3, Ytrain3)
print(f"lgb score 3 : {lgb_score3}")

#Validation Time : 301.473 sec
#lgb score 3 : 1.519919905631499

lgb_params4 = {'n_estimators': 2036, 'learning_rate': 0.010105984741650555, 'num_leaves': 262, 
               'colsample_bytree': 0.5481503221078929, 'subsample': 0.6283957235662508, 
               'reg_alpha': 1.3143634189790955e-06, 'reg_lambda': 1.4769566595629849e-05, 
               'min_data_in_leaf': 86}
lgb4=LGBMRegressor(subsample_freq=1, silent=False, random_state=18, **lgb_params4)
lgb_score4 = model_scoring_cv(lgb4, Xtrain4, Ytrain4)
print(f"lgb score 4 : {lgb_score4}")

#Validation Time : 141.399 sec
#lgb score 4 : 1.0987161424910443

print(f"LightGBM Mean CV Score : {(lgb_score1 + lgb_score2 + lgb_score3 + lgb_score4)/4}"
#LightGBM Mean CV Score : 0.933654023866261

4개의 LightGBM 모델을 만들었다. 마찬가지로 XGBoost와 CatBoost 도 4개씩 만들었다. 

5.2 ) XGBoost

xgb_params1 = {'n_estimators': 2881, 'learning_rate': 0.011711857673636635, 'max_depth': 8, 
               'colsample_bytree': 0.9827808935047092, 
               'subsample': 0.5710690334832227, 'reg_alpha': 1.3854966003147966, 
               'reg_lambda': 0.0003816097499332342}
xgb1 = XGBRegressor(verbosity=0, random_state=18, objective = 'reg:squarederror', eval_metric='mae', 
                    **xgb_params1, 
                   sampling_method='gradient_based', tree_method='gpu_hist'
                   )
xgb_score1 = model_scoring_cv(xgb1, Xtrain1, Ytrain1)
print(f"xgb score 1 : {xgb_score1}")

#Validation Time : 974.623 sec
#xgb score 1 : 0.6597893721626996

xgb_params2 = {'n_estimators': 2865, 'learning_rate': 0.043801989516317016, 'max_depth': 4, 
               'colsample_bytree': 0.8778900801725762, 'subsample': 0.7274550283257443, 
               'reg_alpha': 2.792469294881351e-07, 'reg_lambda': 18.480038009887284}
xgb2 = XGBRegressor(verbosity=0, random_state=18, objective = 'reg:squarederror', eval_metric='mae', 
                    **xgb_params2, 
                   sampling_method='gradient_based', tree_method='gpu_hist', 
                   )
xgb_score2 = model_scoring_cv(xgb2, Xtrain2, Ytrain2)
print(f"xgb score 2 : {xgb_score2}")

#Validation Time : 232.204 sec
#xgb score 2 : 0.4805120802893638

xgb_params3 = {'n_estimators': 2643, 'learning_rate': 0.031712179695614046, 'max_depth': 5, 
               'colsample_bytree': 0.7424966681183006, 'subsample': 0.787669908095534, 
               'reg_alpha': 0.0019021769478904957, 'reg_lambda': 0.6331937967083731}
xgb3 = XGBRegressor(verbosity=0, random_state=18, objective = 'reg:squarederror', eval_metric='mae', 
                    **xgb_params3,
                    sampling_method='gradient_based', tree_method='gpu_hist', 
                   )
xgb_score3 = model_scoring_cv(xgb3, Xtrain3, Ytrain3)
print(f"xgb score 3 : {xgb_score3}")

#Validation Time : 315.204 sec
#xgb score 3 : 1.5351869589180944

xgb_params4 = {'n_estimators': 2692, 'learning_rate': 0.0304561991789567, 'max_depth': 6, 
               'colsample_bytree': 0.561225558419653, 'subsample': 0.7109372329820719, 
               'reg_alpha': 6.551680601955312e-08, 'reg_lambda': 4.484664174506758}
xgb4 = XGBRegressor(verbosity=0, random_state=18, objective = 'reg:squarederror', eval_metric='mae', 
                    **xgb_params4, 
                   sampling_method='gradient_based', tree_method='gpu_hist', 
                   )
xgb_score4 = model_scoring_cv(xgb4, Xtrain4, Ytrain4)
print(f"xgb score 4 : {xgb_score4}")

#Validation Time : 426.062 sec
#xgb score 4 : 1.107904721993923

print(f"XGBoost Mean CV Score : {(xgb_score1 + xgb_score2 + xgb_score3 + xgb_score4)/4}")
#XGBoost Mean CV Score : 0.9458482833410202

진짜 느려 터졌다. 

5.3) CatBoost 

cat_params1 = {'iterations': 2681, 'depth': 7, 'learning_rate': 0.08663866615470771, 
               'random_strength': 9.235074797628041, 'l2_leaf_reg': 8.164833371246262, 
               'bagging_temperature': 0.8220097439681222}
cat1 = CatBoostRegressor(loss_function='MAE', random_seed=18, verbose=False, **cat_params1,
                         task_type='GPU'
                        )
cat_score1 = model_scoring_cv(cat1, Xtrain1, Ytrain1)
print(f"CatBoostScore1 : {cat_score1}")

#Validation Time : 785.542 sec
#CatBoostScore1 : 0.6847231512252785

cat_params2 = {'iterations': 2769, 'depth': 6, 'learning_rate': 0.17889638008330497, 
               'random_strength': 9.283130620824029, 'l2_leaf_reg': 43.78952651533997, 
               'bagging_temperature': 0.7378285753465266}
cat2 = CatBoostRegressor(loss_function='MAE', random_seed=18, verbose=False, **cat_params2,
                         task_type='GPU'
                        )
cat_score2 = model_scoring_cv(cat2, Xtrain2, Ytrain2)
print(f"CatBoostScore2 : {cat_score2}")

#Validation Time : 609.407 sec
#CatBoostScore2 : 0.4901726900123041

cat_params3 = {'iterations': 2843, 'depth': 7, 'learning_rate': 0.2677960942055296, 
               'random_strength': 5.280805075485188, 'l2_leaf_reg': 6.178243639724696, 
               'bagging_temperature': 0.03590951004104092}
cat3 = CatBoostRegressor(loss_function='MAE', random_seed=18, verbose=False, **cat_params3,
                         task_type='GPU'
                        )
cat_score3 = model_scoring_cv(cat3, Xtrain3, Ytrain3)
print(f"CatBoostScore3 : {cat_score3}")

#Validation Time : 938.061 sec
#CatBoostScore3 : 1.5602005106402956

cat_params4 = {'iterations': 2911, 'depth': 7, 'learning_rate': 0.0923974192869855, 
               'bagging_temperature': 0.04030601548502897, 'random_strength': 8.50411971804888}
cat4 = CatBoostRegressor(loss_function='MAE', random_seed=18, verbose=False, **cat_params4,
                         task_type='GPU'
                        )
cat_score4 = model_scoring_cv(cat4, Xtrain4, Ytrain4)
print(f"CatBoostScore4 : {cat_score4}")

#Validation Time : 899.926 sec
#CatBoostScore4 : 1.1108542319122767

print(f"CatBoost Mean CV Score : {(cat_score1 + cat_score2 + cat_score3 + cat_score4) / 4}")
#CatBoost Mean CV Score : 0.9614876459475388

느리고 결과도 안 좋다. 


 

6. Ensemble

models_hhb=[lgb1, xgb1, cat1]
models_hbo2=[lgb2, xgb2, cat2]
models_ca=[lgb3, xgb3, cat3]
models_na=[lgb4, xgb4, cat4]

train_preds_hhb = pd.DataFrame(data=None, index=train.index)
train_preds_hbo2 = pd.DataFrame(data=None, index=train.index)
train_preds_ca = pd.DataFrame(data=None, index=train.index)
train_preds_na = pd.DataFrame(data=None, index=train.index)

test_preds_hhb = pd.DataFrame(data=None, index=test.index)
test_preds_hbo2 = pd.DataFrame(data=None, index=test.index)
test_preds_ca = pd.DataFrame(data=None, index=test.index)
test_preds_na = pd.DataFrame(data=None, index=test.index)

모델과 예측 결과를 담을 데이터프레임을 새로 만들어주고, 

for bag_train, bag_test, x, y, xtest, models in zip(
                                [train_preds_hhb, train_preds_hbo2, train_preds_ca, train_preds_na], 
                                [test_preds_hhb, test_preds_hbo2, test_preds_ca, test_preds_na], 
                                [Xtrain1, Xtrain2, Xtrain3, Xtrain4],
                                [Ytrain1, Ytrain2, Ytrain3, Ytrain4], 
                                [Xtest1, Xtest2, Xtest3, Xtest4],
                                [models_hhb, models_hbo2, models_ca, models_na]):

    for model, names in tqdm(zip(models, ['lgb', 'cat', 'xgb'])):
        bag_train[f"{names}_{i}"] = cross_val_predict(model, x, y, cv=5)
        model.fit(x, y)
        bag_test[f"{names}_{i}"] = model.predict(xtest)
        
print(train_preds_hhb.shape, train_preds_hbo2.shape, train_preds_ca.shape, train_preds_na.shape)
print(test_preds_hhb.shape, test_preds_hbo2.shape, test_preds_ca.shape, test_preds_na.shape)

#(10000, 3) (10000, 3) (10000, 3) (10000, 3)
#(10000, 3) (10000, 3) (10000, 3) (10000, 3)

train 데이터에 대해서 5 fold 교차 검증으로 예측값을 만들고, test 데이터에 대해서는 전부를 이용해서 학습하고 예측을 해서 결과물을 모았다. 

from sklearn.ensemble import ExtraTreesRegressor
et1_params = {}
et2_params = {'n_estimators': 84, 'max_depth': 6, 'min_samples_split': 15, 'min_samples_leaf': 1}
et3_params = {'n_estimators': 67, 'max_depth': 6, 'min_samples_split': 161, 'min_samples_leaf': 1}
et4_params = {'n_estimators': 83, 'max_depth': 6, 'min_samples_split': 158, 'min_samples_leaf': 1}


et1 = ExtraTreesRegressor(**et1_params)
et2 = ExtraTreesRegressor(**et2_params)
et3 = ExtraTreesRegressor(**et3_params)
et4 = ExtraTreesRegressor(**et4_params)

ets_list=[et1, et2, et3, et4]

mean_score = 0
for model, x, y, xtest in zip(ets_list, 
                       [train_preds_hhb, train_preds_hbo2, train_preds_ca, train_preds_na], 
                       [Ytrain1, Ytrain2, Ytrain3, Ytrain4], 
                       [test_preds_hhb, test_preds_hbo2, test_preds_ca, test_preds_na]):
    mean_score = mean_score + model_scoring_cv(model, x, y, n_jobs=1)
    model.fit(x, y)
    pred = model.predict(xtest)
    submission[y.name] = pred
    
print(f"Mean CV Score : {mean_score / 4}")
submission.head()

예측 결과물들을 가벼운 ExtraTreesRegressor를 사용해서 최종 결과를 만들었다. 최종 교차 검증 결과는 0.93309. 

교차검증 점수는 LightGBM 단일 모델과 크게 차이가 나지 않았는데, 리더보드 점수가 LightGBM 단일 모델은 0.956, 스태킹 모델은 약 0.945 정도여서, 어느 정도 아주 미약하게나마 일반화 성능(?)을 개선시켰다고 판단하고 최종 예측 결과로 사용했다. 

submission.to_csv('Final ETS Ensemble CV 0_93309.csv')

 


 

7. 결과

Public LB에서는 0.94532로 21위로 기록되어 있었는데, Private LB 에서는 0.92553으로 17위로 올라와서 대회를 마무리할 수 있었다. 

대회 종료 이후 다른 사람들의 코드를 보았는데, 다들 정말 피쳐 엔지니어링에 신경을 많이 쓴 것 같았고, 단일 모델 dart 로도 점수가 나보다 더 잘 나왔다. 보고 현타가 좀 왔었는데, 정말 내가 쓸데없는 짓 하느라 시간을 많이 끌었구나 생각이 정말 정말 정말로 많이 들었다. 그래도, 상위 10% 안에 드는 것이 목표였는데, 목표도 달성했고, 처음으로 무언가 상금은 아니지만 리턴을 받은 대회여서 너무 좋았다!

진짜 사람들 개잘하는거같다 너무 빡세다

반응형
Posted by Jamm_