[BoostCourse] 4. 피처 엔지니어링
[BoostCourse] 4. 피처 엔지니어링
1. 피처 엔지니어링
원본 데이터로부터 도메인 지식 등을 바탕으로 문제를 해결하는데 도움이 되는 Feature를 생성, 변환하고 이를 머신러닝 모델에 적합한 형식으로 변환하는 작업
일반 머신러닝에서는 성능을 올리는데 가장 중요한 핵심적인 단계
- 딥러닝은 모델의 구조를 통해 데이터의 피처를 모델이 스스로 추출하지만 (end-to-end learning)
- 머신러닝은 피처를 스스로 추출할 수 없기 때문에 사람이 직접 데이터를 이해해서 피처를 만들어야 함
1) groupby / aggregation
- 피처 엔지니어링 함수 정의
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
'''
입력인자로 받는 year_month와 변수 prev_ym 기준으로 train, test 데이터를 생성 하고
집계(aggregation) 함수를 사용하여 피처 엔지니어링을 하는 함수
'''
def feature_engineering1(df: pd.DataFrame, year_month: str):
df = df.copy()
# year_month 이전 월 계산
d = datetime.datetime.strptime(year_month, "%Y-%m")
prev_ym = d - dateutil.relativedelta.relativedelta(months=1)
prev_ym = prev_ym.strftime('%Y-%m')
# train, test 데이터 선택
train = df[df['order_date'] < prev_ym]
test = df[df['order_date'] < year_month]
# train, test 레이블 데이터 생성
train_label = generate_label(df, prev_ym)[['customer_id','year_month','label']]
test_label = generate_label(df, year_month)[['customer_id','year_month','label']]
# group by aggregation 함수 선언
agg_func = ['mean','max','min','sum','count','std','skew']
all_train_data = pd.DataFrame()
for i, tr_ym in enumerate(train_label['year_month'].unique()):
# group by aggretation 함수로 train 데이터 피처 생성
train_agg = train.loc[train['order_date'] < tr_ym].groupby(['customer_id']).agg(agg_func)
# 멀티 레벨 컬럼을 사용하기 쉽게 1 레벨 컬럼명으로 변경
new_cols = []
for col in train_agg.columns.levels[0]:
for stat in train_agg.columns.levels[1]:
new_cols.append(f'{col}-{stat}')
train_agg.columns = new_cols
train_agg.reset_index(inplace = True)
train_agg['year_month'] = tr_ym
all_train_data = all_train_data.append(train_agg)
all_train_data = train_label.merge(all_train_data, on=['customer_id', 'year_month'], how='left')
features = all_train_data.drop(columns=['customer_id', 'label', 'year_month']).columns
# group by aggretation 함수로 test 데이터 피처 생성
test_agg = test.groupby(['customer_id']).agg(agg_func)
test_agg.columns = new_cols
test_data = test_label.merge(test_agg, on=['customer_id'], how='left')
# train, test 데이터 전처리
x_tr, x_te = feature_preprocessing(all_train_data, test_data, features)
print('x_tr.shape', x_tr.shape, ', x_te.shape', x_te.shape)
return x_tr, x_te, all_train_data['label'], features
1) total-sum Feature
1
2
3
4
5
sample1 = df_all[(df_all['total-sum'] > -1000) & (df_all['total-sum'] < 5000)]
sns.distplot(sampel1.loc[sample1['label'] == 0, 'total-sum'], label = 'label=0')
sns.distplot(sampel1.loc[sample1['label'] == 1, 'total-sum'], label = 'label=1')
plt.legend()
plt.show()
total-sum
: label별 분포가 확연히 다르기 때문에 모델에서 사용하기 좋은 feature
2) quantity-sum feature
1
2
3
4
5
sample1 = df_all[(df_all['quantity-sum'] > 0) & (df_all['quantity-sum'] < 1500)]
sns.distplot(sampel1.loc[sample1['label'] == 0, 'quantity-sum'], label = 'label=0')
sns.distplot(sampel1.loc[sample1['label'] == 1, 'quantity-sum'], label = 'label=1')
plt.legend()
plt.show()
quantity-sum
: label별 분포가 확연히 다르기 때문에 모델에서 사용하기 좋은 feature
3) price-sum feature
1
2
3
4
5
sample1 = df_all[(df_all['price-sum'] > 0) & (df_all['price-sum'] < 1000)]
sns.distplot(sampel1.loc[sample1['label'] == 0, 'price-sum'], label = 'label=0')
sns.distplot(sampel1.loc[sample1['label'] == 1, 'price-sum'], label = 'label=1')
plt.legend()
plt.show()
price-sum
: label별 분포가 확연히 다르기 때문에 모델에서 사용하기 좋은 feature
4) total-count feature
1
2
3
4
5
sample1 = df_all[(df_all['total-count'] > 0) & (df_all['total-count'] < 200)]
sns.distplot(sampel1.loc[sample1['label'] == 0, 'total-count'], label = 'label=0')
sns.distplot(sampel1.loc[sample1['label'] == 1, 'total-count'], label = 'label=1')
plt.legend()
plt.show()
total-count
: label별 분포가 확연히 다르기 때문에 모델에서 사용하기 좋은 feature
5) quantity-count feature
1
2
3
4
5
sample1 = df_all[(df_all['quantity-count'] > 0) & (df_all['quantity-count'] < 200)]
sns.distplot(sampel1.loc[sample1['label'] == 0, 'quantity-count'], label = 'label=0')
sns.distplot(sampel1.loc[sample1['label'] == 1, 'quantity-count'], label = 'label=1')
plt.legend()
plt.show()
quantity-count
: label별 분포가 확연히 다르기 때문에 모델에서 사용하기 좋은 feature
6) price-std feature
1
2
3
4
5
sample1 = df_all[(df_all['price-std'] > 0) & (df_all['price-std'] < 10)]
sns.distplot(sampel1.loc[sample1['label'] == 0, 'price-std'], label = 'label=0')
sns.distplot(sampel1.loc[sample1['label'] == 1, 'price-std'], label = 'label=1')
plt.legend()
plt.show()
price-std
: label별 분포가 큰 차이를 보이지 않음
2. Cross Validation을 이용한 Out of Fold 예측
- 모델 훈련시 Cross Validation을 적용해서 Out of Fold Validation 성능 측정 및 Test 데이터 예측을 통해 성능 향상
- Out Of Fold
- Fold마다 학습한 모델로 테스트 데이터를 예측하고, 이를 평균 앙상블하여 최종 예측값으로 사용하는 방법
- 대부분 이 기법 사용
- LightGBM cross validation out of fold train/predict 함수 정의
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
'''
학습 데이터(x_tr), 검증 데이터(x_val), 테스트 데이터(test)로 LightGBM 모델을
학습, 교차(cross) 검증 및 테스트하고 사용된 피처들의 중요도를 반환하는 함수
'''
def make_lgb_oof_prediction(train, y, test, features, categorical_features='auto', model_params=None, folds=10):
x_train = train[features]
x_test = test[features]
# 테스트 데이터 예측값을 저장할 변수
test_preds = np.zeros(x_test.shape[0])
# Out Of Fold Validation 예측 데이터를 저장할 변수
y_oof = np.zeros(x_train.shape[0])
# 폴드별 평균 Validation 스코어를 저장할 변수
score = 0
# 피처 중요도를 저장할 데이터 프레임 선언
fi = pd.DataFrame()
fi['feature'] = features
# Stratified K Fold 선언
skf = StratifiedKFold(n_splits=folds, shuffle=True, random_state=SEED)
for fold, (tr_idx, val_idx) in enumerate(skf.split(x_train, y)):
# train index, validation index로 train 데이터를 나눔
x_tr, x_val = x_train.loc[tr_idx, features], x_train.loc[val_idx, features]
y_tr, y_val = y[tr_idx], y[val_idx]
print(f'fold: {fold+1}, x_tr.shape: {x_tr.shape}, x_val.shape: {x_val.shape}')
# LightGBM 데이터셋 선언
dtrain = lgb.Dataset(x_tr, label=y_tr)
dvalid = lgb.Dataset(x_val, label=y_val)
# LightGBM 모델 훈련
clf = lgb.train(
model_params,
dtrain,
valid_sets=[dtrain, dvalid], # Validation 성능을 측정할 수 있도록 설정
categorical_feature=categorical_features,
verbose_eval=200
)
# Validation 데이터 예측
val_preds = clf.predict(x_val)
# Validation index에 예측값 저장
y_oof[val_idx] = val_preds
# 폴드별 Validation 스코어 측정
print(f"Fold {fold + 1} | AUC: {roc_auc_score(y_val, val_preds)}")
print('-'*80)
# score 변수에 폴드별 평균 Validation 스코어 저장
score += roc_auc_score(y_val, val_preds) / folds
# 테스트 데이터 예측하고 평균해서 저장
test_preds += clf.predict(x_test) / folds
# 폴드별 피처 중요도 저장
fi[f'fold_{fold+1}'] = clf.feature_importance()
del x_tr, x_val, y_tr, y_val
gc.collect()
print(f"\nMean AUC = {score}") # 폴드별 Validation 스코어 출력
print(f"OOF AUC = {roc_auc_score(y, y_oof)}") # Out Of Fold Validation 스코어 출력
# 폴드별 피처 중요도 평균값 계산해서 저장
fi_cols = [col for col in fi.columns if 'fold_' in col]
fi['importance'] = fi[fi_cols].mean(axis=1)
return y_oof, test_preds, fi
3. LightGBM Early Stopping 적용
- Early Stopping
- Iteration을 통해 반복학습이 가능한 머신러닝 모델에서 validation 성능 측정을 통해 validation 성능이 가장 좋은 하이퍼파라미터에서 학습을 조기 종료하는 regularization 기법
- ex) Boosting 트리 모델의 트리 개수, 딥러닝의 Epoch 수
- LightGBM Early Stopping
- LightGBM에서 몇 개의 트리를 만들지
n_estimator
라는 하이퍼파라미터로 설정하고 이 개수만큼 트리를 만들지만, 이 개수가 최적의 값이라고 볼 수 없음 - Early Stopping은 validation 데이터가 있을 시, LightGBM의 트리 개수인
n_estimator
는 충분히 크게 설정하고,early_stopping_rounds
를 적절한 값으로 설정 - 트리를 추가할 때마다 validation 성능을 측정하고, 이 성능이
early_stopping_rounds
값 이상 연속으로 성능이 좋아지지 않으면 더이상 트리를 만들지 않고, 가장 validation 성능이 좋은 트리 개수를 최종 트리 개수로 사용
- LightGBM에서 몇 개의 트리를 만들지
- LightGBM Early Stopping 적용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
folds = 5
model_params = {'n_estimators':1000, ...} # ligthgbm hyperparameter
x_train = ... # train data
x_test = ... # test data
y = ... # train label
# validation out of fold 예측값을 저장할 변수
y_oof = np.zeros(x_train.shape[0])
# test data 예측값을 저장할 변수
y_preds = np.zeros(x_test.shape[0])
skf = StratifiedKFold(n_splits=folds, shuffle=True, ranndom_state=42)
# stratified k fold split 함수로 train index, validation index 가져옴
for fold, (tr_idx, val_idx) in enumerate(skf.split(x_train, y)):
# train, validation index로 train data split
x_tr, x_val = x_train.loc[tr_idx], x_train.loc[val_idx]
y_tr, y_val = y[tr_idx], y[val_idx]
dtrain = lgb.Dataset(x_Tr, label=y_tr)
dvalid = lgb.Dataset(x_val, label=y_val)
# early stopping으로 lightgbm 모델 train
clf = lgv.train(model_params, dtrain, valid_sets=[dtrain, dvalid], early_stopping_rounds=100)
# out of fold validation data 예측
y_oof[val_idx] = clf.predict(x_val)
# train된 모델로 test data 예측
y_preds += clf.predict(x_test) / folds
# 전체 fold loop 순회 후 out of fold validation 성능 측정
print(f'OOF AUC = {roc_auc_score(y, y_oof)}')
This post is licensed under CC BY 4.0 by the author.