품질관리(QAQC) 데이터 부트캠프(본캠프)

본캠프_10주차(화)_TIL(머신러닝 정리)

Wat_zy 2025. 11. 18. 08:40

✅ 오늘 한 것

머신러닝 정리, 태블로를 활용한 데이터 시각화


✏️ 오늘 배운 점

머신러닝

1) Feature Engineering (특성 엔지니어링)

원본 데이터를 머신러닝 모델이 더 잘 이해하도록 재구성하는 과정

# 날짜 데이터 분해
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day'] = df['date'].dt.day
df['weekday'] = df['date'].dt.weekday

# train/test split
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# 범주형 인코딩
1. One-Hot
df = pd.get_dummies(df, columns=['category'], drop_first=True)
2. Label Encoding
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df['cat_encoded'] = le.fit_transform(df['category'])

# 스케일링
1. StandardScaler
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
2. MinMaxScaler
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 결측치 처리
1. KNN
from sklearn.impute import KNNImputer
imputer = KNNImputer(n_neighbors=5)
df_imputed = pd.DataFrame(imputer.fit_transform(df), columns=df.columns)
2. MICE
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
imputer = IterativeImputer()
df_mice = pd.DataFrame(imputer.fit_transform(df), columns=df.columns)

# 과적합 방지
1. VIF기반 변수 제거
from statsmodels.stats.outliers_influence import variance_inflation_factor

def calculate_vif(X):
    vif_data = pd.DataFrame()
    vif_data["Variable"] = X.columns
    vif_data["VIF"] = [variance_inflation_factor(X.values, i)
                       for i in range(X.shape[1])]
    return vif_data.sort_values('VIF', ascending=False)
    
vif_result = calculate_vif(df)
print(vif_result)
2. 높은 상관관계를 가진 변수 제거
corr_matrix = df.corr().abs()
threshold = 0.9
high_corr_vars = set()

for i in range(len(corr_matrix.columns)):
    for j in range(i):
        if corr_matrix.iloc[i, j] > threshold:
            colname = corr_matrix.columns[i]
            high_corr_vars.add(colname)
            
df_corr_reduced = df_numeric.drop(columns=high_corr_vars)
print("제거된 변수들:", high_corr_vars)
3. PCA 분석
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df_scaled = scaler.fit_transform(df)
pca = PCA(n_components=0.95)
df_pca = pca.fit_transform(df_scaled[['a','b']]) # 바꾸고자 하는 두 변수
print(f"PCA 적용 후 차원 수: {df_pca.shape[1]}")

# SMOTE
sm = SMOTE()
X_train_res, y_train_res = sm.fit_resample(X_train_scaled, y_train)

 

train/test split을 진행한 이후 Scaling, Encoding 등 사용해야 함.

 

📌 Scaling과 Encoding은 반드시 Train 데이터로만 fit하고 Test 데이터에는 transform만 적용해야 한다.

 

One-Hot Encoding: 순서가 없는 범주형 변수를 숫자로 변환하는 과정

Label Encoding: 순서가 있는 범주형 변수를 숫자로 변환하는 과정

 

StandardScaler: 각 값을 평균 0, 표준편차 1로 변환 (데이터가 정규분포에 가깝거나 대칭적일 때)

MinMaxScaler: 각 값을 0~1 사이로 변환 (데이터 분포가 비정규형일 때)

 

VIF 값이 10 이상일 때 다중공선성이 높다고 판단하여 제거

상관계수가 0.9 이상인 변수 쌍 중 하나를 제거

 

SMOTE에는 Train에만 수행

2) 모델 선택

회귀분석

1. LightGBM
from lightgbm import LGBMRegressor
model = LGBMRegressor(
    n_estimators=500,
    learning_rate=0.05,
    max_depth=-1,
    num_leaves=31
)
model.fit(X_train, y_train)
pred = model.predict(X_test)
2. CatBoost (범주형 처리)
from catboost import CatBoostRegressor
model = CatBoostRegressor(
    iterations=500,
    learning_rate=0.05,
    depth=6,
    verbose=0
)
model.fit(X_train, y_train)
pred = model.predict(X_test)
3. XGBoost
from xgboost import XGBRegressor
model = XGBRegressor(
    n_estimators=500,
    learning_rate=0.05,
    max_depth=6,
    subsample=0.8,
    colsample_bytree=0.8
)
model.fit(X_train, y_train)
pred = model.predict(X_test)
4. RandomForest
from sklearn.ensemble import RandomForestRegressor
model = RandomForestRegressor(
    n_estimators=300,
    max_depth=None,
    random_state=42
)
model.fit(X_train, y_train)
pred = model.predict(X_test)
from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
import numpy as np

r2 = r2_score(y_test, pred)
rmse = mean_squared_error(y_test, pred, squared=False)
mae = mean_absolute_error(y_test, pred)
mape = np.mean(np.abs((y_test - pred) / y_test)) * 100

def adjusted_r2(r2, n, p): # n=샘플 수, p=독립변수 개수
    return 1 - (1 - r2) * (n - 1) / (n - p - 1)
adj_r2 = adjusted_r2(r2, len(y_test), X_test.shape[1])

R2 (결정계수): 0 ~ 1 (높을수록 좋음)

RMSE(평균 오차 크기): 작을수록 좋음

MAE(평균 절대 오차): 작을수록 좋음

MAPE(예측 대비 실제값 오차 비율): 10% 이하일 때 좋은 성능

Adjusted R2 (수정된 결정계수): 변수 수에 따라 보정된 R2

지표 목적 언제 사용하는가?
모델 설명력 기본 필수
Adjusted R² 변수 많을 때 다중회귀, 고차원 데이터
RMSE 큰 오차에 민감 실무 기본 평가
MAE 평균 오차 직관적 판단
MAPE 예측 오차 비율 시계열, 매출, 수요
MSE 비교용 RMSE의 기반

 

분류분석

1. LightGBM
from lightgbm import LGBMClassifier
model = LGBMClassifier(
    n_estimators=500,
    learning_rate=0.05,
    max_depth=-1,
    num_leaves=31
)
model.fit(X_train, y_train)
pred = model.predict(X_test)

2. CatBoost (범주형 처리)
from catboost import CatBoostClassifier
model = CatBoostClassifier(
    iterations=500,
    learning_rate=0.05,
    depth=6,
    verbose=0
)
model.fit(X_train, y_train)
pred = model.predict(X_test)

3. XGBoost
from xgboost import XGBClassifier
model = XGBClassifier(
    n_estimators=500,
    learning_rate=0.05,
    max_depth=6,
    subsample=0.8,
    colsample_bytree=0.8,
    eval_metric='logloss'
)
model.fit(X_train, y_train)
pred = model.predict(X_test)

4. RandomForest
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(
    n_estimators=300,
    max_depth=None,
    random_state=42
)
model.fit(X_train, y_train)
pred = model.predict(X_test)
from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score, roc_auc_score

acc = accuracy_score(y_test, pred)
f1 = f1_score(y_test, pred, average='binary'(이진분류) or 'macro'(다중분류))
prec = precision_score(y_test, pred)
rec = recall_score(y_test, pred)

proba = model.predict_proba(X_test)[:, 1]
roc = roc_auc_score(y_test, proba)

print("Accuracy:", acc)
print("F1:", f1)
print("Precision:", prec)
print("Recall:", rec)
print("ROC-AUC:", roc)

Accuracy(정확도): 전체 예측 중 맞춘 비율

Precision(정밀도): 양성으로 예측한 것 중 실제로 양성이 얼마나 있는가?

Recall(민감도, 재현율): 실제 양성을 얼마나 잘 찾았는가?

F1 Score: Precision과 Recall의 조화평균

ROC-AUC: 분류 Threshold(임계값)를 바꾸며 모델이 양성과 음성을 얼마나 잘 구분하는가?

지표 해석 값 범위 언제 중요한가 장점 단점
Accuracy 전체 정확도 0~1 데이터 균형일 때 직관적 불균형 데이터에서 무의미
Precision 양성 예측 중 얼마나 맞았는가 0~1 FP가 위험한 상황 FP 감소에 유리 FN 무시
Recall 실제 양성 중 얼마나 맞았나 0~1 FN이 위험한 상황 FN 감소에 유리 FP 증가 가능
F1 Score Precision+Recall 균형 0~1 불균형 데이터 두 지표의 균형 해석이 직관적이지 않음
ROC-AUC 전체 분류 능력 0.5~1.0 전반적 모델 평가 threshold 영향 없음 확률 모델 필요

 

시계열 모델링

1. ARIMA/SARIMA
import statsmodels.api as sm
model = sm.tsa.statespace.SARIMAX(
    y_train,
    order=(1, 1, 1),
    seasonal_order=(1, 1, 1, 12)
)
result = model.fit()
pred = result.predict(start=len(y_train), end=len(y_train)+len(y_test)-1)
print(result.summary())

2. Prophet
from prophet import Prophet
import pandas as pd
df_train = pd.DataFrame({'ds': X_train['date'], 'y': y_train})
model = Prophet()
model.fit(df_train)
future = pd.DataFrame({'ds': X_test['date']})
forecast = model.predict(future)
pred = forecast['yhat']

3. LightGBM
from lightgbm import LGBMRegressor
# Lag 변수 생성 예시
df['lag_1'] = df['y'].shift(1)
df['lag_7'] = df['y'].shift(7)
df['lag_30'] = df['y'].shift(30)
df = df.dropna()
X = df[['lag_1', 'lag_7', 'lag_30']]
y = df['y']
split_point = int(len(df) * 0.8)
X_train, X_test = X[:split_point], X[split_point:]
y_train, y_test = y[:split_point], y[split_point:]
model = LGBMRegressor(
    n_estimators=500,
    learning_rate=0.05,
    num_leaves=31
)
model.fit(X_train, y_train)
pred = model.predict(X_test)

4. LSTM
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
def create_dataset(series, timesteps=10):
    X, y = [], []
    for i in range(len(series) - timesteps):
        X.append(series[i:i+timesteps])
        y.append(series[i+timesteps])
    return np.array(X), np.array(y)
timesteps = 10
X, y = create_dataset(df['y'].values, timesteps=timesteps)
split = int(len(X) * 0.8)
X_train, X_test = X[:split], X[split:]
y_train, y_test = y[:split], y[split:]
model = Sequential()
model.add(LSTM(64, activation='tanh', input_shape=(timesteps, 1)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train, epochs=20, batch_size=32)
pred = model.predict(X_test)

 

모델 장점 단점 적합 데이터
ARIMA/SARIMA 통계 기반, 해석력 좋음 피처 사용 어려움 선형 + 계절성 구조
Prophet 자동으로 계절성/추세 처리 튜닝 어려울 때 있음 비즈니스 주기 데이터
LightGBM 회귀 피처 활용 최고, 정확도 최고 시계열 지식 필요 대규모/복잡 데이터
LSTM 비선형·복잡 패턴 강함 데이터 많이 필요 센서, 수요, 신호 예측
from sklearn.metrics import mean_squared_error, mean_absolute_error
import numpy as np

rmse = mean_squared_error(y_test, pred, squared=False)
mae = mean_absolute_error(y_test, pred)
mape = np.mean(np.abs((y_test - pred) / y_test)) * 100
smape = np.mean(2 * np.abs(y_test - pred) / (np.abs(y_test) + np.abs(pred))) * 100

def mase(y_test, pred, y_train):
    naive = np.mean(np.abs(np.diff(y_train)))
    return np.mean(np.abs(y_test - pred)) / naive

mase_score = mase(y_test, pred, y_train)

 

지표 해석 값 범위 언제 중요한가 장점 단점
RMSE (Root Mean Squared Error) 예측 오차의 크기(큰 오차에 특히 민감) 0~∞ (작을수록 좋음) 급등·급락이 큰 시계열(매출·수요 예측) 큰 오차를 강하게 반영해 모델 차이가 잘 드러남 이상치에 매우 민감
MAE (Mean Absolute Error) 평균 절대 오차(실제값과 예측값의 직관적 거리) 0~∞ (작을수록 좋음) 모델의 전반적 평균 오차를 보고 싶을 때 해석이 쉽고 직관적 큰 오차를 과소평가할 수 있음
MAPE (Mean Absolute Percentage Error) 예측 오차를 %로 표현한 지표 0%~∞ 매출/수요 등 "예측 대비 오차 비율"이 중요할 때 단위의 영향을 받지 않음 → 비교 용이 실제값이 0에 가까우면 수식이 폭발함
sMAPE (Symmetric MAPE) MAPE 개선판, 실제값=0 문제 해결 0%~200% 값이 0에 가까운 시계열(재고·센서 데이터) 0 문제 해결, 실제/예측에 대해 대칭적 해석이 MAPE보다 직관적이지 않음

3) 모델 학습 + Cross Validation

비시계열 데이터

# K-fold (회귀 모델)

from sklearn.model_selection import KFold, cross_val_score
from sklearn.metrics import mean_squared_error, make_scorer
import numpy as np

rmse_scorer = make_scorer(mean_squared_error, greater_is_better=False, squared=False)
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X_train, y_train, cv=kf, scoring=rmse_scorer)
print("개별 Fold RMSE:", -scores)
print("평균 RMSE:", -scores.mean())

# Stratified K-fold (분류 모델)
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.metrics import f1_score, make_scorer

f1_scorer = make_scorer(f1_score, average='binary') # 다중분류면 average='macro'
skf = StratifiedKFold(n_splits=5, shuffle=True)
scores = cross_val_score(model, X_train, y_train, cv=skf, scoring=f1_scorer)
print("개별 Fold F1:", scores)
print("평균 F1:", scores.mean())

 

시계열 데이터

# TimeSeriesSplit (시계열 전용 CV)
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import mean_absolute_error

tscv = TimeSeriesSplit(n_splits=5)
fold_scores = []
for train_idx, valid_idx in tscv.split(X):
    X_tr, X_val = X.iloc[train_idx], X.iloc[valid_idx]
    y_tr, y_val = y.iloc[train_idx], y.iloc[valid_idx]
    model.fit(X_tr, y_tr)
    pred = model.predict(X_val)
    fold_scores.append(mean_absolute_error(y_val, pred))

print("개별 Fold MAE:", fold_scores)
print("평균 MAE:", np.mean(fold_scores))

4) 하이퍼파라미터 튜닝

GridSearchCV

from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestRegressor

param_grid = {
    'n_estimators': [100, 300, 500],
    'max_depth': [None, 5, 10, 15],
    'min_samples_split': [2, 5, 10]
}

model = RandomForestRegressor()

grid = GridSearchCV(
    estimator=model,
    param_grid=param_grid,
    cv=5,
    scoring='neg_root_mean_squared_error',
    n_jobs=-1,
    verbose=1
)

grid.fit(X_train, y_train)

print("Best Params:", grid.best_params_)
print("Best Score (RMSE):", -grid.best_score_)

RandomizedSearchCV

from sklearn.model_selection import RandomizedSearchCV
from sklearn.ensemble import RandomForestRegressor
from scipy.stats import randint

param_dist = {
    'n_estimators': randint(100, 600),
    'max_depth': randint(3, 20),
    'min_samples_split': randint(2, 15)
}

model = RandomForestRegressor()

random_search = RandomizedSearchCV(
    estimator=model,
    param_distributions=param_dist,
    n_iter=30,
    cv=5,
    scoring='neg_root_mean_squared_error',
    n_jobs=-1,
    verbose=1,
    random_state=42
)

random_search.fit(X_train, y_train)

print("Best Params:", random_search.best_params_)
print("Best Score (RMSE):", -random_search.best_score_)

Optuna

import optuna
from lightgbm import LGBMRegressor
from sklearn.metrics import mean_squared_error

def objective(trial):
    params = {
        'n_estimators': trial.suggest_int('n_estimators', 200, 800),
        'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.2),
        'num_leaves': trial.suggest_int('num_leaves', 20, 80),
        'max_depth': trial.suggest_int('max_depth', -1, 15),
        'subsample': trial.suggest_float('subsample', 0.5, 1.0),
        'colsample_bytree': trial.suggest_float('colsample_bytree', 0.5, 1.0)
    }

    model = LGBMRegressor(**params)
    model.fit(X_train, y_train)
    pred = model.predict(X_test)

    rmse = mean_squared_error(y_test, pred, squared=False)
    return rmse

study = optuna.create_study(direction='minimize')
study.optimize(objective, n_trials=40)

print("Best Params:", study.best_params)
print("Best RMSE:", study.best_value)

 

방법 탐색 방식 속도 성능 언제 사용하는가?
GridSearchCV 모든 조합 탐색 느림 보통 소규모 파라미터
RandomizedSearchCV 랜덤 샘플링 빠름 좋음 중간 규모 파라미터
Optuna Bayesian Optimization 매우 빠름 최고 대규모·고성능 모델

5) 모델 해석

1. Feature Importance (특성 중요도)

Permutation Importance → 특징을 임의로 섞었을 때 성능 얼마나 떨어지는지

from sklearn.inspection import permutation_importance

result = permutation_importance(
    model, X_test, y_test, n_repeats=20, random_state=42
)

importances = result.importances_mean
sorted_idx = importances.argsort()

print("Permutation Importance:")
for i in sorted_idx[::-1]:
    print(f"{X_test.columns[i]}: {importances[i]:.4f}")

2. SHAP (최강의 모델 해석 도구)

import shap

explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)

shap.summary_plot(shap_values, X_test)

shap.summary_plot(shap_values, X_test, plot_type="bar")

shap.force_plot(
    explainer.expected_value, 
    shap_values[0], 
    X_test.iloc[0]
)

3. Partial Dependence Plot (PDP)

from sklearn.inspection import PartialDependenceDisplay

PartialDependenceDisplay.from_estimator(
    model,
    X_test,
    features=['temperature'],  # 또는 [0] 인덱스
    kind='average'
)

4. ICE Plot (Individual Conditional Expectation)

PartialDependenceDisplay.from_estimator(
    model,
    X_test,
    features=['humidity'],
    kind='individual'
)

 

기법 목적 장점 단점 언제 사용하는가?
Permutation Importance 변수 중요도 해석 간단 상호작용 고려 어려움 모델 전체 방향성 파악
SHAP Summary Plot 전체 영향도 + 국소 영향도 가장 정교함, 논문 수준 계산 비쌈(비트리 모델) 실무/논문/설명 책임 필요
SHAP Force Plot 개별 예측 근거 설명 Explainable AI에서 필수 많은 샘플에 사용 어려움 고객 맞춤형 설명, 의사결정
PDP(Partial Dependence) 단일 변수 영향 직관적 이질성 반영 어려움 전체 패턴 확인
ICE Plot 개별 변화 패턴 개별 경향 파악 해석 난이도 있음 비선형 데이터

📌추가로 해야할 점

태블로를 활용한 데이터 시각화, 라이브 세션