수달이네 기술 블로그

17. 호텔 수요 예측 2(모델 학습, 스케일링, 예측) 본문

AI공부/머신러닝

17. 호텔 수요 예측 2(모델 학습, 스케일링, 예측)

슬픈 수달이 2026. 1. 11. 23:34

학습

이제 object변수가 없으니 학습이 가능해진다. 먼저

train set과 test set으로 나눠준다.

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(hotel_df.drop('is_canceled', axis=1), hotel_df['is_canceled'], test_size=0.3, random_state=42)
X_train.shape, y_train.shape

# ((83447, 38), (83447,))

X_test.shape, y_test.shape

# ((35763, 38), (35763,))

Logistic Regression(로지스틱 회귀)

이진분류(BinaryClassification)과 다중분류(Multiclass Classification)문제를 해결하기 위한 머신러닝 알고리즘

  • 선형모델
  • 입력 데이터에 대한 선형 결합으로 확률을 예측하여 로지스틱 함수(시그모이드)를 통해 예측한다.
  • 과적합을 방지하기 위한 규제,기법등을 지원한다.
  • 선형적으로 구분 가능한 데이터셋에서 뛰어난 성능을 발휘된다.

이진분류:a/b로 나눔

  • 컴퓨터가 예측할때는 확률이 50%이상인 부분으로 예측한다.

다중분류:n개의 카테고리로 나눔

  • 컴퓨터가 예측할때 확률이 가장 높은 카테고리를 예측으로 낸다.

규제(Regularization)

모델이 과적합 되는 것을 방지하기 위해 사용되는 기법

  • 과적합: train데이터에 너무 학습되어 test를 못봄
규제 유형 설명 효과 활용 상황
L1규제(Lasso) 가중치 절댓값에 페널티 (컬럼의) 일부 특성 제거 변수 선택이 필요할 때
L2규제(Ridge) 가중치 제곱에 페널티 모든 특성을(균일하게) 사용, 가중치 축소 다중 공선성 문제 해결
Elastic Net L1+L2결합 두 효과를 결합함 특성이 많고, 다중 공선성이 있을때

구현

from sklearn.linear_model import LogisticRegression

lr = LogisticRegression()
lr.fit(X_train, y_train)

# STOP: TOTAL NO. OF ITERATIONS REACHED LIMIT

로지스틱 회귀 모델은 scikit learn에서 역시 사용할 수 있다.

해당 모델으로 돌릴 경우 위 주석과 같은 오류가 발생한다.

STOP: TOTAL NO. OF ITERATIONS REACHED LIMIT

  • 학습이 제대로 안된 것 같다는 경고문이다.(ConvergenceWarning)

반복횟수가 부족하면 위와 같은 오류가 발생하는데, 알고리즘이 최적의 해를 찾지 못했다는 의미이다.

  • 기본값은: max_iter가 100 즉, 최대 100번 반복하여 최적의 가중치를 찾아야한다.
from sklearn.linear_model import LogisticRegression

lr = LogisticRegression(max_iter=1000)
lr.fit(X_train, y_train)
y_pred = lr.predict(X_test)

# STOP: TOTAL NO. OF ITERATIONS REACHED LIMIT

이터레이터를 1000으로 늘려도 같은 오류가 뜬다. 데이터의 학습 효율이 않좋다는 이야기이므로 데이터 스케일링을 진행해보자.

데이터 스케일링

서로 다른 범위와 단위를 가진 데이터를 일정한 범위로 변환하여 모델 학습을 더 효율적으로 진행할 수 있도록 만들어준다.

  • 변수 간의 값의 크기차이가 클때 발생하는 불균형을 해결하기 위해 주로 사용한다.
  • 표준화(standardization), 정규화(Normalization)등이 있다.
    • 표준화: 평균(0)과 표준편차(1)를 중심으로 스케일링 데이터의 분포가 정규분포일때 효과적이다.

각 데이터에서 평균을 빼고 표준편차로 나눠서 평균을 0으로 맞추고, 변동성을 1로 맞춘다.

  • 정규화: 값을 0~1사이로 스케일링 최대값과 최소값을 기준으로 스케일링한다.(min-max scailing)

원래 데이터에서 변수의 최소값을 빼서 최소값을 0으로 맞추고 , 최대값에서 최솟값을 뺀 값을 나눈다.

  • 학습 속도와 기울기 소실 문제를 완화한다.
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.fit_transform(X_test)

X_train_scaled
# array([[ 0.55726227,  1.19240779,  0.28361965, ..., -0.61566104,
#          1.47895619, -0.45830472],
#        [-0.56406077,  1.19240779, -1.9221054 , ..., -0.61566104,
#         -0.67615255,  2.18195442],
#        [-0.45192847, -1.63662818,  1.09238551, ..., -0.61566104,
#         -0.67615255, -0.45830472],
#        ...,
#        [-0.81635846, -0.22211019,  1.90115136, ..., -0.61566104,
#         -0.67615255,  2.18195442],
#        [ 0.23955408, -1.63662818,  0.28361965, ..., -0.61566104,
#          1.47895619, -0.45830472],
#        [-0.57340513, -1.63662818,  0.28361965, ..., -0.61566104,
#          1.47895619, -0.45830472]], shape=(83447, 38))

sklearn의 표준화 스케일럴르 사용하여 스케일링을 해주면. 위와 같다.

  • 다구현되어있음
from sklearn.linear_model import LogisticRegression

lr = LogisticRegression(max_iter=1000)
lr.fit(X_train_scaled, y_train)
y_pred = lr.predict(X_test_scaled)

이제 max_iter를 1000으로 맞춰서 돌려보면 경고가 뜨지 않은 것을 확인할 수 있다.

from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_pred)

# 1.0

accuracy_score를 확인해보니 완벽하게 다 맞아 1.0의 값이 나온것을 확인할 수 있었다.

지금까지 분류문제의 예측성능은 accuracy로 나타냈다.

혼동행렬

분류모델의 성능을 평가하기 위해 사용되는 도구, 예측 결과와 실제 레이블 간의 관계를 요약하여 나타낸 행렬이다. 4가지 값으로 구성된다.

TP(True Positive): 모델이 긍정적으로 예측하고 실제도 맞는경우

TN(True Negative): 모델이 부정적으로 예측하고 실제도 맞는경우

FP(False Positive): 모델이 긍정적으로 예측했지만 실제론 틀린경우

FN(False Negative): 모델이 부정적으로 예측했지만 실제론 틀린경우

이를 통해 정확도, 정밀도, 재현율, F1점수등을 계산할 수 있다.

  • 데이터에 불균형 즉, 한쪽으로 데이터 양이 쏠린 경우 예측 성능을 구체적으로 분석할 수 있다.
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, y_pred)

# array([[22461,     0],
#        [    0, 13302]])

혼동행렬을 확인해보면, TN의 경우와 TP의 경우가 나눠져 있다. 즉, 모델이 한쪽으로 찍은 것이 아닌 실제로 잘 맞춘 결과라는 것이다.

혼동행렬 계산지표

Accuracy (정확도)

  • 모델이 올바르게 예측한 비율

$$ \text{Accuracy} = \frac{TP + TN}{TP + TN + FP + FN} $$

Precision (정밀도)

  • 양성으로 예측한 것 중 실제 양성의 비율(FP를 줄이는데 초점)

$$ \text{Precision} = \frac{TP}{TP + FP} $$

Recall (재현율)

  • 실제 양성중에서 모델이 양성으로 예측한 비율(FN을 줄이는데 초점)

$$ \text{Recall} = \frac{TP}{TP + FN} $$

F1-score

  • 정밀도와 재현율을 조화평균

$$ \text{F1-score} = 2 \times \frac{\text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}} $$

  • 조화평균: 해당 값이 얼마만큼 조화롭게 있는지 평균을 냄(값들이 서로 모여있을때)
from sklearn.metrics import precision_score, recall_score, f1_score

print("Precision:", precision_score(y_test, y_pred))
print("Recall:", recall_score(y_test, y_pred))
print("F1 Score:", f1_score(y_test, y_pred))

# Precision: 1.0
# Recall: 1.0
# F1 Score: 1.0

예측은 위와 같이 사용할 수 있다.(현재 100프로 맞기 때문에 어쩔수 없음)

lr.coef_

# array([[ 1.97337123e-01,  1.27314182e-02,  3.33145833e-02,
#          4.54301177e-03, -1.66420682e-01, -8.48290878e-02,
#          2.05749711e-03,  8.43424906e-02, -2.55349859e-01,
#         -2.10469071e-01,  5.60230519e-02,  4.56979118e-02,
#          2.18187820e-01,  4.18392001e-01,  1.32382514e-01,
#          1.16740544e-01,  3.28328738e-02,  1.22032178e-02,
#         -1.15689342e-02, -2.60838278e-02,  6.29275403e-02,
#         -6.23318806e-02,  2.10796924e-02,  5.97684492e-03,
#         -5.43052740e-02, -7.14257350e-03,  3.93245399e-02,
#          1.55769113e-02,  8.14626125e-01,  1.11622325e-03,
#         -2.01233819e-02,  1.36675445e-01, -4.57716276e-02,
#         -8.13570429e+00,  2.74910431e-01, -2.61914915e-02,
#          1.61553533e-02,  8.19102225e-04]])

위 값은 각 컬럼 당 기울기를 나타낸다.

  • 즉, 선형으로 구분하게 해주는 각 컬럼의 기울기(위 array의 개수는 컬럼의 개수와 같다)
lr.intercept_

# array([-2.17286742])

위 값은 모델의 절편(vias)을 나타낸다.

proba = lr.predict_proba(X_test_scaled)
proba

# array([[9.99790719e-01, 2.09280603e-04],
#        [9.99822339e-01, 1.77660941e-04],
#        [1.25031233e-05, 9.99987497e-01],
#        ...,
#        [1.96327537e-04, 9.99803672e-01],
#        [9.99925864e-01, 7.41362285e-05],
#        [2.22474211e-06, 9.99997775e-01]], shape=(35763, 2))

기울기와 절편에 x값을 넣어서 해당 값일 확률을 나타낸다.

즉, 첫번째것으로 예시를 들자면,

  • 0일 확률: 9.99790719e-01, 1일확률:2.09280603e-04
  • 이며, 모든 데이터값을 보여준다.
proba = lr.predict_proba(X_test_scaled)[:,1]
proba

# array([2.09280603e-04, 1.77660941e-04, 9.99987497e-01, ...,
#        9.99803672e-01, 7.41362285e-05, 9.99997775e-01], shape=(35763,))

위와 같이 뽑아주면, 취소할 확률 즉, 1일 확률만 골라서 나타나게 된다.

threshold = 0.5
pred = (proba >= threshold).astype(int)
pred

# array([0, 0, 1, ..., 1, 0, 1], shape=(35763,))

그런채로, 기준(threshold)를 0.5로 잡고 0.5보다 크면 1, 작으면 0으로 나타나게 해서 예측을 만들어줄 수 있다.

분류 문제에서의 랜덤 포레스트

이전엔 회귀문제에서 사용해보았으니 이번엔 분류문제에서 살펴본다.

  • 분류문제에선 (트리들의)다수결로 정한다.
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(random_state=42)
rf.fit(X_train, y_train)
pred_rf = rf.predict(X_test)  

예측한 후 1로 예측할 확률을 보여보면

proba_rf = rf.predict_proba(X_test)[:,1]
proba_rf

# array([0., 0., 1., ..., 1., 0., 1.], shape=(35763,))

위와 같이 0과 1로 나타난다. 즉, 0일확률이 0, 1일 확률이 1인 값이 나오기도 한다는 것을 알 수 있다.

accuracy_score(y_test, pred_rf)
# 1.0

둘다 예측 성능이 뛰어남을 알 수 있다.

그러나, 앙상블 모델인 랜덤포레스트의 경우 코스트가 높기 때문에 굳이 같은 1.0의 accuracy를 내는 두 모델사이에선 logistic regression을 사용하는게 좋아보인다.

ROC-AUC(Receiver Operating Characteristic - Area Under the Curve)

ROC-AUC score는 이진 분류 모델의 성능을 평가하는 지표로, 예측 능력을 직관적으로 나타낸다.

ROC곡선: FPR(거짓 양성 비율), TPR(참 양성 비율)을 축으로 다양한 임계값에서 모델의 성능을 시각화한 곡선

AUC면적: 곡선 아래의 면적으로 값이 1에 가까울 수록 완벽한 분류 성능, 0.5에 가까울수록 랜덤추측에 가까운 성능이다.

$$ \text{TPR} = \frac{TP}{TP + FN} $$

$$ \text{FPR} = \frac{FP}{FP + TN} $$

from sklearn.metrics import roc_auc_score, classification_report

print(classification_report(y_test, pred_rf))

#               precision    recall  f1-score   support

#            0       1.00      1.00      1.00     22461
#            1       1.00      1.00      1.00     13302

#     accuracy                           1.00     35763
#    macro avg       1.00      1.00      1.00     35763
# weighted avg       1.00      1.00      1.00     35763
roc_auc_score(y_test, proba_rf)
# 1.0

위와 같이 예측 가능하다.

시각화

import matplotlib.pyplot as plt
from sklearn.metrics._plot.roc_curve import roc_curve

fpr, tpr, thr = roc_curve(y_test, proba_rf)
plt.plot(fpr, tpr, label='ROC curve')
plt.plot([0, 1], [0, 1], 'k--')  # 대각선 기준선
plt.show()