※전처리/데이터 소개 : [머신러닝]Bank-Marketing 데이터 분석(1) (데이터셋 설명 ~ 데이터 전처리) 참고
3. 모델 선택 및 학습
비교 모델/평가지표 소개
적절한 모델 선택과 최적의 예측률을 위해서 각 모델을 학습 시 평가지표를 도출하고, 제일 우수한 평가지표를 보인 모델을 선택하기로 했다.
선택 후보 모델 :
- LogisticRegression(로지스틱 분류 모델)
- SVM(Support Vector Machine)
- RandomForest(트리-배깅 활용 모델)
- GradientBoosting(경사하강법 이용 부스팅 모델)
- HistGradientBoostingRegressor(Leaf-wise 방식 활용 모델)
활용 평가지표 :
- 정확도(accuracy) : 전체 샘플 중 모델이 맞게 예측한 비율
- f1-score : 정밀도와 재현율의 조화평균, 불균형 클래스에서 균형 잡힌 성능 평가
- RMSE : 예측값과 실제값 오차의 제곱평균의 제곱근, 평균적 오차 크기
- ROC-AUC Score : 모든 분류 임계값에서의 민감도와 특이도의 trade-off를 통합한 곡선 아래 면적 - 클래스 구별 능력 평가
- 측정 시간 : 모델 학습에 걸린 시간
먼저 train, test 데이터를 split했다.
#테스트, train 데이터 split
#라벨링데이터와 특징 분할
X = processed_df.drop(columns = ['y'])
Y = processed_df['y'].to_numpy()
train_X, test_X, train_Y, test_Y = train_test_split(X, Y,
test_size=0.2,
random_state=42)
모델 비교
#결과를 저장할 리스트 생성
res = []
models = {
'LogisticRegression' : LogisticRegression(),
'SVM' : svm.SVC(kernel = 'rbf', C = 10.0, max_iter = 100, probability = True),
'RandomForest' : RandomForestClassifier(
n_estimators = 30,
criterion = 'entropy',
max_depth = 20,
max_samples = 0.7,
random_state = 42
) ,
'GradientBoosting' : GradientBoostingClassifier(
n_estimators = 30,
max_depth = 3,
learning_rate = 0.2,
random_state = 42
),
'HistGradientBoosting' : HistGradientBoostingClassifier(
max_depth = 6,
l2_regularization = 0.1,
max_iter = 1000,
learning_rate = 0.1,
max_bins = 255 ,
random_state = 42
)
}
for name, model in models.items():
start = time.time()
model.fit(train_X, train_Y) #각각의 모델 학습
pred = model.predict(test_X) #학습된 모델로 test데이터 predict
pred_proba = model.predict_proba(test_X)[:, 1] #roc_auc 점수를 위한 정답 클래스에 대한 확률
#성능지표 도출해 저장
accuracy = accuracy_score(test_Y, pred)
f1 = f1_score(test_Y, pred, average = 'binary')
roc_auc = roc_auc_score(test_Y, pred_proba)
rmse = np.sqrt(mean_squared_error(test_Y, pred))
end = time.time()
res.append({
'model' : name,
'accuracy' : accuracy,
'f1' : f1,
'roc_auc' : roc_auc ,
'rmse' : rmse,
'time(seconds)' : end - start
})
print(f'{name} : accuracy : {accuracy}, roc_auc : {roc_auc}, f1 : {f1}, rmse : {rmse}, time : {end - start}')
이후 성능지표의 결괏값들을 가독성을 위해서 DataFrame으로 바꾸어서 표 형태로 볼 수 있게 하였고, 성능지표마다 가장 우수한 값을 Bold처리하여 나타냈다.
#DataFrame 변환
res_df = pd.DataFrame(res)
res_df.set_index('model', inplace = True)
#가장 우수한 성능지표를 bold처리하기 위해 함수 정의
def highlight_max(s):
is_max = s == s.max()
return ['font-weight: bold' if value else '' for value in is_max]
def highlight_min(s):
is_min = s == s.min()
return ['font-weight: bold' if value else '' for value in is_min]
res_df = res_df.style.apply(highlight_max, subset = ['accuracy', 'f1', 'roc_auc'],axis = 0)
res_df = res_df.apply(highlight_min, subset = ['rmse', 'time(seconds)'], axis = 0)

결과 :
- SVM 모델의 경우에는 데이터 스케일링이 이루어지지 않아 낮은 성능지표를 보였다.
- LogisticRegression 모델의 경우에는 다른 앙상블 모델보다 다소 낮은 f1-score를 보였다.
- RandomForest, Gradient Boosting, HistGradientBoosting 모델의 경우에는 모델들이 비슷한 성능지표를 보였지만 다른 모델에 비해 HistGradientBoosting 모델이 시간이 압도적으로 덜 걸려서, 효율적인 예측이 가능하다고 판단했다.
→ 최종 예측 모델은 효율적으로 예측이 가능하다고 판단한 HistGradientBoosting 모델로 결정하게 되었다.
4. 분석 결과 도출
트리 기반 모델이기 때문에 치환 중요도, SHAP 분석을 통해서, 특징별 분석을 진행했다.
모델 학습
#HistGradientBoosting 모델을 활용하여 학습
final_model = HistGradientBoostingClassifier(max_depth = 6,
l2_regularization = 0.1,
max_iter = 1000,
learning_rate = 0.1,
max_bins = 255 ,
random_state = 42)
final_model.fit(train_X, train_Y)
predicts = final_model.predict(test_X)
특성 중요도(치환 중요도) 분석
먼저 전체적인 특성의 중요도를 보기 위해서 permutation Importance를 먼저 확인해 보기로 했다.
#특성 중요도 분석
result = permutation_importance(final_model,
test_X,
test_Y,
n_repeats=10,
random_state=42)
importances = result.importances_mean
#특성중요도 값 순으로 sort
sorted_importances = np.argsort(importances)[::-1]
features = test_X.columns
plt.figure(figsize = (10,10))
plt.title("Permutation Importance")
plt.barh(range(test_X.shape[1]), importances[sorted_importances])
plt.yticks(range(test_X.shape[1]), features[sorted_importances])
plt.xlabel('특성 중요도')
plt.ylabel('특성')
plt.tight_layout()
plt.show()

특성 중요도 분석 결과
- 고용자 수에 따른 예측값이 큰 영향을 보여, 고용시장의 변화에 따라 예금 가입에 영향이 있다고 막연하게 볼 수 있는데, 뒤따르는 cons_price.idx 역시 경제 상황에 면밀한 관계가 있는 척도이다. month 특징도 큰 관여를 하고 있는 것을 보아, 2008~2010년 월에 따른 포르투갈 경제 상황을 참고하면 좋겠다고 생각이 든다.
- 또한 중요도 양수를 보이고 있는 previous_exist, poutcome, previous 특징들은 이전에 예금 가입을 한 적이 있는 사람들에 관한 데이터인데, 예금상품 자체가 이율이 괜찮았을까? 그래서 다시 들게 되었을까? 아니면 그 반대일까? shap 분석을 통해서 비례관계인지, 반비례 관계인지 확인해 볼 필요가 있겠다고 생각이 든다.
- 사람의 개인적인 특성인 age/marital/education의 경우에는 크게 중요도가 없는 것을 보아 고객의 특성보다는 경제상황/이전 상품 경험에 조금 더 초점을 맞추고 생각해 볼 수도 있겠다고 생각했다.
SHAP Value 분석
explainer = shap.Explainer(final_model)
shap_values = explainer.shap_values(test_X)
shap.summary_plot(shap_values, test_X, feature_names=test_X.columns)

SHAP Value의 변동폭이 가장 큰 nr.employed에 대해 분석해 보기로 결정했다.
고용자 수(nr.employed) 분석
- 모델에 가장 크게 기여하는 특징 중 하나인 nr.employed 특징은, 낮아질수록 SHAP Value가 올라가는 것을 볼 수 있다.
- 고용자 수가 감소할수록 정기 예금을 들 확률이 올라간다! 그렇다면 고용자 수가 왜 감소할까? 언제 감소할까 ? 이에 대해 마케팅 담당자가 할 수 있는 일이 있을까? 를 분석해 보자
먼저 target-encoding 된 데이터를 데이터 값 자체로 분석하기 위해서 Decoding을 진행했다.
test_X_df = pd.DataFrame(test_X)
#target inverse
test_X_df = processor.inverse_target_encoding(test_X_df)
먼저 고용자 수와 관련 있어 보이는 column들부터 dependence plot을 만들어 분석해 보기로 했다. 불경기인지 아닌지를 볼 수 있는 column으로는 cons.price.idx, cons.conf.idx가 있다.
#con.price.idx Dependence plot
shap.dependence_plot('cons.price.idx', shap_values, test_X_df, interaction_index = 'nr.employed')

#cons.conf.idx Dependence Plot
shap.dependence_plot('cons.conf.idx', shap_values, test_X_df, interaction_index = 'nr.employed')

- 소비자 물가지수(cons.price.idx)와 소비자 신뢰지수(cons.conf.idx)가 큰 관련이 있어 보이진 않았다.. 경제 상황을 볼 수 있는 변수가 또 뭐가 있을까? 하고 고민하다가 drop 했던 특징인 유리보 금리(euribor3 m)와 고용 변동률(emp.var.rate)이 생각났다.
- 노동자 수(nr.employed)와 상관계수가 높았던 것으로 보아 밀접한 관련이 있을 것 같았고, 테스트 데이터셋으로 dependece plot을 만들 순 없지만 원본 데이터를 복사해 와 상관관계를 분석하는 용도로 사용했다.
#유리보 금리와 노동자 수 비교
df_analyze = df.copy()
plot_x = df_analyze['euribor3m']
plot_y = df_analyze['nr.employed']
corr = plot_x.corr(plot_y)
plt.figure(figsize = (10,10))
sns.regplot(x = plot_x, y = plot_y, scatter_kws={'alpha':0.2, 's':10})
plt.xlabel('3개월 유리보 금리 ')
plt.ylabel('고용자 수')
plt.title(f'유리보 금리에 따른 고용자 수 분석 corr = {corr:.4f}')
plt.show()

#고용 변동률과 고용자 수 분석
plot_x = df_analyze['emp.var.rate']
plot_y = df_analyze['nr.employed']
corr = plot_x.corr(plot_y)
plt.figure(figsize = (8,5))
sns.regplot(x = plot_x, y = plot_y, scatter_kws={'alpha':0.2, 's':10})
plt.xlabel('고용 변동률')
plt.ylabel('고용자 수')
plt.title(f'고용 변동률에 따른 고용자 수 분석 corr= {corr:.4f}')
plt.show()

- 당연하게도 상관관계가 높은 column들을 모았기에 비례관계인 것은 명확하지만, 그래도 고용자 수가 증가하고 감소하는 원인에 고용 변동률과 3개월 유리보 금리가 있다는 것을 알 수 있었다.
그런데 왜?
경험적으로 생각했을 때, 고용자 수가 줄어들면 벌어들이는 돈이 적어질 테고, 그렇다 보면 수중에 있는 돈이 없고, 예금에 묶어놓을 돈이 없어서 예금을 가입할 사람이 감소할 것이라고 예상했었다.
-> 하지만 데이터셋을 분석해 보았을 때는 고용자 수가 감소하면 예금 가입률이 증가하는 추세를 보인다.
-> 그렇다면 왜 이런 현상이 일어났을까? 답은 이 당시의 포르투갈의 경제 상황에서 찾을 수 있었다.
2008~ : 리먼 브라더스 파산 -> 미국의 대형 투자 은행이 채무를 감당하지 못해 파산했고, 약 6390달러의 부채가 발생했다. 이로 인해서 전 세계적인 대규모 금융위기(GFC)가 발생했다.
-> 따라서 포르투갈도 이의 여파를 피할 수 없었고, 2011년 IMF 구제금융을 신청하기까지 매우 큰 경제위기가 발생했다.
2011 : 포르투갈의 IMF구제금융 신청
이렇게 경제 위기 속에서는 오히려 행동경제학적인 요인으로 실업률이 상승하면 '오히려' 소비 대신에 안전 자산인 정기 예금으로 저축을 해서 비상금을 축적한다는 예방적 저축 동기라는 개념이 있다.
-> 왜? 미래 소득이 불안정하기 때문에
-> 따라서 이 데이터셋은 2008~2010년 사이의 포르투갈 경제 위기 상황의 데이터셋이어서 결과가 이렇게 나타난다.
그럼 어떡할까?
위의 데이터에서 얻을 수 있었던 것 :
- 경제 위기 상황에서는 고용자 수가 줄어들수록 오히려 정기예금 가입률이 올라간다.
- 경제 위기일 때 오히려 정기 예금 가입자가 늘어나는 것을 이용해, 경제 위기 상황을 파악하고 그에 맞는 상품들을 내놓는 방식으로 고객을 유치하면 좋을 것이라고 생각했다.
-> 먼저, 경제 위기 여부 판단을 해야 하는데, 위에 column에 있던 고용자 수, 3개월 유리보 금리, 고용 변동률뿐만 아니라, 추가로 GDP, 금융안정지수(FSI) 등을 활용하여 경제 위기인지 아닌지 판단할 수 있는 척도를 해당 은행에서 만들어 놓는다.
이후 경제 위기 시에 오히려 고객을 안심시키고, 유치할 수 있는 상품들을 내놓는 방법이 있겠다. 예를 들면 :
- 짧은 기간의 정기 예금 : 위기일수록 돈을 오래 묶어두기보다는 3개월/6개월 단위의 정기예금을 예치하는 것을 추천한다.
- 광고 문구 개선 : '안정적', '원금 보장', '안전' 이런 키워드를 활용한 광고를 하게 되면 위기에 불안해하는 고객들을 더 유치할 수 있을 것이라고 생각한다.
※데이터 인사이트(PPT) 파일 첨부
Preview :

'My IT > Codes' 카테고리의 다른 글
| [My IT : Codes] LSTM 활용 Air Pollution Forecast 데이터셋 예측(1) 데이터셋 소개 ~ 모델링 (1) | 2026.03.25 |
|---|---|
| [My IT : Codes] AutoEncoder 활용 Denoising (0) | 2026.03.23 |
| [My IT : Codes]Bank-Marketing 데이터 분석(1) (데이터셋 설명 ~ 데이터 전처리) (2) | 2026.03.07 |
| [My IT : Codes] Hotel Booking Demand Datasets 분석(2)(특징 분석~결론 도출) (0) | 2026.02.13 |
| [My IT : Codes] Hotel Booking Demand Datasets 분석(1)(데이터셋 불러오기~ 이상치/결측치 처리) (0) | 2026.02.13 |