[BoostCourse] 4. 피처 엔지니어링
1. 피처 엔지니어링
-
원본 데이터로부터 도메인 지식 등을 바탕으로 문제를 해결하는데 도움이 되는 Feature를 생성, 변환하고 이를 머신러닝 모델에 적합한 형식으로 변환하는 작업
-
일반 머신러닝에서는 성능을 올리는데 가장 중요한 핵심적인 단계
- 딥러닝은 모델의 구조를 통해 데이터의 피처를 모델이 스스로 추출하지만 (end-to-end learning)
- 머신러닝은 피처를 스스로 추출할 수 없기 때문에 사람이 직접 데이터를 이해해서 피처를 만들어야 함
1) groupby / aggregation
-
Pandas의 groupby, aggregation 함수를 적용하여 새로운 피처 생성
-
EDA를 위한 Feature 생성 코드
- Feature 생성
- 피처 엔지니어링 함수 정의
'''
입력인자로 받는 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
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
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
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
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
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
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 함수 정의
'''
학습 데이터(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 적용
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)}')