안녕하세요.
새벽 5시 캐글 필사하기 3주차입니다.
사실 캐글 필사는 계속 하고 있는데 블로그에 올리기가 너무 힘드네요
요즘 바빠서 퇴근 시간이 늦다 보니(집오면 10시 ㅠ) 블로그에 올릴 시간이 없네요 ㅠ
3주차 주제는 porto 데이터 셋으로 진행합니다. 안전하게 운전을 하는 운전자를 예측하는 데이터입니다.
데이터는 https://www.kaggle.com/c/porto-seguro-safe-driver-prediction 에 있습니다.
이번 주제도 1, 2주차에 걸쳐서 진행합니다. 1주차는 먼저 머신러닝 탐구생활이라는 책의 EDA 과정을 볼 것이고, 좀 이해하기 힘든 커널을 1개 필사했습니다.(아직도 이해가 안갑니다…)
시작해봅니다!
머신러닝 탐구생활 책을 기준으로 진행합니다.
역시 데이터부터 살펴보기 위해 numpy, pandas, matplotlib, seaborn 라이브러리를 import 합니다.
그리고 여기서는 gini 계수라는 것을 이용해서 예측을 진행합니다. Gini 계수 예측 설명까지 쓰기에는… 너무 바빠서 ㅠ.. 아무튼 저런 식을 이용해서 예측을 시도합니다.
그리고 위에서 나온 배열 값을 가지고 gini 계수를 예측하면 값이 나오게 됩니다.
이제 본격적으로 데이터를 살펴보죠. 기초적인 통계로 살펴봅니다.
이 데이터 셋의 특이한 점은 nan 값이 -1로 처리가 되어 있습니다. 그리고 feature 이름들이 전부 익명화 되어 있습니다. 아마도 개인정보를 보호하기 위해서 그렇게 제공된 것 같습니다. 하지만 여러 특징을 살펴볼 수 있습니다.
Nan 값이 -1로 되었기 때문에 pd.read_csv할 때 na_values를 지정해서 가져옵니다. 그러면 -1값이 np.nan과 같은 값으로 변환되어집니다.
Shape를 통해 보면 train보다 test에 데이터가 많습니다. test에는 target이 없기에 feature 계수가 하나 줄어있죠.
그리고 head를 보면 id, target, ps_ind_01, ps_ind_05_cat과 같이 완전 feature가 익명화 되어 있습니다.
train.info()를 통해 살펴보면 feature들과 정보를 볼 수 있는데요. 모든 값이 int 혹은 float로 되어 있습니다.
그리고 데이터도 깔끔하게 정제되어 있구요. 그리고 특성 이름을 보면 bin, cat과 같은 것들이 있는데요.
이거는 binary, category를 뜻합니다. 그래서 feature가 정확히 무엇을 말하는지는 알 수 없지만, 어떤 feature가 category형 feature인지, binary 특성인지는 알 수 있습니다.
그리고 이 데이터 셋은 매우 불균형합니다. Target은 0과 1이있는데 1의 계수가 전체의 3.6%밖에 되지 않습니다.
그래서 upsampling이나 downsampling 과정이 약간은 필요합니다.
isnull.sum()을 통해 보면 nan 값의 합을 볼 수 있습니다. 아까 -1를 nan 값으로 대체 했기에 이렇게 확인할 수 있습니다.
이제 시각화를 통해 데이터를 살펴봅니다. Train과 test를 합쳐서 봐야하니까 test에 없는 target을 np.nan으로 채워주고 pd.concat을 이용해 데이터를 합쳐줍니다.
그리고 3개의 함수를 만드는데요. bar_plot는 컬럼과 data, hue를 받아서 hue값에 따른 seaborn의 countplot을 보여줍니다.
dist_plot은 히스토그램 그래프를 그려줍니다. 그래서 data[col]을 이용해서 해당 컬럼의 히스토그램을 보여줍니다.
bar_plot_ci는 target을 기준으로 컬럼 값과 target의 관계를 살펴봅니다.
그리고 미리 이렇게 이진(binary)변수, 카테고리(category) 변수, 정수형(integer) 변수, 소수형(float) 변수를 나누었습니다.
자 이제 본격적으로 그래프를 그려봅니다. 먼저 binary variable입니다.
For 문을 이용해서 col값을 bar_plot에 보내서 count를 세봅니다. 처음에 나오는 애들은 0이 많네요.
그리고 밑으로 가니까 1은 아예 없는 것도 있고 0과 차이가 많이 나는 애들도 있습니다. 어떤건 1이 더 많구요.
그리고 category 변수를 보죠. Binary와 마찬가지로 for문을 이용해서 봐봅니다.
이렇게 어떤 카테고리가 많은지 손 쉽게 볼 수 있습니다.
그리고 ps_car_11_cat같은 경우는 category값이 매우 많네요!
다음은 integer variable입니다. 마찬가지로 for문을 이용해서 확인합니다.
Integer 변수들은 보니까 한 쪽에 쏠려 있는 모습들을 볼 수 있네요
마찬가지로 float 변수를 봅니다.
다음은 상관관계 분석(corr)을 보죠. Feature가 많다 보니 어떤 것이 어떤 것인지 잘 보기가 힘든데요. 저 ps_car_10_cat 부터 특징을 따로 뽑아내서 또 보시면 보기 편하실 겁니다.
상관 관계 분석은 pandas.corr()을 이용해서 seaborn의 heatmap을 사용해 넣어줍니다.
이제 중요한 target 변수와의 관계를 살펴봅니다. 마찬가지로 binary부터 integer까지 살펴봅니다.
먼저 binary입니다. 첫 변수는 0이 target과 더 관계가 높네요.
하지만 여길 보시면 ps_calc_로 시작하는 애들은 별로 target값과 특별한 무엇인가를 찾기가 힘듭니다.
0, 1 둘 다 target 값과 연관이 거의 같기 때문이죠.
다음으로 category와 target 값도 살펴볼 수 있습니다.
그리고 integer 변수들과 target 값의 관계도 살펴볼 수 있습니다.
근데 여기서도 ps_calc_라는 변수는 썩 좋아 보이지 않습니다. 저 검은색 막대기가 95% 신뢰구간? 그런 것을 뜻한다고 하는데요.
한쪽에 쏠려서 하나만 엄청 큽니다. 별로 좋지 않다는 것을 뜻한다고 하네요.
이제는 unbalance한 데이터를 살펴봅니다. 서두에서 말씀 드렸듯이 이 데이터 셋은 매우 불균형합니다.
0이 너무 많죠. 1은 얼마 되지도 않습니다. 이런 상황에서 단순히 accuracy(정확도)로 측정하려고 하면 별로 정확하지 않은 모델입니다.
왜냐면 그냥 이건 0이야~ 라고 만 생각해도 정확도가 90%가 넘어버리기 때문이죠. 대부분 0이니까요. 테스트를 해볼까요?
단순히 데이터를 sklearn.model_selection의 train_test_split을 이용해서 분할한 뒤 훈련을 시킵니다.
정확도가 96%가 나오죠. 말도 안되는 것입니다.
그래서 sklearn.metrics에 있는 confustion_matrix를 살펴보면 0, 0 인 지점만 쏠려있는 것을 볼 수 있습니다.
그래서 이럴 때는 보통 f1-score 같은 것을 많이 사용합니다.
아무튼 이러한 이유로 upsampling이나 downsampling 과정이 필요한데요. 그것을 살펴봅니다.
Cojnt_class_0, 1을 train.taget.value_counts()값으로 가져옵니다.
그리고 df_class_0에 train[train[‘target’] == 0 ]인 애를 가져오는 식으로 따로 데이터를 뽑아냅니다.
그리고 downsampling을 봐봅니다. Downsampling은 데이터 개수가 많은 애들을 down 시키는 방법입니다.
방법은 class_1의 개수만큼 sample을 랜덤하게 뽑아내는 것이죠. 매우 간단하죠?
그래서 데이터가 적은 1의 개수와 맞춰주는 방식이 downsampling입니다. 그래프에서 보시면 개수가 같아졌죠.
근데 저는 downsampling보다 upsampling이 좋다고 개인적으로는 생각합니다. 왜냐하면 머신러닝, 딥러닝 쪽에서는 데이터 개수도 매우 중요한데 그 데이터 개수를 줄이면 성능이 좋지 않아지기 때문이죠. 그래서 upsampling 방법이 좋은 것 같더군요.
여기 예제에서는 upsampling을 극단적으로 많이 했는데요. 그냥 단순히 2만개면 2만개만 더 생성하는 방법을 사용만 해도 효과를 보실 수 있습니다.
Upsampling은 class_1에서 sample을 뽑아내는데요 count_class_0 개수만큼 뽑아냅니다. 그러면 개수가 더 많아지겠죠? 그 결과를 보면 둘 다 57만개로 데이터가 동일해졌습니다.
이렇게 극단적으로 하지 않으셔도 됩니다.
자 이제 본격적인 필사를 해보겠습니다.
이 커널을 필사 했는데요. 솔직히 좀 어려웠습니다. 아직까지도 이해가 잘 안되구요. Porto도 2개의 커널을 필사를 했는데요. 2번째는 무난하지만 이 첫 번째 커널을 진짜 잘 모르겠습니다.. ㅠ
아무튼 보죠
먼저 지니 계수를 정의합니다. 맨 처음에 설명 드렸던 부분입니다.
그리고 noise를 더하는 부분과 target_encode 함수인데요. 이 target_encode가 뭘 하는 것인지 이해하기가 힘들더군요..
다른 커널을 보니까 저 2개의 함수는 어떤 잘하시는 분이 만들어놓고 그냥 다른 분들이 복사해서 가져다가 쓰시는 것 같기도 했습니다.
이 함수는 나중에 category형 데이터를 일종의 변환? 작업을 해주는 함수입니다. 정확한 설명은 잘 모르겠습니다. ㅠ
그리고 train과 test 데이터를 불러옵니다. 또한 여기에서는 미리 features를 정해 놓았습니다.
아래 빨간색 줄로 표시를 해놨는데요. 저 해당 커널에서 이 feature를 미리 정리를 해주신 것 같습니다.
그래서 많은 커널들이 이 feature를 그대로 복사해서 가져다가 씁니다. Target_encode 함수처럼요.
그리고 comb를 기준으로 enumerate를 돌면서 데이터 전처리를 진행합니다.
Reg와 cat값을 string으로 바꾸고 이것을 합칩니다. 이후 라벨 인코더를 사용해서 라벨링 시켜줘서 새로운 feature를 만들어줍니다.
그리고 train_features에다가 더해주는데요. Train_features는 앞서 정의한 그 feature값들 입니다.
그리고 _cat이 있는 컬럼 값 애들의 목록을 가지고 옵니다.
[f for f in trn.columns if “_cat” in f] 이런 식으로 category 컬럼을 뽑아낼 수 있습니다.
자, 이제 아까 정의했던 target_encode 함수를 사용합니다. _avg의 컬럼 명을 f_ + “_avg”로 바꾸면서 그 return 값을 받습니다. 그래서 새로운 컬럼이 추가됩니다. 일종의 특성 공학이죠. 무슨 원리인지는 모르겠지만요 ㅠ
위에서 pd_ind_04_cat을 찍어봤을 때는 0, 1, -1 등의 값이 있었죠? 근데 이제 그 값이 0.038~ 이런 식으로 바뀌었습니다. 여기서 신기한 것은 nan 값도 사용한다는 것이죠.
자 이제 훈련을 해야합니다. 특성별 데이터를 살펴보았고, target_encode 등의 작업도 진행했습니다. 남은 것은 훈련이죠. 여기서는 StratifiedKFold를 사용합니다. KFold 중에선 굉장히 유용한 방법이고 저도 많이 사용하는 방법입니다. 그리고 많은 캐글 커널에서 사용하죠.
KFold 개수는 5개입니다. 그리고 여기서 조금 아직 이해가 잘 가지 않으면서도 굉장히 좋은 방법을 사용합니다. Oof 라는 방법인데요. 각 split loop에서 모델을 훈련할 때마다 그때의 validation 의 예측 상태 값을 저장해두는 것입니다. 그래서 이 값들은 보통 np.empty나 np.zero 값을 넣어두고 예측했을 때 그 값을 그때그때 np.zero 등의 변수에 넣어주는 방법이죠. 최종적으로 예측할 때 이것을 이용하기도 합니다. 매우 좋은 방법이라는 것은 느껴지는데요. 아직까지 이해가 부족해서 경험이 더 쌓여야 할 것 같습니다. Imp_df는 feature_importance 값을 넣어주는 변수입니다. Xgb_evals는 validation의 gini 계수 예측 값입니다. Increase = True는 upsampling을 할 것인가의 flag를 뜻하죠.
이제 for문을 이용해서 split 만큼 enuermate를 사용해 loop를 돕니다. StraifiedKFold를 사용할 때 선언과 동시에 split을 써도 되지만 여기서는 folds.split(target, target)을 통해 루프를 진행합니다.
Trn_dat, trn_tgt, val_dat, val_tgt 등은 KFold를 할 때마다 나오는 train, validation index를 이용해서 값을 뽑아낸 것입니다.
그 다음 XGBClassifier를 선언합니다. 이제 increase가 나오는데요. 차근히 해석해보면 train에서 target 값이 1인 애들의 목록을 pd.Series로 가져옵니다. 그리고 train_dat와 tra_target에다가 pandas의 concat을 이용해서 데이터를 합쳐주는 것이죠. 근데 합쳐줄 때 기존의 데이터와 Series로 가져온 해당 데이터를 train.loc[pos]를 이용해서 합쳐줍니다. 그러면 target이 1인 애들이 한 번 더 들어갑니다. 아까 위에서 봤던 upsampling때 처럼 무식하게 예를 들면 지금 2만개이고 다른 target은 20만개라고 해서 2만개 -> 20만개로 맞춰주는 것이 아니라, 2만개를 한 번 더 늘려서 4만개로 만들어주는 것이죠! 그리고 난 후에 shuffle을 한 번 해주고 idx를 다시 셋팅합니다. 아무래도 concat으로 추가했기 때문에 shuffle이 필요하겠죠!
증가시키고 난 후 clf.fit(trn_dat, trn_tgt) 등을 이용해서 fit을 시켜줍니다. 이때 eval_metric를 아까 선언했던 gini 계수를 구하는 gini_xgb를 넣어줍니다.
이후에 특성 중요도 순으로 데이터를 넣어주고, gini 계수의 값을 뽑아낸 후에 가장 높은 지니 계수를 가져옵니다.
Oof[val_idx]에다가 현재 validation을 이용해 clf.predict_proba를 한 값들을 넣어줍니다. 이렇게 훈련을 진행합니다!
끝으로 mean_eval에다가 xgb_evals의 평균 값을 넣어줍니다. 그리고 std_eval에다가는 std 값을 넣어주고요. 또한, best_round에다가 mean_eval의 최 상위 애를 가져옵니다.
그리고 출력하면 best mean score는 0.28이 나옵니다!. 1등이 0.29698이니 꽤나 높은 점수죠!
이번 글은 꽤나 어려웠습니다. 글을 정리하면서도 제가 모르니까 잘 정리가 되지 않네요..
하지만 이 캐글을 필사하면서 배운 점은 있습니다. oof라는 것을 배운게 매우 큰 것 같습니다.
다음 포스트는 이거보다 훨씬 쉬운 커널이지만, 이거보다 점수가 높게 나오는 커널입니다!
4주차에서 해당 글을 작성하겠습니다.
'kaggle(캐글)' 카테고리의 다른 글
[5주차] 새벽 5시 캐글(kaggle) 필사하기 - home credit 데이터 편 - 1 (2) | 2019.02.03 |
---|---|
[4주차] 새벽 5시 캐글(kaggle) 필사하기 - porto 데이터 편 - 2 (0) | 2019.01.26 |
캐글을 하면서 겪은 이슈들(kernel stopping, timeout error) (0) | 2019.01.24 |
[2주차] 새벽 5시 캐글(kaggle) 필사하기 - 타이타닉 편_2 (0) | 2019.01.16 |
[1주차] 새벽 5시 캐글(kaggle) 필사하기 - 타이타닉(titanic) 편 (8) | 2019.01.12 |