머신러닝·딥러닝 문제해결 전략 책을 읽으면서
Kaggle 경진대회 코드와 문제해결 전략을 정리한 글
import pandas as pd
# 데이터 경로
data_path = '/kaggle/input/porto-seguro-safe-driver-prediction/'
train = pd.read_csv(data_path + 'train.csv', index_col='id')
test = pd.read_csv(data_path + 'test.csv', index_col='id')
submission = pd.read_csv(data_path + 'sample_submission.csv', index_col='id')
train.shape, test.shape
((595212, 58), (892816, 57))
훈련 데이터는 약 59만개, 테스트 데이터는 약 89만개로 훈련 데이터보다 테스트 데이터가 더 많음.
타깃값을 제외하면 피처는 총 57개.
지금까지의 경진대회에 비해 데이터가 크고 피처 수도 많음
train.head()
target | ps_ind_01 | ps_ind_02_cat | ps_ind_03 | ps_ind_04_cat | ps_ind_05_cat | ps_ind_06_bin | ps_ind_07_bin | ps_ind_08_bin | ps_ind_09_bin | ... | ps_calc_11 | ps_calc_12 | ps_calc_13 | ps_calc_14 | ps_calc_15_bin | ps_calc_16_bin | ps_calc_17_bin | ps_calc_18_bin | ps_calc_19_bin | ps_calc_20_bin | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||||||||||
7 | 0 | 2 | 2 | 5 | 1 | 0 | 0 | 1 | 0 | 0 | ... | 9 | 1 | 5 | 8 | 0 | 1 | 1 | 0 | 0 | 1 |
9 | 0 | 1 | 1 | 7 | 0 | 0 | 0 | 0 | 1 | 0 | ... | 3 | 1 | 1 | 9 | 0 | 1 | 1 | 0 | 1 | 0 |
13 | 0 | 5 | 4 | 9 | 1 | 0 | 0 | 0 | 1 | 0 | ... | 4 | 2 | 7 | 7 | 0 | 1 | 1 | 0 | 1 | 0 |
16 | 0 | 0 | 1 | 2 | 0 | 0 | 1 | 0 | 0 | 0 | ... | 2 | 2 | 4 | 9 | 0 | 0 | 0 | 0 | 0 | 0 |
17 | 0 | 0 | 2 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | ... | 3 | 1 | 1 | 3 | 0 | 0 | 0 | 1 | 1 | 0 |
5 rows × 58 columns
test.head()
ps_ind_01 | ps_ind_02_cat | ps_ind_03 | ps_ind_04_cat | ps_ind_05_cat | ps_ind_06_bin | ps_ind_07_bin | ps_ind_08_bin | ps_ind_09_bin | ps_ind_10_bin | ... | ps_calc_11 | ps_calc_12 | ps_calc_13 | ps_calc_14 | ps_calc_15_bin | ps_calc_16_bin | ps_calc_17_bin | ps_calc_18_bin | ps_calc_19_bin | ps_calc_20_bin | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||||||||||
0 | 0 | 1 | 8 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | ... | 1 | 1 | 1 | 12 | 0 | 1 | 1 | 0 | 0 | 1 |
1 | 4 | 2 | 5 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | ... | 2 | 0 | 3 | 10 | 0 | 0 | 1 | 1 | 0 | 1 |
2 | 5 | 1 | 3 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | ... | 4 | 0 | 2 | 4 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 1 | 6 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | ... | 5 | 1 | 0 | 5 | 1 | 0 | 1 | 0 | 0 | 0 |
4 | 5 | 1 | 7 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | ... | 4 | 0 | 0 | 4 | 0 | 1 | 1 | 0 | 0 | 1 |
5 rows × 57 columns
submission.head()
target | |
---|---|
id | |
0 | 0.0364 |
1 | 0.0364 |
2 | 0.0364 |
3 | 0.0364 |
4 | 0.0364 |
타깃값 확률은 0.0364로 일괄 입력되어 있음.
7장과 마찬가지로 본 경진대회에서 예측해야 하는 값은 '타깃값이 1일 확률'임.
타깃값 0은 운전자가 보험금을 청구하지 않는 경우, 타깃값 1은 청구하는 경우를 의미.
train.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 595212 entries, 7 to 1488027
Data columns (total 58 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 target 595212 non-null int64
1 ps_ind_01 595212 non-null int64
2 ps_ind_02_cat 595212 non-null int64
3 ps_ind_03 595212 non-null int64
4 ps_ind_04_cat 595212 non-null int64
5 ps_ind_05_cat 595212 non-null int64
6 ps_ind_06_bin 595212 non-null int64
7 ps_ind_07_bin 595212 non-null int64
8 ps_ind_08_bin 595212 non-null int64
9 ps_ind_09_bin 595212 non-null int64
10 ps_ind_10_bin 595212 non-null int64
11 ps_ind_11_bin 595212 non-null int64
12 ps_ind_12_bin 595212 non-null int64
13 ps_ind_13_bin 595212 non-null int64
14 ps_ind_14 595212 non-null int64
15 ps_ind_15 595212 non-null int64
16 ps_ind_16_bin 595212 non-null int64
17 ps_ind_17_bin 595212 non-null int64
18 ps_ind_18_bin 595212 non-null int64
19 ps_reg_01 595212 non-null float64
20 ps_reg_02 595212 non-null float64
21 ps_reg_03 595212 non-null float64
22 ps_car_01_cat 595212 non-null int64
23 ps_car_02_cat 595212 non-null int64
24 ps_car_03_cat 595212 non-null int64
25 ps_car_04_cat 595212 non-null int64
26 ps_car_05_cat 595212 non-null int64
27 ps_car_06_cat 595212 non-null int64
28 ps_car_07_cat 595212 non-null int64
29 ps_car_08_cat 595212 non-null int64
30 ps_car_09_cat 595212 non-null int64
31 ps_car_10_cat 595212 non-null int64
32 ps_car_11_cat 595212 non-null int64
33 ps_car_11 595212 non-null int64
34 ps_car_12 595212 non-null float64
35 ps_car_13 595212 non-null float64
36 ps_car_14 595212 non-null float64
37 ps_car_15 595212 non-null float64
38 ps_calc_01 595212 non-null float64
39 ps_calc_02 595212 non-null float64
40 ps_calc_03 595212 non-null float64
41 ps_calc_04 595212 non-null int64
42 ps_calc_05 595212 non-null int64
43 ps_calc_06 595212 non-null int64
44 ps_calc_07 595212 non-null int64
45 ps_calc_08 595212 non-null int64
46 ps_calc_09 595212 non-null int64
47 ps_calc_10 595212 non-null int64
48 ps_calc_11 595212 non-null int64
49 ps_calc_12 595212 non-null int64
50 ps_calc_13 595212 non-null int64
51 ps_calc_14 595212 non-null int64
52 ps_calc_15_bin 595212 non-null int64
53 ps_calc_16_bin 595212 non-null int64
54 ps_calc_17_bin 595212 non-null int64
55 ps_calc_18_bin 595212 non-null int64
56 ps_calc_19_bin 595212 non-null int64
57 ps_calc_20_bin 595212 non-null int64
dtypes: float64(10), int64(48)
memory usage: 267.9 MB
ps_[분류]_[분류별 일련번호]_[데이터 종류]
import numpy as np
import missingno as msno
# 훈련 데이터 복사본에서 -1을 np.NaN로 변환
train_copy = train.copy().replace(-1, np.NaN)
# 결측값 시각화(처음 28개만)
msno.bar(df=train_copy.iloc[:, 1:29], figsize=(13, 6))
<AxesSubplot:>
막대 그래프 높이가 낮을수록 결측값이 많다는 뜻
그래프 아래 피처명이 기재되어 있고 그래프 위에는 정상 값이 몇 개인지 표시되어 있음.
ps_reg_03
, ps_car_03_cat
, ps_car_05_cat
피처에 결측값이 많음
# 나머지 피처들의 결측값
msno.bar(df=train_copy.iloc[:, 29:], figsize=(13, 6))
<AxesSubplot:>
ps_car_14
에 결측값이 조금 있고 나머지 피처에는 없음
결측값 매트릭스 형태로 시각화하기
bar() 대신 matrix() 함수를 사용하면 결측값을 매트릭스 형태로 시각화할 수도 있음
msno.matrix(df=train_copy.iloc[:, 1:29], figsize=(13, 6))
<AxesSubplot:>
오른쪽 막대는 결측값의 상대적인 분포를 보여줌
검은색으로 뾰족하게 튀어나온 부분이 결측값이 몰려있는 행을 의미함
왼쪽에 표시된 22는 결측값이 없는 열 개수를, 오른쪽의 28은 전체 열 개수를 뜻함
def resumetable(df):
print(f'데이터셋 형상: {df.shape}')
summary = pd.DataFrame(df.dtypes, columns=['데이터 타입'])
summary['결측값 개수'] = (df == -1).sum().values # 피처별 -1 개수
summary['고윳값 개수'] = df.nunique().values
summary['데이터 종류'] = None
for col in df.columns:
if 'bin' in col or col == 'target':
summary.loc[col, '데이터 종류'] = '이진형'
elif 'cat' in col:
summary.loc[col, '데이터 종류'] = '명목형'
elif df[col].dtype == float:
summary.loc[col, '데이터 종류'] = '연속형'
elif df[col].dtype == int:
summary.loc[col, '데이터 종류'] = '순서형'
return summary
summary = resumetable(train)
summary
데이터셋 형상: (595212, 58)
데이터 타입 | 결측값 개수 | 고윳값 개수 | 데이터 종류 | |
---|---|---|---|---|
target | int64 | 0 | 2 | 이진형 |
ps_ind_01 | int64 | 0 | 8 | 순서형 |
ps_ind_02_cat | int64 | 216 | 5 | 명목형 |
ps_ind_03 | int64 | 0 | 12 | 순서형 |
ps_ind_04_cat | int64 | 83 | 3 | 명목형 |
ps_ind_05_cat | int64 | 5809 | 8 | 명목형 |
ps_ind_06_bin | int64 | 0 | 2 | 이진형 |
ps_ind_07_bin | int64 | 0 | 2 | 이진형 |
ps_ind_08_bin | int64 | 0 | 2 | 이진형 |
ps_ind_09_bin | int64 | 0 | 2 | 이진형 |
ps_ind_10_bin | int64 | 0 | 2 | 이진형 |
ps_ind_11_bin | int64 | 0 | 2 | 이진형 |
ps_ind_12_bin | int64 | 0 | 2 | 이진형 |
ps_ind_13_bin | int64 | 0 | 2 | 이진형 |
ps_ind_14 | int64 | 0 | 5 | 순서형 |
ps_ind_15 | int64 | 0 | 14 | 순서형 |
ps_ind_16_bin | int64 | 0 | 2 | 이진형 |
ps_ind_17_bin | int64 | 0 | 2 | 이진형 |
ps_ind_18_bin | int64 | 0 | 2 | 이진형 |
ps_reg_01 | float64 | 0 | 10 | 연속형 |
ps_reg_02 | float64 | 0 | 19 | 연속형 |
ps_reg_03 | float64 | 107772 | 5013 | 연속형 |
ps_car_01_cat | int64 | 107 | 13 | 명목형 |
ps_car_02_cat | int64 | 5 | 3 | 명목형 |
ps_car_03_cat | int64 | 411231 | 3 | 명목형 |
ps_car_04_cat | int64 | 0 | 10 | 명목형 |
ps_car_05_cat | int64 | 266551 | 3 | 명목형 |
ps_car_06_cat | int64 | 0 | 18 | 명목형 |
ps_car_07_cat | int64 | 11489 | 3 | 명목형 |
ps_car_08_cat | int64 | 0 | 2 | 명목형 |
ps_car_09_cat | int64 | 569 | 6 | 명목형 |
ps_car_10_cat | int64 | 0 | 3 | 명목형 |
ps_car_11_cat | int64 | 0 | 104 | 명목형 |
ps_car_11 | int64 | 5 | 5 | 순서형 |
ps_car_12 | float64 | 1 | 184 | 연속형 |
ps_car_13 | float64 | 0 | 70482 | 연속형 |
ps_car_14 | float64 | 42620 | 850 | 연속형 |
ps_car_15 | float64 | 0 | 15 | 연속형 |
ps_calc_01 | float64 | 0 | 10 | 연속형 |
ps_calc_02 | float64 | 0 | 10 | 연속형 |
ps_calc_03 | float64 | 0 | 10 | 연속형 |
ps_calc_04 | int64 | 0 | 6 | 순서형 |
ps_calc_05 | int64 | 0 | 7 | 순서형 |
ps_calc_06 | int64 | 0 | 11 | 순서형 |
ps_calc_07 | int64 | 0 | 10 | 순서형 |
ps_calc_08 | int64 | 0 | 11 | 순서형 |
ps_calc_09 | int64 | 0 | 8 | 순서형 |
ps_calc_10 | int64 | 0 | 26 | 순서형 |
ps_calc_11 | int64 | 0 | 20 | 순서형 |
ps_calc_12 | int64 | 0 | 11 | 순서형 |
ps_calc_13 | int64 | 0 | 14 | 순서형 |
ps_calc_14 | int64 | 0 | 24 | 순서형 |
ps_calc_15_bin | int64 | 0 | 2 | 이진형 |
ps_calc_16_bin | int64 | 0 | 2 | 이진형 |
ps_calc_17_bin | int64 | 0 | 2 | 이진형 |
ps_calc_18_bin | int64 | 0 | 2 | 이진형 |
ps_calc_19_bin | int64 | 0 | 2 | 이진형 |
ps_calc_20_bin | int64 | 0 | 2 | 이진형 |
# 피처 요약표에서 명목형 피처 추출
summary[summary['데이터 종류'] == '명목형'].index
Index(['ps_ind_02_cat', 'ps_ind_04_cat', 'ps_ind_05_cat', 'ps_car_01_cat',
'ps_car_02_cat', 'ps_car_03_cat', 'ps_car_04_cat', 'ps_car_05_cat',
'ps_car_06_cat', 'ps_car_07_cat', 'ps_car_08_cat', 'ps_car_09_cat',
'ps_car_10_cat', 'ps_car_11_cat'],
dtype='object')
# 피처 요약표에서 데이터 타입이 실수형인 피처 추출
summary[summary['데이터 타입'] == 'float64'].index
Index(['ps_reg_01', 'ps_reg_02', 'ps_reg_03', 'ps_car_12', 'ps_car_13',
'ps_car_14', 'ps_car_15', 'ps_calc_01', 'ps_calc_02', 'ps_calc_03'],
dtype='object')
import seaborn as sns
import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline
def write_percent(ax, total_size):
'''도형 객체를 순회하며 막대 그래프 상단에 타깃값 비율 표시'''
for patch in ax.patches:
height = patch.get_height() # 도형 높이 (데이터 개수)
width = patch.get_width() # 도형 너비
left_coord = patch.get_x() # 도형 왼쪽 테두리의 x축 위치
percent = height/total_size*100 # 타깃값 비율
# (x, y) 좌표에 텍스트 입력
ax.text(left_coord + width/2.0, # x축 위치
height + total_size*0.001, # y축 위치
'{:1.1f}%'.format(percent), # 입력 텍스트
ha='center') # 가운데 정렬
mpl.rc('font', size=15)
plt.figure(figsize=(7, 6))
ax = sns.countplot(x='target', data=train)
write_percent(ax, len(train)) # 비율 표시
ax.set_title('Target Distribution')
Text(0.5, 1.0, 'Target Distribution')
타깃값 0은 96.4%를 차지하며 1은 단 3.6%만을 차지함.
전체 운전자 중 3.6%만 보험금을 청구했다는 뜻.
→ 타깃값 불균형
타깃값이 불균형하기 때문에 비율이 작은 타깃값 1을 잘 예측하는 게 중요함
따라서 이번에는 각 피처의 분포를 알아보기 보다는, 각 피처의 고윳값별 타깃값 1 비율을 알아보도록 함
고윳값별 타깃값 1 비율을 통해 해당 피처가 모델링에 필요한 피처인지 확인할 수 있음
예컨대 피처 A에 고윳값 a, b가 있다고 하면 이때 고윳값 a, b별로 타깃값 1 비율이 얼마나 되는지 살펴보려는 것
고윳값별로 타깃값 1 비율이 똑같거나 통계적 유효성이 떨어지면, 즉 통계적으로 유의미한 차이가 없다면 피처 A로는 무언가를 분별하기 어려우므로 예측에 도움되지 않음
다시 말해, 고윳값에 따라 타깃값 비율이 달라야 그 피처가 타깃값 예측에 도움을 줌
타깃값 1 비율의 통계적 유효성이 떨어져도 불필요한 피처가 될 수 있음
통계적 유효성은 barplot()을 그릴 때 나타나는 신뢰구간으로 판단함
신뢰구간이 좁다면 통계적으로 어느 정도 유효하다고 보고, 구간이 넓다면 신뢰하기 어렵다고 보는 것
통계적 유효성이 높아야(신뢰구간이 좁아야) 모델링에 도움이 됨
종합하면, 고윳값별 타깃값 1 비율이 충분히 차이가 나고 신뢰구간도 작은 피처여야 모델링에 도움이 됨
그렇지 않은 피처는 제거하는 게 좋음
import matplotlib.gridspec as gridspec
def plot_target_ratio_by_features(df, features, num_rows, num_cols, size=(12, 18)):
mpl.rc('font', size=9)
plt.figure(figsize=size) # 전체 Figure 크기 설정
grid = gridspec.GridSpec(num_rows, num_cols) # 서브플롯 배치
plt.subplots_adjust(wspace=0.3, hspace=0.3) # 서브플롯 좌우/상하 여백 설정
for idx, feature in enumerate(features):
ax = plt.subplot(grid[idx])
# ax축에 고윳값별 타깃값 1 비율을 막대 그래프로 그리기
sns.barplot(x=feature, y='target', data=df, palette='Set2', ax=ax)
bin_features = summary[summary['데이터 종류'] == '이진형'].index # 이진 피처
# 이진 피처 고윳값별 타깃값 1 비율을 막대 그래프로 그리기
plot_target_ratio_by_features(train, bin_features, 6, 3) # 6행 3열 배치
ps_ind_06_bin 피처의 고윳값별 타깃값 비율
고윳값 | 타깃값 1 비율 | 타깃값 0 비율 |
---|---|---|
0 | 4% | 96% |
1 | 2.8% | 97.2% |
ps_ind_06_bin 피처는 고윳값별로 타깃값 비율이 다르므로 타깃값을 추정하는 예측력이 있음. 게다가 신뢰구간도 좁기 때문에 모델링에 도움이 되는 피처.
제거해야 할 이진 피처
서브플롯 위치 | 피처명 | 제거해야 하는 이유 |
---|---|---|
1행 2열~2행 2열 | ps_ind_10_bin ~ ps_ind_13_bin | 신뢰구간이 넓어 통계적 유효성이 떨어짐 |
4행 0열~5행 2열 | ps_calc_15_bin ~ ps_calc_20_bin | 고윳값별 타깃값 비율 차이가 없어 타깃값 예측력이 없음 |
여기서 주목할 점은 calc 분류의 이진 피처는 모두 타깃값 비율에 차이가 없다는 사실
신뢰구간이 얼마나 넓어야 제거할 피처로 볼지, 고윳값별 타깃값 비율이 얼마나 달라야 제거할 피처로 볼지는 사람마다 다르게 판단할 수 있음.
또한 제거할 피처를 찾기 위한 다른 방법도 많으므로 이런 논리로 제거할 피처를 골라낼 수 있다는 정도로 알아두기
nom_features = summary[summary['데이터 종류'] == '명목형'].index # 명목형 피처
plot_target_ratio_by_features(train, nom_features, 7, 2) # 7행 2열
ps_ind_02_cat
피처의 경우 결측값 -1이 다른 고윳값들보다 타깃값 1 비율이 큼. ps_car_02_cat
피처의 경우 -1일 때 타깃값 1 비율은 0%ps_car_02_cat
피처 값이 -1이면 타깃값이 0이라고 판단할 수 있음→ 결측값 자체에 타깃값 예측력이 있다면 고윳값으로 간주 (결측값 처리 필요 없음)
명목형 피처 중 제거할 피처는 없음
ps_ind_02_cat
, ps_ind_04_cat
, ps_cat_01_cat
그래프를 보면 ps_car_10_cat
피처의 경우ord_features = summary[summary['데이터 종류'] == '순서형'].index # 순서형 피처
plot_target_ratio_by_features(train, ord_features, 8, 2, (12, 20)) # 8행 2열
ps_ind_14
피처 그래프를 보면 고윳값 0, 1, 2, 3의 타깃값 비율은 큰 차이가 없고, 고윳값 4의 신뢰구간은 넓음ps_ind_14
피처는 제거ps_calc_04
부터 ps_calc_14
까지는 모두 고윳값별 타깃값 비율이 거의 비슷함제거해야 할 순서형 피처
서브플롯 위치 | 피처명 | 제거해야 하는 이유 |
---|---|---|
1행 0열 | ps_ind_14 | 타깃값 비율의 신뢰구간이 넓어 통계적 유효성이 떨어짐 |
2행 1열~7행 1열 | ps_calc_04 ~ ps_calc_14 | 고윳값별 타깃값 비율 차이가 없음 타깃값 비율이 다르더라도 신뢰구간이 넓어 통계적 유효성이 떨어짐 |
# 판다스의 cut() 함수를 활용하여 여러 개의 값을 3개 구간으로 나누는 예
# 함수의 첫 번째 인수가 값들의 리스트이며, 두 번째 인수가 나눌 구간의 개수
pd.cut([1.0, 1.5, 2.1, 2.7, 3.5, 4.0], 3)
[(0.997, 2.0], (0.997, 2.0], (2.0, 3.0], (2.0, 3.0], (3.0, 4.0], (3.0, 4.0]]
Categories (3, interval[float64, right]): [(0.997, 2.0] < (2.0, 3.0] < (3.0, 4.0]]
cont_features = summary[summary['데이터 종류'] == '연속형'].index # 연속형 피처
plt.figure(figsize=(12, 26)) # Figure 크기 설정
grid = gridspec.GridSpec(5, 2) # GridSpec 객체 생성
plt.subplots_adjust(wspace=0.2 , hspace=0.4) # 서브플롯 간 여백 설정
for idx, cont_feature in enumerate(cont_features):
# 값을 5개 구간으로 나누기
train[cont_feature] = pd.cut(train[cont_feature], 5)
ax = plt.subplot(grid[idx]) # 분포도를 그릴 서브플롯 설정
sns.barplot(x=cont_feature, y='target', data=train, palette='Set2', ax=ax)
ax.tick_params(axis='x', labelrotation=10) # x축 라벨 회전
제거해야 할 연속형 피처
서브플롯 위치 | 피처명 | 제거해야 하는 이유 |
---|---|---|
3행 1열~4행 1열 | ps_calc_01~ps_calc_03 | 구간별 타깃값 비율 차이가 없음 |
상관관계가 얼마나 강한 피처를 제거해야 하는가?
상관계수가 얼마 이상일 때 제거하는 게 좋은지 정해진 기준은 없으며 상황과 모델에 따라 다름
더불어, 강한 상관관계를 보이는 두 피처 중 하나를 제거한다고 해서 반드시 성능이 향상되는 것도 아님
'고려해볼 만한 요소'정도로 생각하면 되고 상관계수가 0.8 이상이라면 제거를 고려해보는 것도 좋은 방법
피처 간 상관관계를 파악하기 위해 히트맵 그려보기
train_copy = train_copy.dropna() # np.NaN 값 삭제
plt.figure(figsize=(10, 8))
cont_corr = train_copy[cont_features].corr() # 연속형 피처 간 상관관계
sns.heatmap(cont_corr, annot=True, cmap='OrRd') # 히트맵 그리기
<AxesSubplot:>
ps_car_12
와 ps_car_14
피처 간 상관계수는 0.77ps_cat_14
피처를 제거하니 성능이 더 좋아짐ps_cat_14
피처를 추가로 제거하도록 함ps_reg_02
와 ps_reg_03
피처 간 상관계수는 0.76ps_ind_14
, ps_calc_04
~ps_calc_14
)ps_calc_04
~ps_calc_14
)ps_calc_01
~ps_calc_03
)ps_car_14
)이번 장의 목표는 캐글에서 실제로 많이 활용되는 여러 가지 고급 모델링 기법 배우기
주요 키워드는 OOF 예측, 베이지안 최적화, LightGBM, XGBoost, 앙상블