티스토리 뷰
지난시간에 이어서 피처 엔지니어링 + 베이스라인 모델 생성이다.
데이터 합치기와 데이터 나누기는 지난 챌린지를 참고하면 되고, 이번에는 먼저 결과를 봐보자.
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder() # 원-핫 인코더 생성
all_data_encoded = encoder.fit_transform(all_data) # 원-핫 인코딩 적용
all_data_encoded
<496204x5700 sparse matrix of type '<class 'numpy.float64'>'
with 11412692 stored elements in Compressed Sparse Row format>
X_train.shape
(298042, 5700)
from sklearn.metrics import roc_auc_score # ROC AUC 점수 계산 함수
# 검증 데이터 ROC AUC
roc_auc = roc_auc_score(y_valid, y_valid_preds)
print(f'검증 데이터 ROC AUC : {roc_auc:.4f}')
검증 데이터 ROC AUC : 0.7798
X_train.shape는 298042니까, 저절로 테스트 데이터는 496024-298042로 198162가 될 것이다.
여기까지만 보면, 검증 데이터 ROC_AUC도 잘 나왔고, 아무 문제가 없어 보인다.
하지만 당연한 장애물이 있었으니..그건 바로 submission의 크기보다 데이터 모델의 차원 수가 월등히 작다는 문제였다.
# 제출 파일 생성
submission['target'] = y_preds
submission.to_csv('submission.csv')
ValueError: Length of values (198162) does not match length of index (400000)
자 왜 이런걸까?
너무나 당연하다. submission이라는 파일은 40만 개수로 고정되어 있는데, 생성된 예측 결과는 198162로 40만이 안된다.
즉, 이런식으로 나누게 되면 모델은 분명 생성되지만 제출은 불가능하다.
그럼 그냥 훈련 데이터와 테스트 데이터를 따로 하면 되지 않는가? 하고 생각하는 사람들이 있을 것이다.
결론적으로 불가능한 이유는, 피처 엔지니어링을 같이 하려면 결국 테스트 데이터에 있는 결측값도 제거해줘야 정상적으로 돌아간다. 그런데 테스트 데이터를 따로 피처 엔지니어링 하게 되면, 인코딩 과정에서 훈련 데이터와 고윳값의 차이가 생긴다.
(298042, 5700) # 훈련 데이터
(400000, 5718) # 처음 테스트 데이터 원-핫 인코딩
이러면 predict 자체가 안된다. 결국에는 꼼수도 막혀버리는 것이다.
그렇다면 이 과정을 피하려면 어떻게 해야 할까?
결론적으로 필자가 스스로 생각해낸 해답은 두 가지이다.
1. 훈련 데이터에서 결측값을 제거할 때, 절대로 테스트 데이터 수보다 적게 남게 하지 않는다.
2. 결측값을 그냥 많은 값(대세 값)으로 채워버린다.
이 이유는, 타겟이 훈련 데이터에만 있기 때문이다.
만약 정말 Worst case를 한다고 하면, 훈련 데이터가 테스트 데이터보다 많을 시 적어도 잘라서 쓸 수 있다.
하지만 훈련 데이터가 40만 이하로 내려가게 된다면, 피처 엔지니어링을 할 때 잘라서 쓸 수도 없다.
따라서 정말 중요한 것이다. 타깃값이 만약 테스트 데이터에도 있다면 가능하겠지만, 이 케이스는 아니니까.
본 경진대회는 플레이 그라운드이므로 연습 겸 시도해보는 게 중요하다. 따라서 필자는 이렇게 도전해본 것이다.
따라서 이번에는 번거로운 1번보다는 비교적 쉬운 2번으로 시도해보겠다.
bin0~bin5 같은 바이너리 값들이다. 따라서 0과 1은 대세인 0으로 처리하면 된다. N이나 F는 4~5만.
마찬가지로 nom_0~4는 = red,Triangle,Hamster,India,Theremin으로,
ord는 1.0,Novice,freezing,n, N,Fl로 , day와 month는 3.0과 8.0으로 각각 치환해주겠다. (Fl의 경우, sort 해보면 알 수 있다.)
그에 따라서 각각 나온 코드는 다음과 같다.
def fill(df,feature,g):
df[feature] = df[feature].fillna(g)
return df
fe = ['bin_0' ,'bin_1','bin_2','bin_3','bin_4']
for i in fe:
fill(train,i,0)
train.info()
#bin_0~4는 대세값인 0으로 처리
fe = ['nom_0' ,'nom_1','nom_2','nom_3','nom_4','ord_0' ,'ord_1','ord_2','ord_3','ord_4','ord_5']
g = ['red','Triangle','Hamster','india','Theremin',1.0,'Novice','Freezing','n', 'N','Fl']
for i,j in zip(fe,g):
fill(train,i,j)
train.info()
#ord_0~4, nom_0~4는 각각 대세값 처리
train.head()
fe = ['day','month']
g = [3.0,8.0]
for i,j in zip(fe,g):
fill(train,i,j)
train.info()
#ord_0~4는 대세값 처리
train.head()
문제는 nom_5~nom9인데, 이 녀석들이 골칫거리다.
nan을 대세값으로 대체 하기에는 너무 고유값이 많다. 그렇지만 다 날려버리기에도 애매하다.
이럴 땐 어떻게 해야 할까? 찾아보니 결측값 예측이라는 게 있다고 한다.
결측값이 없는 데이터들로 학습한 모델을 활용하여 예측을 시키는 것이라고 한다.
# 결측값 예측
missing_df = df.loc[df["target_variable"].isna()]
predicted_values = model.predict(missing_df[related_variables])
# 결측값 대체
missing_df["target_variable"] = predicted_values
# 결측값이 대체된 데이터 저장
missing_df.to_csv("imputed_data.csv")
그러나 아직 배우지 않았으니, nom_5~nom_9은 그대로 두고 일단 결과를 봐보자.
어떻게 나왔을까?
version 2가 아무것도 안한 데이터, version 4가 이번에 바꾼 데이터이다.
정말 충격적이게도 version4가 더 안나왔다..
여기서 우리는 대략 2가지를 배울 수 있다.
1. 아무렇게나 최빈값으로 대체를 하게 되면, 오히려 예측값이 떨어지는 결과가 나온다.
2. 결측값 처리를 제대로 해주지 않으면 오히려 기본보다도 못하다.
또한 이 뜻은 아무것도 안한 베이스라인 모델만 해도 엄청 잘 예측하는 편이라는 소리다. 따라서 다음 시간에는 아무것도 안한 베이스라인 모델을 구성하고, 그걸 토대로 한 번 예측을 해보도록 하자.