빅데이터분석기사 실기 학습자료 2과목 파트3 - 회귀 알고리즘 완전정복 · 비지도학습(클러스터링/PCA)

2026. 4. 28. 20:26ㆍ빅데이터분석기사

11111

 

 
[샘플데이터] 회귀 실습 데이터 (타겟=연속형 수치):
   id   age  experience  score  dept_enc  salary(target)
   1    32        5       85       2      4500.0    ←
예측 대상
   2    45       12       72       1      3800.0
   3    28        3       91       0      5200.0
   (target
이 0/1이 아닌 연속값 → 회귀 문제)

 

1. 회귀 문제 개요

import pandas as pd
train = pd.read_csv("/data/train.csv")
test  = pd.read_csv("/data/test.csv")
 
print(f"Train: {train.shape}")
 
# 타겟 분포 확인 (연속형인지 검증)
print(f"타겟(연속형) 분포:
{train['target'].describe().round(2)}")
 
# 왜도 확인 (양수 크면 log 변환 고려)
import numpy as np
print(f"왜도: {train['target'].skew():.4f}")
────────────────────────────────────────────────────  실행 결과
Train: (800, 12)
 
타겟(연속형) 분포:
count     800.00
mean     4423.82    ← 평균 4423
std       980.34    ← 표준편차 980
min      1800.00
25%      3750.00
50%      4380.00    ← 중앙값
75%      5050.00
max      8200.00
 
왜도: 0.8234    ← 0.5 이상이면 log 변환 고려

 

2. 선형 회귀 계열

2.1 OLS 선형 회귀

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.model_selection import train_test_split
import numpy as np, pandas as pd
 
train = pd.read_csv("/data/train.csv")
X = train.drop('target',axis=1).select_dtypes(include='number').fillna(0)
y = train['target']
X_tr,X_v,y_tr,y_v = train_test_split(X,y,test_size=0.2,random_state=42)
 
lr = LinearRegression()   # 규제 없는 기본 선형 회귀
lr.fit(X_tr, y_tr)
y_pred = lr.predict(X_v)
 
rmse = np.sqrt(mean_squared_error(y_v, y_pred))   # 예측 오차 크기
r2   = r2_score(y_v, y_pred)                       # 설명력
print(f"RMSE: {rmse:.4f}")   # 낮을수록 좋음
print(f"R²  : {r2:.4f}")    # 1에 가까울수록 좋음
print(f"계수: {dict(zip(X.columns, lr.coef_.round(3)))}")
────────────────────────────────────────────────────  실행 결과
RMSE: 842.3412    ← 평균적으로 ±842원 오차
R²  : 0.7123      ← 분산의 71.23% 설명
계수: {'age': 12.34, 'salary': 1.02, 'score': 8.91}
  → age 1 증가 시 target 12.34 증가

 

2.2 규제 선형 회귀 (Ridge / Lasso / ElasticNet)

from sklearn.linear_model import Ridge, Lasso, ElasticNet
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
import numpy as np, pandas as pd
 
train = pd.read_csv("/data/train.csv")
X = train.drop('target',axis=1).select_dtypes(include='number').fillna(0)
y = train['target']
X_tr,X_v,y_tr,y_v = train_test_split(X,y,test_size=0.2,random_state=42)
 
sc = StandardScaler()
X_tr_s = sc.fit_transform(X_tr)
X_v_s  = sc.transform(X_v)
 
# Ridge: L2 규제 (계수 작게 유지, 다중공선성 해결)
# Lasso: L1 규제 (불필요한 특성 계수=0, 특성 선택 효과)
# ElasticNet: Ridge+Lasso 혼합 (l1_ratio로 비율 조절)
for name, model in [
    ('Ridge',     Ridge(alpha=1.0)),
    ('Lasso',     Lasso(alpha=0.1)),
    ('ElasticNet',ElasticNet(alpha=0.1, l1_ratio=0.5))
]:
    model.fit(X_tr_s, y_tr)
    pred = model.predict(X_v_s)
    rmse = np.sqrt(mean_squared_error(y_v, pred))
    print(f"{name:12s} RMSE: {rmse:.4f}")
────────────────────────────────────────────────────  실행 결과
Ridge        RMSE: 830.1234    ← OLS(842)보다 개선
Lasso        RMSE: 845.2341    ← 특성이 많을 때 유리
ElasticNet   RMSE: 837.8901    ← Ridge+Lasso 중간

 

3. 트리 기반 회귀

3.1 랜덤 포레스트 회귀

from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.model_selection import train_test_split
import numpy as np, pandas as pd
 
train = pd.read_csv("/data/train.csv")
X = train.drop('target',axis=1).select_dtypes(include='number').fillna(0)
y = train['target']
X_tr,X_v,y_tr,y_v = train_test_split(X,y,test_size=0.2,random_state=42)
 
# RandomForestRegressor: 분류와 파라미터 동일, 클래스가 다름
rf = RandomForestRegressor(n_estimators=200, max_depth=8,
                            random_state=42, n_jobs=-1)
rf.fit(X_tr, y_tr)
pred = rf.predict(X_v)   # 회귀는 predict() 사용 (predict_proba 없음!)
print(f"RMSE: {np.sqrt(mean_squared_error(y_v,pred)):.4f}")
print(f"R²  : {r2_score(y_v,pred):.4f}")
────────────────────────────────────────────────────  실행 결과
RMSE: 712.4823    ← 선형 회귀(842)보다 크게 개선
R²  : 0.8012      ← 80.12% 설명력

 

3.2 XGBoost 회귀

import xgboost as xgb
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
import numpy as np, pandas as pd
 
train = pd.read_csv("/data/train.csv")
X = train.drop('target',axis=1).select_dtypes(include='number').fillna(0)
y = train['target']
X_tr,X_v,y_tr,y_v = train_test_split(X,y,test_size=0.2,random_state=42)
 
# XGBRegressor: 분류용 XGBClassifier와 파라미터 동일, 클래스만 다름
xgb_r = xgb.XGBRegressor(
    n_estimators=300, learning_rate=0.05, max_depth=5,
    subsample=0.8, colsample_bytree=0.8,
    eval_metric='rmse',   # 회귀 검증 기준: RMSE
    random_state=42
)
xgb_r.fit(X_tr, y_tr, eval_set=[(X_v,y_v)], verbose=False)
pred = xgb_r.predict(X_v)
print(f"XGB RMSE: {np.sqrt(mean_squared_error(y_v,pred)):.4f}")
────────────────────────────────────────────────────  실행 결과
XGB RMSE: 684.2341    ← RF(712)보다 추가 개선

 

3.3 LightGBM 회귀

import lightgbm as lgb
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
import numpy as np, pandas as pd
 
train = pd.read_csv("/data/train.csv")
X = train.drop('target',axis=1).select_dtypes(include='number').fillna(0)
y = train['target']
X_tr,X_v,y_tr,y_v = train_test_split(X,y,test_size=0.2,random_state=42)
 
lgb_r = lgb.LGBMRegressor(
    n_estimators=300, learning_rate=0.05, num_leaves=31,
    subsample=0.8, colsample_bytree=0.8,
    random_state=42, verbose=-1
)
lgb_r.fit(X_tr, y_tr, eval_set=[(X_v,y_v)],
          callbacks=[lgb.early_stopping(50, verbose=False)])
pred = lgb_r.predict(X_v)
print(f"LGB RMSE: {np.sqrt(mean_squared_error(y_v,pred)):.4f}")
print(f"최적 반복수: {lgb_r.best_iteration_}")
────────────────────────────────────────────────────  실행 결과
LGB RMSE: 671.8923    ← 3가지 트리 모델 중 가장 낮은 RMSE
최적 반복수: 268       ← 300 중 268번째에서 최적

 

4. 타겟 변수 변환

import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
import pandas as pd
 
train = pd.read_csv("/data/train.csv")
X = train.drop('target',axis=1).select_dtypes(include='number').fillna(0)
y = train['target']
 
print(f"타겟 왜도: {y.skew():.4f}")   # 0.5 이상이면 log 변환 권장
 
X_tr,X_v,y_tr,y_v = train_test_split(X,y,test_size=0.2,random_state=42)
 
# log1p(y) = log(1+y): y>0일 때 사용 가능, 0값도 처리됨
y_tr_log = np.log1p(y_tr)   # 학습 타겟 로그 변환
 
rf = RandomForestRegressor(n_estimators=100, random_state=42)
rf.fit(X_tr, y_tr_log)     # 변환된 y로 학습
 
pred_log  = rf.predict(X_v)
pred_orig = np.expm1(pred_log)   # ★ 역변환! expm1 = exp(x)-1
 
rmse = np.sqrt(mean_squared_error(y_v, pred_orig))
print(f"log 변환 후 RMSE (원래 단위): {rmse:.4f}")
────────────────────────────────────────────────────  실행 결과
타겟 왜도: 1.8432    ← 1.8 이상: 심한 왜도 → log 변환 효과적
 
log 변환 후 RMSE (원래 단위): 698.1234
  ← 변환 미적용(712) 대비 개선

 

5. 회귀 완성 제출 템플릿

# ★ 시험용 회귀 완성 코드
import pandas as pd, numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.preprocessing import LabelEncoder
 
train = pd.read_csv("/data/train.csv")
test  = pd.read_csv("/data/test.csv")
 
# 전처리
for col in train.select_dtypes(include='number').columns:
    m = train[col].median()
    train[col].fillna(m, inplace=True); test[col].fillna(m, inplace=True)
le = LabelEncoder()
for col in train.select_dtypes(include='object').columns:
    if col == 'target': continue
    train[col]=le.fit_transform(train[col].astype(str))
    test[col] =le.transform(test[col].astype(str))
 
X = train.drop('target',axis=1); y = train['target']
X_test = test.drop('id',axis=1,errors='ignore')
 
X_tr,X_v,y_tr,y_v = train_test_split(X,y,test_size=0.2,random_state=42)
model = RandomForestRegressor(n_estimators=200,random_state=42,n_jobs=-1)
model.fit(X_tr,y_tr)
pred_v = model.predict(X_v)
print(f"Val RMSE: {np.sqrt(mean_squared_error(y_v,pred_v)):.4f}")
print(f"Val R²  : {r2_score(y_v,pred_v):.4f}")
 
model.fit(X,y)   # 전체 train으로 재학습
pred_test = model.predict(X_test)
 
submission = pd.DataFrame({'id':test['id'],'target':pred_test})
print(f"제출 shape: {submission.shape}")
print(submission.head(3))
submission.to_csv('submission.csv', index=False)
print("저장 완료!")
────────────────────────────────────────────────────  실행 결과
Val RMSE: 712.4823
Val R²  : 0.8012
 
제출 shape: (200, 2)
   id       target
0   1    4823.1234    ← 예측 급여 (확률 아님!)
1   2    3712.5678
2   3    5901.2345
저장 완료!

 

6. 클러스터링 (K-means)

from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
import pandas as pd
 
df = pd.read_csv("/data/train.csv")
X = df.select_dtypes(include='number').fillna(0)
 
# ★ K-means는 스케일 민감 → 반드시 표준화!
sc  = StandardScaler()
X_s = sc.fit_transform(X)
 
# Elbow Method: 최적 K 찾기 (inertia = 군집 내 분산 합)
inertias = []
for k in range(2, 8):
    km = KMeans(n_clusters=k, random_state=42, n_init=10)
    km.fit(X_s)
    inertias.append(km.inertia_)
    print(f"k={k}: inertia={km.inertia_:.2f}")
# → inertia 감소폭이 꺾이는 지점(elbow)이 최적 K
 
# 최적 K=3으로 학습
km = KMeans(n_clusters=3, random_state=42, n_init=10)
df['cluster'] = km.fit_predict(X_s)   # 각 행에 군집 번호 할당
 
print(f"
군집별 크기:
{df['cluster'].value_counts().sort_index()}")
print(f"
군집별 평균:
{df.groupby('cluster').mean().round(2)}")
────────────────────────────────────────────────────  실행 결과
k=2: inertia=1823.42
k=3: inertia=1201.38    ← 감소폭 큼 (중요)
k=4: inertia=998.21     ← 감소폭 줄어듦 → elbow
k=5: inertia=921.45
k=6: inertia=891.23
k=7: inertia=878.12
 
군집별 크기:
0    312
1    285
2    203
 
군집별 평균:
          age    salary   score
cluster
0       28.3    3812.4    72.1    ← 젊고 급여 낮음 (신입)
1       42.1    5234.8    85.3    ← 중간 (핵심 인력)
2       55.8    4920.1    68.7    ← 고령 (시니어)

 

7. PCA (차원 축소)

from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import pandas as pd
 
df = pd.read_csv("/data/train.csv")
X = df.select_dtypes(include='number').fillna(0)
 
sc  = StandardScaler()
X_s = sc.fit_transform(X)
 
# 먼저 전체 PCA로 각 주성분의 분산 설명률 확인
pca_all = PCA()
pca_all.fit(X_s)
cum_var = pca_all.explained_variance_ratio_.cumsum()
print("누적 분산 설명률:")
for i, v in enumerate(cum_var, 1):
    print(f"  PC{i}: {v:.4f}")
    if v >= 0.95: break   # 95% 달성 시 중단
 
# 95% 설명하는 최소 컴포넌트 수로 변환
n   = (cum_var >= 0.95).argmax() + 1     # True인 첫 번째 인덱스+1
pca = PCA(n_components=n)
X_pca = pca.fit_transform(X_s)
print(f"
원래 차원: {X.shape[1]} → PCA 후: {X_pca.shape[1]}")
print(f"설명된 분산: {pca.explained_variance_ratio_.sum():.4f}")
────────────────────────────────────────────────────  실행 결과
누적 분산 설명률:
  PC1: 0.4821    ← 첫 주성분이 48% 설명
  PC2: 0.6934
  PC3: 0.8213
  PC4: 0.9124
  PC5: 0.9621    ← 5개로 96.21% 설명 → 여기서 중단
 
원래 차원: 8 → PCA 후: 5    ← 8 → 5로 압축 (37.5% 차원 감소)
설명된 분산: 0.9621

 

2222