Study/머신러닝

머신러닝 Advanced_ 기본 ML 모델

김 도경 2024. 11. 6. 13:06

[2024.11.06] 필수 온라인 강의 Part16 Machine Learning Advanced CH05 기본 ML 모델

 

이론

 

  • 머신러닝 모델의 목적
    - 분류와 회귀 문제
       - 분류(Classification) : 예측하고 싶은 종속변수(Y)가 범주형일 때
       - 회귀(Regression) : 예측하고 싶은 종속변수(Y)가 연속형일 때
          -> 대부분의 머신러닝 모델들은 두 문제 모두 해결 가능 e.g. DecisionTreeClassifier, DecisionTreeRegressor

  • 머신러닝 모델의 분류
    - 선형모델과 비선형모델
        - 선형모델 : X와 Y의 관계를 선형으로 매핑 ➞ 선형 결정경계(Decision boundary) 생성 (e.g. 선형회귀모형)
        - 비선형모델 : X와 Y의 관계를 비선형으로 매핑 ➞ 비선형 결정경계(Decision boundary) 생성 (e.g. KNN, Tree)

  • Linear Regression (선형회귀)
    - 데이터를 가장 잘 대변하는 최적의 선을 찾는 과정
    - 독립변수들(X)과 연속형 종속변수(Y) 사이의 선형 관계를 학습

    - 최소자승법(Ordinary Least Square, OLS) : 선을 잘 찾기 위한 방법
       - X와 Y의 관계를 가장 잘 나타내는 Best line을 찾는 방법
       - 잔차제곱의 합(SSE, Sum of Square Error)을 최소화 하는 회귀계수 β 들을 구하는 방법 
           - 잔차(Residual) : 실제 관측값 - 예측값
           - 잔차 제곱의 합(SSE) : 회귀 모형의 Cost function → Minimize

    - 장점
        - 학습 및 예측 속도가 빠른 것이 특징
        - 모델의 해석이 명확 (회귀계수 해석 가능)
    - 단점 
        - X와 Y의 선형관계를 가정하기 때문에, 이러한 가정이 현실에서는 잘 적용되지 않을 가능성
        - 이상치에 다소 민감

    - 가정
        - 선형성 : 독립변수(X)와 종속변수(Y) 사이에는 선형 관계가 성립  ➞ 주로 시각화를 통해 확인

      - 잔차 관련 가정    ➞ 회귀 분석의 신뢰성을 높이는 요소
        - 정규성 : 잔차들은 평균이 0인 정규분포를 구성
        - 등분산성 : 잔차들의 분산은 일정
        - 위배시
             - 회귀계수의 신뢰 구간 및 가설 검정 결과의 부정확 동반
             - 모델의 예측 능력 저하 증상
        - 대표 확인 방법 : Q-Q(Quantile-Quantile) plot
            - 잔차를 오름차순으로 나열 했을때의 분위수와 이론적인 잔차의 분위수 값을 비교해서 정규성을 확인
                  -> 시각화를 해서도 확인을 가능.
        - 해결 방법
            - 데이터에 로그나 루트를 취하기
            - 더 많은 데이터를 수집해 중심극한정리에 가까워 지도록 기대

        - 독립성 : 독립변수들(X) 간 상관관계(Correlation)가 존재하지 않아야 함
           - 다중공선성 : 회귀 모형에 사용된 일부 독립변수가 다른 독립변수와 상관성이 높아, 모델 결과에 부정적 영향을 미치는 현상
           - 위배하면 : 독립변수들의 상관관계의 영향으로 회귀 모형의 예측 결과가 부정확해 질 수 있음
          → 독립성 가정이 위배되는 상황으로, 제거 필요
           - 다중공선성 확인 방법 : VIF(Variance Inflation Factor, 분산팽창계수)
               - 보통 VIF가 10이 넘으면 다중공선성에 문제가 있다고 여겨짐
              ➞ <해결 방법 > 다중공선성이 높은 변수 조합 중 하나의 변수를 제거하는 방향으로 진행

    - 사용시 주의할 점
        - 이상치 처리 
          - 회귀분석의 결과는 이상치의 유무에 민감한 경향 → 적절한 이상치 제거 필요
          - 이상치 확인 방법
               - IQR(사분범위) : Q3(3사분위수, 전체 데이터 분포의 75% 지점) - Q1(1사분위수, 전체 데이터 분포의 25% 지점)
                      - 하위이상치 : Q1 - 1.5 * IQR 보다 작은 값
                      - 상위이상치 : Q3 + 1.5 * IQR 보다 큰 값

    - 모델 평가와 결과 해석
      - R-square(결정계수)
         - 회귀 모형 내에서 독립변수들(X)이 설명할 수 있는 종속변수(Y)의 변동성
               0 ≤ R-square ≤ 1 범위를 가짐
                  ○ R-square = 0 : X와 Y는 어떤 선형관계도 존재하지 않음
                  ○ R-square = 1 : X와 Y는 회귀모형으로 설명될 수 있는 완벽한 선형관계를 가짐

       - 모델 결과 해석
           - 회귀 계수에 대한 표준편차, 신뢰구간, p-value 등으로 회귀 모형 해석 가능
    - 실전 사례
         - 최신 LLM 데이터의 평가 모델로 활용 
         - 데이터의 평가 지표를 Variable로 두고 Quality를 예측하는 모델을 학습해서새로운 데이터가 들어왔을때 Quality를 예측하는 방법 
        ➞ 각 데이터 평가 지표가 얼마나 유의미하고 중요한지도 확인 가능 (선형 회귀를 동일한 방법으로도 활용 가능)

  • KNN(K-Nearest Neighbor, K-최근접 이웃)
       - 가까운 이웃에 위치한 K개의 데이터를 보고, 데이터가 속할 그룹을 판단하는 과정
        → 거리 기반 모델, 사례 기반 학습(Instance-Based Learning)

    - 거리 측정 방법
      - 거리 측정 방법에 따른 KNN 모델의 성능 차이 존재
      - 인접한 K개의 데이터를 고려하기 위해 “인접함”의 정의가 필요 → 거리 측정 Metric 선택
      - 유클리드 거리(Euclidean Distance)
      - 맨해튼 거리 (Manhattan Distance)

    - 분류와 회귀를 위한 KNN 뿐 아니라 유사한 데이터(인접한 이웃)를 구하기 위한 방법론으로도 사용 가능

    - 실전 사례
        - 이웃 정보를 추천시스템 모델의 Side information으로도 활용 가능
        - 결측치 보간과 KNN : KNN을 통해 인접한 이웃들을 구해 결측치 보간에도 활용 가능

        - 장점
            - 단순하고, 특별한 훈련을 거치지 않아 빠르게 수행
            - 데이터에 대한 특별한 가정이 존재 하지 않는 특징
        - 단점
            - 적절한 K 선택 필요
            - 데이터가 많아지면 분류가 느려지는 현상
            - 데이터 스케일에 민감하기에 스케일링이 필수적

       - K 값 결정하기
            - K값에 따라 고려하는 이웃 수가 달라지기 때문에 K값에 민감
              → 주로 Cross Validation 을 통해 경험적으로 K 값을 선택

  • Decision Tree(의사결정나무)
     - 데이터 내 규칙을 찾아 Tree 구조로 데이터를 분류/회귀 하는 과정
     - 특정 기준(질문)에 따라 데이터를 구분
     - 노드 명칭
    - Tree 분기 기준 - 불순도
         - 의사결정나무의 목표는 맨 마지막 리프 노드의 불순도 (Impurity)를 최소화하는 것
           → 정보획득량 (Information Gain, 분기 이전의 불순도와 이후의 불순도 차이)을 노드 분기의 기준으로 이용
          - 불순도 함수 : 불순도가 높을 수록 아래 지표들이 높게 나타남
              - 지니 계수(Gini coefficient)        : 줄어드는 방향으로 분기를 진행
              - 엔트로피(Entropy)                     : 줄어드는 방향으로 분기를 진행

    - 과정
         - 분할 종료 조건 확인 → 순도가 100%가 되는 지점에서 분할 종료
         - 분할 기준 선택 → 모든 변수에 대해 불순도를 계산하고 해당 불순도가 가장 낮은 값을 기준으로 진행

    - 장점
          - 직관적으로 이해가 쉽고, 데이터 스케일에 민감하지 않음
          - 범주형과 연속형 데이터를 모두 처리 가능
    - 단점
          - 트리 깊이가 깊어지면 과적합의 위험 존재 → 적절한 Pruning이 필요
          - 새로운 Sample에 취약할 수 있음

    - 주의할 점
      - 가지치기(Pruning)
         - 트리의 깊이가 깊어지거나 Leaf가 많아지면 과적합의 위험이 커진다는 단점을 보완하기 위함
         - 형성된 결정트리의 특정 노드 아래 트리나 Leaf를 제거해 일반화 성능을 높이기 위한 전략
      - 트리의 Depth 조절하기
         - 결국 과적합 방지에 중요한 것은 트리의 Depth(깊이)와 Leaf(노드)를 적절히 조정하는 것!
         - 가지치기 이외에도 아래처럼 sklearn에는 트리 깊이와 노드를 조정할 수 있는(=분할을 규제하는) Hyperparameter가 존재
            - Max depth : 트리의 최대 깊이     : 과적합 방지해야함
            - Max Leaf Nodes : 최대 몇개 Leaf 노드가 만들어 질 때까지 split 할건지?     : leaf가 많아지면 과적합
            - Min sample split : 최소 샘플이 몇개 이상이어야 하위 노드로 split 할건지?    : 많은 값- 너무 적으면 과적합


    - 모델 평가와 결과 해석
      - Tree 시각화 : 결정경계와 트리 분할 과정을 시각화 가능

    - Feature Importance(변수 중요도)
        - 트리 분기 과정에서 불순도를 많이 낮출 수 있는 feature = 중요한 feature
            → Feature selection의 기준으로 활용되기도 함
        - 해당 지표는 절대적이지 않음에 유의 : 분할 기준, 모델 학습 과정 등에 따라 달라질 수 있음

 

  • Random Forest

    - Ensemble 개념의 등장
          - 앙상블(Ensemble) : 주어진 데이터로 여러개의 서로 다른 예측 모형을 생성해, 이들의 결과를 종합하는 것
             → 여러 모델의 결과를 voting(범주형)이나 average(연속형)로 종합해 하나의 최종 예측 결과 도출
             → 더 정확한 예측 결과를 기대 가능

          - 트리 앙상블(Tree Ensemble)
              → 대표적으로 배깅(Bagging), 부스팅(Boosting) 방법이 존재

    - 배깅(Bagging, Bootstrap aggregation)
       - 앞선 부트스트랩의 복원추출 과정을 머신러닝 앙상블에 사용
       - 부트스트랩을 통해 표본을 여러번 뽑아 모델을 학습시키고, 그 결과를 집계(Aggregation) 하는 앙상블 방법
       - 부트스트랩(Bootstrap)
       - 복원추출을 사용해 표본을 추출해 모집단의 통계량을 추론하는 통계적 방법
       - 여기서 각 표본들은 복원추출로 생성되었으므로 독립적

    - 랜덤 포레스트(Random forest)
         - 배깅(Bagging)의 일종
         - 학습시키는 데이터 뿐 아니라 독립변수(X)들도 무작위로 선택해 트리를 생성하는 방법

        - 모델 학습 과정에서 서로 다른 N개의 Tree 생성
        - N개의 Tree에서 발생한 Output을 Voting(범주형, 분류문제)하거나, Average(연속형, 회귀문제)해 최종 Output 생성
                → High variance, Low bias 상황에서 분산(Variance) 감소에 도움

    - 장점
       - 부트스트랩으로 인해, 단일 의사결정나무의 단점인 높은 분산이 줄어 예측 성능 향상 기대
       - 여러 트리의 융합으로 과적합 완화 기대
       - 그 외 의사결정나무의 장점을 모두 흡수
    - 단점
       - 데이터 크기에 비례해 N개의 트리를 생성하기에, 대용량 데이터 학습의 경우 오랜 시간 소요
       - 생성하는 모든 트리를 모두 확인하기 어려워, 해석 가능성이 단일 트리모델보다 떨어지는 문제

 

실습

 

1. 데이터 불러오기 및 전처리

1-1 실습 데이터 구성하기
     - Yahoo Finance OHLCV 데이터셋  사용

1-2 데이터셋 분할

-> 앞서 실습한 내용과 동일함 : https://glowdp.tistory.com/80


2. 모델링 (Modeling)

- 기초 ML 모델들(선형회귀, KNN, 의사결정나무)을 활용해 종가(Close)를 예측하는 실습
2-1 Linear Regression

- 컬럼 구성 확인하기

X_train.columns     # train_data의 칼럼은 아래와 같이 구성


- 회귀 가정 체크 + 시각화

# 회귀 가정 체크 -> 시계열 데이터이다보니 변수(종가, 시가 등)들의 상관관계가 아래와 같이 너무 높음
# 하지만 회귀 가정 중에서는 변수간의 독립성을 고려해야한다는 가정이 존재
# 따라서 본 시계열 데이터를 바로 회귀모델에 적용
corr = X_train.corr()
sns.heatmap(corr)

# 시각화
plt.title("Correlation plot")
plt.show()
# 일단 독립성에 위배되지만, 모델 결과는 어떻게 나타나는지 sklearn으로 살펴봄
lin_reg = LinearRegression()
lin_reg.fit(X_train,Y_train) # sklearn based의 회귀 모델을 적합

- 선형회귀를 사용하기에 적합한 데이터 셋은 아님. 그래서 그냥 일단 어떤 식으로 나타내는가만 알아보기

# 위에서 적합된 모델로 MSE와 R^2를 계산
y_pred_sk = lin_reg.predict(X_valid)
J_mse_sk = np.sqrt(mean_squared_error(y_pred_sk, Y_valid))
R_square_sk = lin_reg.score(X_valid,Y_valid)

display(f"RMSE : {J_mse_sk}")
display(f"R square : {R_square_sk}")
# 동일한 과정을 statsmodel 패키지로 사용
# 모델 적합 결과는 sklearn과 동일함
model = sm.OLS(Y_train, X_train)
result = model.fit()
print(result.summary())
# QQ plot으로 가정을 만족하는지 체크
f,ax = plt.subplots(1,2,figsize=(10,4))
_,(_,_,r)= sp.stats.probplot((Y_valid - y_pred_sk),fit=True,plot=ax[0])
ax[0].set_title('Check for Multivariate Normality: \nQ-Q Plot')

# 등분산성도 체크
sns.scatterplot(y = (Y_valid - y_pred_sk), x= y_pred_sk, ax = ax[1],color='r')
ax[1].set_title('Check for Homoscedasticity: \nResidual Vs Predicted');

# QQ plot이 비선형적으로 휘어져있고, 등분산성은 다소 만족하는 모습을 보여줌
# QQ plot으로 보아 이는 선형회귀 가정이 만족되고 있지 않음을 보여줌
# VIF를 체크해봐도 터무니없이 높음
VIF = 1/(1- R_square_sk)
display(f"VIF: {VIF}")

 

- 시계열 데이터는 단순 선형 회귀로는 가정도 만족하지 않을 뿐더러, 적합하지 않음


2-2 KNN (K-Nearest Neighbor)

# 이론 수업 시간에 KNN은 K가 하이퍼 파라미터로서, K값에 따라 KNN의 성능이 달라질 수 있음
# 최적의 K값 찾기
knn_db=[]
for i in tqdm(range(3,15,2)):
    M=[]
    knn = KNeighborsRegressor(n_neighbors=i)
    knn.fit(X_train, Y_train)
    y_pred = knn.predict(X_valid)
    rmse = np.sqrt(mean_squared_error(Y_valid, y_pred))   # MSE를 K값에 따라 저장하도록 설정합니다.
    M.append(i)
    M.append(rmse)
    knn_db.append(M)
# 최적의 k값을 찾기 위한 장치. MSE가 가장 작게 나오는 모델을 적합시킨 K값을 최적의 값으로 설정
min=knn_db[0];
for i in range(len(knn_db)):
    if knn_db[i][1]<min[1]:
        min=knn_db[i];
n=min[0];

# 최적의 K 값이 반환됩니다.
display(n)
# K값에 따른 MSE 변화를 시각화. K=3일 때 가장 낮은 MSE를 보임
# K값이 올라갈수록 MSE가 증가하는 것을 확인
x = [x[0] for x in knn_db]
y = [x[1] for x in knn_db]

# 시각화를 진행
plt.plot(x, y, color='red')
plt.title('K selection using MSE')
plt.xlabel('K')
plt.ylabel('MSE')
plt.show()
# 위에서 찾은 최적의 K값을 이용해 모델을 학습시켜 결과를 관찰
knn = KNeighborsRegressor(n_neighbors=3)
knn.fit(X_train, Y_train)
# ytrainpredict_kk = knn.predict(X_train)     # 시간이 꽤 오래걸리므로 생략
ytestpredict_kk = knn.predict(X_valid)

# 회귀 관련 metric을 통해 train/valid의 모델 적합 결과를 관찰.
display(f'RMSE Test: {np.sqrt(metrics.mean_squared_error(Y_valid, ytestpredict_kk))}')
# 모델 적합 결과를 시각화(잔차플롯)
plt.scatter(ytestpredict_kk, ytestpredict_kk-Y_valid, c='limegreen', marker='s', edgecolors='white', s=35, alpha=0.9, label="Valid data")
plt.xlabel('Predicted Values')
plt.ylabel('Residuals')
plt.legend(loc='upper left')
plt.hlines(y=0, xmin=ytestpredict_kk.min()-1, xmax=ytestpredict_kk.max()+1, lw=1, color='black')
plt.xlim([ytestpredict_kk.min()-1, ytestpredict_kk.max()+1])
plt.title('Residual plot from KNN Regression')
plt.show()

 


2-3 Decision Tree

# DecisionTreeRegressor 이용해 의사결정나무 생성
dt = DecisionTreeRegressor(
    max_depth=10, # 트리의 깊이를 규제
    random_state=1214, # 트리의 랜덤시드를 설정
    min_samples_split=100 # 해당하는 샘플이 100개 이상이면 split
    )

dt.fit(X_train, Y_train)
ytrainpredict_df = dt.predict(X_train)
ytestpredict_df = dt.predict(X_valid)
# 회귀 관련 metric을 통해 train/valid의 모델 적합 결과를 관찰
display(f"RMSE Train: {np.sqrt(metrics.mean_squared_error(Y_train, ytrainpredict_df))}")
display(f"RMSE Test: {np.sqrt(metrics.mean_squared_error(Y_valid, ytestpredict_df))}")
# 트리를 시각화.
export_graphviz(dt, out_file ='tree.dot')
with open("tree.dot") as f:
    dot_graph = f.read()

pydot_graph = pydotplus.graph_from_dot_file("tree.dot")
Image(pydot_graph.create_png())

# 모델 적합 결과를 시각화(잔차플롯)
plt.scatter(ytestpredict_df, ytestpredict_df-Y_valid, c='limegreen', marker='s', edgecolors='white', s=35, alpha=0.9, label="Valid data")
plt.xlabel('Predicted Values')
plt.ylabel('Residuals')
plt.legend(loc='upper left')
plt.hlines(y=0, xmin=ytestpredict_df.min()-1, xmax=ytestpredict_df.max()+1, lw=1, color='black')
plt.xlim([ytestpredict_df.min()-1, ytestpredict_df.max()+1])
plt.title('Residual plot from DecisionTreeRegressor')
plt.show()

 


2-4 Random Forest

# RandomForestRegressor를 이용해 회귀 모델을 적합
forest_rf = RandomForestRegressor(n_estimators=10, criterion='squared_error', random_state=1, n_jobs=-1)
forest_rf.fit(X_train, Y_train)
ytestpredict_rf = forest_rf.predict(X_valid)

# 회귀 관련 metric을 통해 train/valid의 모델 적합 결과를 관찰
print(f'RMSE test: {np.sqrt(metrics.mean_squared_error(Y_valid, ytestpredict_rf))}')
# 모델 적합 결과를 시각화(잔차플롯)
plt.scatter(ytestpredict_rf, ytestpredict_rf-Y_valid, c='limegreen', marker='s', edgecolors='white', s=35, alpha=0.9, label="Valid data")
plt.xlabel('Predicted Values')
plt.ylabel('Residuals')
plt.legend(loc='upper left')
plt.hlines(y=0, xmin=ytestpredict_rf.min()-1, xmax=ytestpredict_rf.max()+1, lw=1, color='black')
plt.xlim([ytestpredict_rf.min()-1, ytestpredict_rf.max()+1])
plt.title('Residual plot from RandomForestRegressor')
plt.show()

 

 

3. 부동산 데이터에 관한 회귀분석
3-1 회귀분석 모델링

# 데이터
data_file = "House_X_train_val.csv"
df = pd.read_csv(data_file)

# Target과 독립변수들을 분리
y_train = df['target']
X_train = df.drop(['target'], axis=1)

# 학습 데이터와 검증 데이터를 8:2 비율로 분리
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.2, random_state=2023)
# 상관계수를 체크해봤을 때 위 주식데이터와 비교하여 매우 양호한 상황
corr = X_train.corr()
sns.heatmap(corr);
plt.show()
# 회귀모델을 적합다. 총 80개의 변수가 존재하기에 각 독립변수별로 회귀계수가 출력된 것을 확인
# p-value가 낮은 것도 있고, 높은 변수. 이는 모든 변수가 모두 유효하지는 않음
# 하지만 F-statistics도 높고, R-squard도 꽤 높은 경향을 보이므로, 독립변수들이 타겟변수를 비교적 잘 설명.
model = sm.OLS(y_train, X_train)
result = model.fit()
print(result.summary())
# 사이킷런도 이용
lin_reg = LinearRegression()
lin_reg.fit(X_train,y_train) # sklearn based의 회귀 모델을 적합

# 위에서 적합된 모델로 MSE와 R^2를 계산
y_pred_sk = lin_reg.predict(X_val)
J_mse_sk = mean_squared_error(y_pred_sk, y_val)
R_square_sk = lin_reg.score(X_val,y_val)

display(f"The Mean Square Error(MSE) : {J_mse_sk}")
display(f"R square : {R_square_sk}")

# 앞선 sm.OLS 결과와 비슷한 결과를 얻는 것을 확인.
# 예측 값을 시각화
plt.figure(figsize=(10,4))
sns.lineplot(x=range(0, len(y_val)), y=y_val.values, label="Actual values", alpha=1.0)
sns.lineplot(x=range(0, len(y_pred_sk)), y=y_pred_sk, label="Predicted values", alpha=1.0, linestyle='--')

plt.title("Close Price Prediction Plot")
plt.ylabel("Close")
plt.legend()
plt.grid(True)
plt.show()
# QQ plot으로 가정을 만족하는지 체크
f,ax = plt.subplots(1,2,figsize=(10,4))
_,(_,_,r)= sp.stats.probplot((y_val - y_pred_sk),fit=True,plot=ax[0])
ax[0].set_title("Check for Multivariate Normality: \nQ-Q Plot")

# 등분산성도 체크
sns.scatterplot(y = (y_val - y_pred_sk), x= y_pred_sk, ax = ax[1],color="r")
ax[1].set_title("Check for Homoscedasticity: \nResidual Vs Predicted");

# VIF(다중공선성)도 체크
VIF = 1/(1- R_square_sk)

display(f"VIF: {VIF}")