물고기 분류 문제잡은 물고기를 재고, 분류 모델이 판단해, 종별 상자로 나누는 모습을 픽셀 그림으로 표현① 잡은 물고기를 잰다????② 판단을 더 정확하게모델은 그대로평가 · 학습 · 특성 · 조합을 개선③ 종별로 나눈다도미빙어농어로치7종 정확도 0.899 → 0.980 (특성 2개 추가)
Fish Market Data로 쉽게 따라하는 머신러닝 · 2

모델을 바꾸기 전에, 성능을 올리는 네 가지 방법

분류 모델의 성능이 기대에 못 미칠 때, 모델을 바꾸기 전에 할 수 있는 일이 많습니다. 이 가이드는 모델은 그대로 두고 성능을 올리는 방법을 다룹니다. 점수를 믿을 수 있게 평가를 바로잡고(1), 가중치를 찾아가는 학습 과정을 조정하고(2), 특성을 만들되 과적합을 규제하고(3), 마지막으로 여러 모델을 묶습니다(4).

● 클래스 A · ○ 클래스 B · 점선 밑줄 단어는 눌러서 비유 보기

쉬운 설명판에는 장마다 "물고기로 보면" 칸이 있어, 잡은 물고기의 무게·길이·높이·두께로 종을 맞히는 문제에 같은 방법을 실제로 적용한 결과를 보여 줍니다. 어려운 단어는 점선 밑줄을 누르면 비유로 볼 수 있고, 맨 뒤에 용어 사전이 있습니다.

0

출발점 — 무엇이 문제인지부터Where to start

모델을 바꾸기 전에 확인할 네 곳이 있습니다. 증상으로 해당 장을 찾으세요. 낯선 용어는 점선 밑줄을 누르거나 맨 뒤 용어 사전에서 볼 수 있습니다.

0증상에서 출발하기

개선은 "무엇이 문제인지"를 먼저 알아야 방향이 정해집니다. 기준선 점수를 적어 둔 뒤, 아래 증상 중 어디에 해당하는지 찾아 해당 장으로 가면 됩니다.

이런 증상이 보이면먼저 볼 곳고치는 대상
나눌 때마다 점수가 크게 달라진다1. 데이터 분할점수를 믿을 수 있게
정확도는 높은데 소수 클래스를 거의 못 찾는다1. 불균형과 지표무엇을 잴지
손실이 줄지 않거나, 튀거나, 너무 느리게 준다2. 학습 과정가중치를 찾는 과정
학습 점수와 시험 점수가 둘 다 낮다3. 특성 생성모델이 볼 재료
학습 점수는 높은데 시험 점수가 낮다3. 규제모델의 복잡도
모델 하나의 판단이 불안정하거나 한쪽으로 치우친다4. 여러 모델 혼합판단을 묶는 방식
1

데이터 분할의 오류 바로잡기Stratify · Cross-validation

점수가 모델의 실력인지, 나눈 운인지부터 가립니다.

①무엇이 문제인가 · 한 번만 나누면 점수가 운에 달린다

점 120개가 있습니다. 파란 점(다수 클래스) 102개, 주황 빈 원(소수 클래스) 18개로 소수가 15%입니다. 이 점들을 학습 세트 84개와 시험 세트 36개로 나눈 뒤, 학습 세트만 보고 KNN(k = 5)을 학습해 시험 세트를 채점합니다. 배경색은 모델의 판단입니다. "다시 나누기"를 누를 때마다 데이터도 모델도 같은데 정확도가 달라집니다.

분할 방식

지금까지 나눌 때마다 나온 시험 정확도

층화로 바꾸면 시험 세트의 소수 클래스가 매번 5개(전체와 같은 약 15%)로 고정됩니다. 무작위는 나눌 때마다 2~9개 사이로 흔들려, 소수 클래스를 거의 못 본 채 채점되는 경우가 생깁니다.

쉽게 말하면반 대표를 뽑는 시험에서 문제지를 딱 한 장만 뽑으면, 우연히 쉬운 문제가 걸린 학생이 뽑힐 수 있습니다. 문제지를 바꿔 가며 여러 번 보고 평균을 내면 운이 걸러집니다. 이것이 교차검증입니다. 층화 추출(stratify)은 여론조사에서 연령대 비율을 인구와 똑같이 맞춰 표본을 뽑는 것과 같습니다.

②원리와 수식
ntest, k ≈ ntest × (nk / n)층화 추출: 시험 세트에서 클래스 k의 개수를 전체 비율대로 맞춥니다.
CV = (1/K) Σk=1K sk,   함께 보고: 표준편차 σK겹 교차검증: K번의 점수 sk를 평균하고, 흔들림(σ)도 함께 적습니다.
정밀도 = TP / (TP + FP),   재현율 = TP / (TP + FN),   F1 = 2PR / (P + R)불균형 데이터에서는 정확도 대신 이 지표를 함께 봅니다. 네 칸 TP·FN·FP·TN이 무엇이고 실제 숫자가 어떻게 들어가는지는 ④의 혼동행렬에서 봅니다.
세 데이터의 역할
  1. 학습 세트가중치를 배우는 데이터. 교과서입니다.
  2. 검증 (교차검증)하이퍼파라미터를 고르고 비교하는 데이터. 모의고사입니다. 따로 떼지 않고 교차검증으로 대신하는 경우가 많습니다.
  3. 시험 세트모든 선택이 끝난 뒤 맨 마지막에 한 번만 보는 데이터. 수능입니다. 여러 번 보고 고르면 시험 세트에 과적합됩니다.
③교차검증 · 모든 점이 한 번씩 시험을 본다

①과 같은 점 120개, 같은 모델(KNN, k = 5)입니다. 이번에는 점을 클래스 비율을 맞춰(층화) 다섯 조각으로 나누고, 돌아가며 한 조각을 시험 세트로 씁니다. 각 그림에서 진한 점이 그 회차의 시험 조각, 흐린 점이 학습용입니다. 다섯 점수의 평균이 교차검증 점수입니다.

①의 "한 번 나누기" 점수와 교차검증 평균, 어느 쪽이 덜 흔들릴까

①에서 여러 번 나눌수록 윗줄 점이 쌓입니다. 아랫줄(교차검증 평균)이 훨씬 좁게 모이는 것을 확인해 보세요. 한 번 나눈 점수 대신 교차검증 평균을 보고해야 하는 이유입니다.

④함께 점검할 것 · 누수와 혼동행렬
데이터 누수: 시험 답안지를 미리 본 실수
  • 분할 전에 스케일링·결측치 채우기: 시험 데이터의 평균이 학습에 섞입니다. 전처리는 Pipeline 안에 넣어 학습 조각에서만 계산하게 합니다.
  • 중복 데이터: 같은 사람·같은 문서가 학습과 시험에 동시에 들어가면 외운 답으로 맞힙니다. 사람 단위로 나누려면 GroupKFold를 씁니다.
  • 시간 순서 무시: 미래 데이터로 과거를 맞히는 셈입니다. 시계열은 TimeSeriesSplit으로 과거→미래 순서를 지킵니다.
혼동행렬: 정확도 하나로는 안 보이는 것

①에서 지금 나눈 시험 세트를 네 칸으로 나눠 셉니다. 소수 클래스를 찾아야 할 대상(양성)으로 봅니다.

모델
예측: 소수 (양성)
예측: 다수 (음성)
실제: 소수 (양성)
TP0맞게 찾음
FN0놓침
실제: 다수 (음성)
FP0헛경보
TN0맞게 거름

실제 데이터에서도 같은 일이 일어납니다. 아래 ⑤ 코드의 결과에서 기본 로지스틱 회귀는 정확도 0.917이지만 소수 클래스 재현율은 0.357입니다. class_weight="balanced"로 소수 클래스에 무게를 주면 재현율이 0.700으로 오르고 정확도는 0.823으로 내려갑니다. 무엇을 우선할지(놓치지 않기 vs 헛경보 줄이기)는 문제에 따라 정합니다.

⑤코드와 실제 결과
# 1. 데이터 분할: stratify와 교차검증
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

# 소수 클래스가 10%인 불균형 데이터
X, y = make_classification(n_samples=300, n_features=6, n_informative=4,
                           weights=[0.9, 0.1], random_state=0)

# (1) stratify 없이 20번 나누기 vs stratify로 20번 나누기: 테스트 세트의 소수 클래스 비율
plain, strat = [], []
for seed in range(20):
    _, _, _, y_te = train_test_split(X, y, test_size=0.2, random_state=seed)
    plain.append(y_te.mean())
    _, _, _, y_te = train_test_split(X, y, test_size=0.2, random_state=seed, stratify=y)
    strat.append(y_te.mean())
print(f"전체 소수 비율 {y.mean():.3f}")
print(f"stratify 없음: 테스트 소수 비율 {min(plain):.3f} ~ {max(plain):.3f}")
print(f"stratify 사용: 테스트 소수 비율 {min(strat):.3f} ~ {max(strat):.3f}")

# (2) 한 번 나눈 점수는 분할 운에 따라 흔들린다
model = make_pipeline(StandardScaler(), LogisticRegression())   # 스케일링을 파이프라인 안에: 누수 방지
single = []
for seed in range(20):
    X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=seed, stratify=y)
    single.append(model.fit(X_tr, y_tr).score(X_te, y_te))
print(f"한 번 분할 정확도 20회: {min(single):.3f} ~ {max(single):.3f}")

# (3) 층화 5겹 교차검증: 평균과 표준편차로 보고
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
acc = cross_val_score(model, X, y, cv=cv, scoring="accuracy")
rec = cross_val_score(model, X, y, cv=cv, scoring="recall")
print(f"5겹 정확도 {acc.mean():.3f} ± {acc.std():.3f}")
print(f"5겹 재현율(소수 클래스) {rec.mean():.3f} ± {rec.std():.3f}")

# (4) 정확도의 함정: 전부 다수 클래스라고 답하는 모델
print(f"모두 다수 클래스로 찍어도 정확도 {1 - y.mean():.3f}, 재현율 0.000")

# (5) 불균형 보정: 소수 클래스에 더 큰 가중치
balanced = make_pipeline(StandardScaler(), LogisticRegression(class_weight="balanced"))
rec_b = cross_val_score(balanced, X, y, cv=cv, scoring="recall")
acc_b = cross_val_score(balanced, X, y, cv=cv, scoring="accuracy")
print(f"class_weight='balanced': 재현율 {rec_b.mean():.3f}, 정확도 {acc_b.mean():.3f}")

실행 결과 (scikit-learn 1.8, CPU)

전체 소수 비율 0.113
stratify 없음: 테스트 소수 비율 0.050 ~ 0.183
stratify 사용: 테스트 소수 비율 0.117 ~ 0.117
한 번 분할 정확도 20회: 0.867 ~ 0.967
5겹 정확도 0.917 ± 0.018
5겹 재현율(소수 클래스) 0.357 ± 0.128
모두 다수 클래스로 찍어도 정확도 0.887, 재현율 0.000
class_weight='balanced': 재현율 0.700, 정확도 0.823
🐟물고기로 보면 · 같은 물고기의 복제본이 점수를 부풀린다

이 물고기 1,554마리는 Kaggle의 Fish Market 원본 159마리를 정규분포를 고려해 증강한 것이라, 같은 원본에서 나온 물고기가 9~10마리씩 있습니다(같은 seed_id). 마리 단위로 섞어 나누면 시험 세트의 물고기와 거의 똑같은 "쌍둥이"가 학습 세트에 이미 들어 있습니다.

분할 방법별 5겹 교차검증 정확도분할 방법별 5겹 교차검증 정확도0.600.700.800.901.00KNN (k = 1)0.9590.9200.810로지스틱 회귀0.9090.8990.804의사결정나무0.8840.8720.735행 단위 분할 (누수)물고기 단위 분할원본만 158마리

읽는 법. 가장 가까운 한 마리를 베끼는 KNN(k = 1)이 누수에 가장 크게 속습니다(0.959 → 0.920). 쌍둥이가 바로 옆에 있으니까요. 물고기 단위로 나누는 StratifiedGroupKFold가 "처음 보는 물고기"에 대한 정직한 점수입니다. 원본만 쓴 점수가 더 낮은 것은 누수 때문이 아니라 데이터가 158행뿐이라 학습 재료가 적기 때문입니다.

층화도 함께. 흰송어는 48행이지만 물고기로는 6마리뿐입니다. 물고기 단위로 5겹을 나누면 어떤 조각에는 흰송어가 한 마리도 없을 수 있습니다. StratifiedGroupKFold는 물고기를 묶어 두면서도 종의 비율을 최대한 고르게 맞춥니다.

코드 보기 (물고기 데이터, scikit-learn)
import pandas as pd, numpy as np, warnings; warnings.filterwarnings("ignore")
# 데이터 불러오기와 정리: 무게 0인 행과 완전히 같은 행을 뺀다
df = pd.read_csv("fish_market_x10.csv")
df = df[df["weight_g"] > 0].drop_duplicates().reset_index(drop=True)
FEATS = ["weight_g", "length1_cm", "length2_cm", "length3_cm", "height_cm", "width_cm"]
X, y, groups = df[FEATS], df["species"], df["seed_id"]   # seed_id: 같은 물고기에서 만든 복제본 묶음

# 1장 · 물고기 데이터로 보는 누수: 같은 물고기의 복제본이 학습과 시험에 나뉘면?
from sklearn.model_selection import StratifiedKFold, StratifiedGroupKFold, cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier

print(df.groupby("species").agg(행=("seed_id", "size"), 물고기=("seed_id", "nunique")).sort_values("행", ascending=False).T)

models = {"KNN (k=1)": make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=1)),
          "로지스틱 회귀": make_pipeline(StandardScaler(), LogisticRegression(max_iter=5000)),
          "의사결정나무": DecisionTreeClassifier(random_state=0)}
plain = StratifiedKFold(5, shuffle=True, random_state=0)          # 행 단위로 섞어 나눔 (누수)
grouped = StratifiedGroupKFold(5, shuffle=True, random_state=0)   # 같은 seed_id는 한쪽에만
orig = df[df["source"] == "original"]
print(f"\n{'모델':<10}{'행 단위 분할':>10}{'물고기 단위 분할':>12}{'원본만 (158마리)':>12}")
for name, m in models.items():
    a = cross_val_score(m, X, y, cv=plain).mean()
    b = cross_val_score(m, X, y, cv=grouped, groups=groups).mean()
    c = cross_val_score(m, orig[FEATS], orig["species"], cv=StratifiedKFold(5, shuffle=True, random_state=0)).mean()
    print(f"{name:<10}{a:>10.3f}{b:>12.3f}{c:>12.3f}")

실행 결과 (scikit-learn 1.8, CPU)

species  Perch  Bream  Roach  Pike  Smelt  Parkki  Whitefish
행          560    348    192   164    138     104         48
물고기         56     35     19    17     14      11          6

모델           행 단위 분할   물고기 단위 분할 원본만 (158마리)
KNN (k=1)      0.959       0.920       0.810
로지스틱 회귀        0.909       0.899       0.804
의사결정나무         0.884       0.872       0.735
✓확인 문제

Q1. 소수 클래스가 5%인 데이터에서 "모두 다수 클래스"라고 답하는 모델의 정확도는?

Q2. StandardScaler를 전체 데이터에 먼저 적용한 뒤 train_test_split을 하면?

2

학습 과정 조정하기Gradient descent · Learning rate · Epoch · Batch

가중치를 찾아가는 보폭과 방식을 맞춥니다.

①무엇이 문제인가

같은 모델이라도 가중치를 찾아가는 방식이 나쁘면 성능이 나오지 않습니다. 손실이 줄지 않거나, 줄다가 튀거나, 너무 느리게 줄어드는 경우입니다.

0.500 → 0.943 → 0.500학습률만 0.0001 → 0.5 → 20으로 바꾼 같은 신경망의 시험 정확도
700번 vs 1번데이터 700개로 한 에포크를 돌 때, 배치 1과 배치 700의 가중치 갱신 횟수
26 에포크조기 종료가 멈춘 시점 (최대 500 에포크 중), 시험 정확도 0.940

적용 대상 · 이 장은 경사하강법으로 가중치를 찾는 모델에 해당합니다: 로지스틱 회귀, 퍼셉트론, 선형 SVM(SGD 방식), 신경망. KNN과 의사결정나무는 경사하강법을 쓰지 않으므로 해당하지 않습니다.

쉽게 말하면짙은 안개가 낀 산에서 가장 낮은 골짜기를 찾는다고 해 봅시다. 멀리는 안 보이고 발밑의 기울어짐만 느낄 수 있습니다. 경사하강법은 기울어진 아래쪽으로 한 걸음씩 옮기는 방법이고, 학습률은 보폭, 배치는 방향을 정하기 전에 길을 물어보는 사람 수, 에포크는 모든 사람에게 한 번씩 다 물어본 횟수입니다.

②원리와 수식
w ← w − η ∇L(w)한 걸음: 기울기 ∇L의 반대 방향으로 보폭 η만큼 이동합니다.
∇L(w) ≈ (1/B) Σi ∈ 배치 ∇ℓi(w)전체 데이터 대신 B개 묶음의 평균 기울기로 방향을 어림합니다. B가 작을수록 어림이 흔들립니다.
한 에포크의 갱신 횟수 = ⌈ n / B ⌉데이터 n개, 배치 B개일 때. 예: n = 700, B = 32이면 22번.
수식 한 조각씩 읽기
  1. ∇L(w)"나블라 L". 가중치마다 손실이 얼마나 가파르게 변하는지 모은 화살표입니다. 오르막 방향을 가리키므로 빼 줍니다.
  2. η 너무 작음안전하지만 느립니다. 정해진 에포크 안에 바닥에 못 갈 수 있습니다.
  3. η 너무 큼골짜기를 건너뛰어 반대편 벽으로 가고, 심하면 점점 멀어져 발산합니다.
  4. ⌈ · ⌉올림 기호. 나누어떨어지지 않으면 마지막 작은 묶음까지 한 번 더 셉니다.
  5. 모멘텀굴러가던 공의 관성처럼, 이전 걸음의 방향을 일부 이어 갑니다. 좁은 골짜기에서 좌우로 흔들리는 것을 줄이고 평평한 구간을 빨리 지나갑니다. 아래 코드의 momentum=0.0은 순수 경사하강만 비교하려고 일부러 끈 것입니다.
  6. Adam가중치마다 보폭을 자동으로 조절하는 경사하강법의 변형입니다. 자주 크게 흔들린 방향은 보폭을 줄이고, 꾸준한 방향은 늘립니다. 모멘텀도 함께 씁니다. 학습률을 덜 신경 써도 되어 신경망의 기본값(solver="adam")입니다.
  7. 스케일링과 경사하강특성의 단위가 크게 다르면 손실 지형이 한쪽으로 길쭉한 골짜기가 되어, 한 학습률로는 한 방향은 너무 느리고 다른 방향은 튑니다. 표준화로 단위를 맞추면 지형이 둥글어져 훨씬 빨리 내려갑니다. 그래서 경사하강법을 쓰는 모델에는 스케일링이 사실상 필수입니다.
③움직여 보기 · 학습률(보폭) 바꾸기

손실 곡선 위에서 공이 w ← w − η × 기울기로 움직입니다. η를 0.01, 0.1, 0.45, 0.6 정도로 바꿔 가며 "자동으로 굴리기"를 눌러 보세요. 출발점에 따라 작은 웅덩이(지역 최솟값)에 갇히는지도 볼 수 있습니다.

출발점

③움직여 보기 · 배치 크기와 에포크

등고선은 손실 지형을 위에서 본 모습이고, 가운데가 가장 낮습니다. 같은 출발점과 같은 학습률(η = 0.12)에서 배치 크기만 다르게 합니다(데이터 40개, 실제 계산). 비교 기준을 바꿔 보세요. "같은 에포크"는 본 데이터 양을, "같은 갱신 횟수"는 걸음 수를 맞춘 비교입니다. 기준에 따라 결론이 달라집니다.

비교 기준

④조기 종료 · 언제 멈출까
조기 종료에포크가 늘수록 학습 손실은 계속 줄지만 검증 손실은 줄다가 다시 오르며, 그 최저점에서 멈춘다에포크 →손실여기서 멈춤학습 손실검증 손실

학습 손실(실선)은 에포크가 늘수록 계속 줄지만, 검증 손실(점선)은 어느 순간부터 다시 오릅니다. 그때부터는 새로운 규칙을 배우는 대신 학습 데이터를 외우기 시작한 것입니다(과적합).

조기 종료는 검증 점수가 정해진 횟수(n_iter_no_change) 동안 나아지지 않으면 멈추고, 가장 좋았던 시점의 모델을 씁니다. "에포크를 몇으로 할까"라는 고민을 대신해 줍니다. 그림은 개념을 보여 주는 예시 곡선입니다.

⑤코드와 실제 결과
# 2. 학습 과정 조정: 학습률, 에포크, 배치
import math, warnings; warnings.filterwarnings("ignore")
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neural_network import MLPClassifier

X, y = make_moons(n_samples=1000, noise=0.25, random_state=0)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=0, stratify=y)
sc = StandardScaler().fit(X_tr); X_tr, X_te = sc.transform(X_tr), sc.transform(X_te)

# momentum=0.0: 모멘텀을 꺼서 순수 경사하강법끼리 비교
print("학습률 비교 (배치 32, 에포크 50, 순수 SGD)")
for lr in [0.0001, 0.01, 0.5, 20.0]:
    m = MLPClassifier(hidden_layer_sizes=(16, 16), solver="sgd", momentum=0.0,
                      learning_rate_init=lr, batch_size=32, max_iter=50, random_state=0)
    m.fit(X_tr, y_tr)
    print(f"  η = {lr:<7} 마지막 손실 {m.loss_:.3g}  테스트 정확도 {m.score(X_te, y_te):.3f}")

# 주의: 같은 η, 같은 에포크 비교라서 갱신 횟수가 많은 작은 배치가 유리합니다.
#       실무에서는 배치를 키우면 학습률도 함께 키웁니다.
print("배치 크기 비교 (η = 0.01, 에포크 50)")
for bs in [1, 32, len(X_tr)]:
    m = MLPClassifier(hidden_layer_sizes=(16, 16), solver="sgd", momentum=0.0,
                      learning_rate_init=0.01, batch_size=bs, max_iter=50, random_state=0)
    m.fit(X_tr, y_tr)
    print(f"  배치 {bs:<4} 한 에포크당 갱신 {math.ceil(len(X_tr) / bs):>3}번  손실 {m.loss_:.3f}  테스트 {m.score(X_te, y_te):.3f}")

print("조기 종료 (검증 점수가 10 에포크 동안 나아지지 않으면 멈춤)")
# solver="adam": 가중치마다 보폭을 자동 조절하고 모멘텀도 쓰는 방식
m = MLPClassifier(hidden_layer_sizes=(64, 64), solver="adam", learning_rate_init=0.01,
                  max_iter=500, early_stopping=True, validation_fraction=0.15,
                  n_iter_no_change=10, random_state=0).fit(X_tr, y_tr)
print(f"  {m.n_iter_} 에포크에서 멈춤, 테스트 정확도 {m.score(X_te, y_te):.3f}")

실행 결과 (scikit-learn 1.8, CPU)

학습률 비교 (배치 32, 에포크 50, 순수 SGD)
  η = 0.0001  마지막 손실 0.695  테스트 정확도 0.500
  η = 0.01    마지막 손실 0.304  테스트 정확도 0.870
  η = 0.5     마지막 손실 0.158  테스트 정확도 0.943
  η = 20.0    마지막 손실 2.96e+36  테스트 정확도 0.500
배치 크기 비교 (η = 0.01, 에포크 50)
  배치 1    한 에포크당 갱신 700번  손실 0.161  테스트 0.940
  배치 32   한 에포크당 갱신  22번  손실 0.304  테스트 0.870
  배치 700  한 에포크당 갱신   1번  손실 0.629  테스트 0.503
조기 종료 (검증 점수가 10 에포크 동안 나아지지 않으면 멈춤)
  26 에포크에서 멈춤, 테스트 정확도 0.940
🐟물고기로 보면 · 무게는 수백, 두께는 한 자릿수: 스케일링이 학습률보다 먼저

농어와 로치를 특성 6개로 가르는 로지스틱 회귀를 경사하강법(SGD)으로 학습합니다. 무게의 표준편차는 약 299 g, 두께는 1.6 cm로 단위가 200배 가까이 다릅니다.

에포크별 손실원래 단위와 표준화한 특성에서 학습률별 손실 곡선원래 단위 (무게가 수백 g)0918에포크 → 30표준화 (평균 0, 표준편차 1)00.3750.75에포크 → 30η = 0.0001η = 0.01η = 0.1
특성η = 0.0001η = 0.01η = 0.1
원래 단위손실 0.635, 시험 0.576손실 15.7, 시험 0.331손실 13.8, 시험 0.457
표준화손실 0.606, 시험 0.689손실 0.326, 시험 0.874손실 0.165, 시험 0.815

읽는 법. 원래 단위에서는 무게 방향의 기울기가 너무 커서, 작은 학습률(0.0001)은 거의 움직이지 못하고 조금만 키워도(0.01) 손실이 15 이상으로 튑니다. 어떤 학습률도 맞지 않는 길쭉한 골짜기입니다. 표준화하면 같은 학습률 0.01로 손실이 매끄럽게 줄어 시험 정확도 0.874에 이릅니다. 학습률을 고르기 전에 단위부터 맞춰야 하는 이유입니다. η = 0.1은 학습 손실이 가장 낮지만 시험 점수는 0.815로 오히려 낮아, 손실만 보고 고르면 안 된다는 것도 보여 줍니다.

코드 보기 (물고기 데이터, scikit-learn)
import pandas as pd, numpy as np, warnings; warnings.filterwarnings("ignore")
# 데이터 불러오기와 정리: 무게 0인 행과 완전히 같은 행을 뺀다
df = pd.read_csv("fish_market_x10.csv")
df = df[df["weight_g"] > 0].drop_duplicates().reset_index(drop=True)
FEATS = ["weight_g", "length1_cm", "length2_cm", "length3_cm", "height_cm", "width_cm"]
X, y, groups = df[FEATS], df["species"], df["seed_id"]   # seed_id: 같은 물고기에서 만든 복제본 묶음

# 2장 · 물고기 데이터로 보는 스케일링과 학습률: 농어 vs 로치, 특성 6개
from sklearn.model_selection import GroupShuffleSplit
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import SGDClassifier
from sklearn.metrics import log_loss

d = df[df["species"].isin(["Perch", "Roach"])]
tr, te = next(GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=0).split(d, groups=d["seed_id"]))
Xtr, Xte = d[FEATS].values[tr], d[FEATS].values[te]
ytr, yte = (d["species"].values[tr] == "Roach"), (d["species"].values[te] == "Roach")
print("특성의 크기 차이:", {f: round(float(d[f].std()), 1) for f in FEATS})

def run(Xa, Xb, eta, epochs=30):
    m = SGDClassifier(loss="log_loss", learning_rate="constant", eta0=eta, random_state=0)
    losses = []
    for _ in range(epochs):                     # partial_fit 한 번 = 1 에포크
        m.partial_fit(Xa, ytr, classes=[False, True])
        losses.append(log_loss(ytr, m.predict_proba(Xa), labels=[False, True]))
    return losses, m.score(Xb, yte)

sc = StandardScaler().fit(Xtr)
for label, Xa, Xb in [("원래 단위", Xtr, Xte), ("표준화", sc.transform(Xtr), sc.transform(Xte))]:
    for eta in [0.0001, 0.01, 0.1]:
        L, acc = run(Xa, Xb, eta)
        print(f"{label:<6} η={eta:<7} 손실: 1에포크 {L[0]:7.3f} → 30에포크 {L[-1]:7.3f}   시험 정확도 {acc:.3f}")

실행 결과 (scikit-learn 1.8, CPU)

특성의 크기 차이: {'weight_g': 299.4, 'length1_cm': 7.5, 'length2_cm': 7.9, 'length3_cm': 8.2, 'height_cm': 2.5, 'width_cm': 1.6}
원래 단위  η=0.0001  손실: 1에포크   0.688 → 30에포크   0.635   시험 정확도 0.576
원래 단위  η=0.01    손실: 1에포크  13.458 → 30에포크  15.737   시험 정확도 0.331
원래 단위  η=0.1     손실: 1에포크  15.717 → 30에포크  13.780   시험 정확도 0.457
표준화    η=0.0001  손실: 1에포크   0.685 → 30에포크   0.606   시험 정확도 0.689
표준화    η=0.01    손실: 1에포크   0.558 → 30에포크   0.326   시험 정확도 0.874
표준화    η=0.1     손실: 1에포크   0.440 → 30에포크   0.165   시험 정확도 0.815
✓확인 문제

Q1. 학습률을 너무 크게 잡으면 흔히 생기는 일은?

Q2. 데이터 1,000개, 배치 크기 50으로 3 에포크를 학습하면 가중치 갱신은 몇 번?

3

변수 생성과 그 부작용 다스리기Feature engineering · Overfitting · L2/L1 · Optimization

재료를 늘리되, 외우지 않도록 벌점을 겁니다. 입체 지형으로 극소·극대·안장점을 봅니다.

①무엇이 문제인가

직선 경계 모델도 x₁², x₁x₂ 같은 새 특성을 넣어 주면 곡선 경계를 그릴 수 있습니다(커널 SVC가 차원을 올려 곡선 경계를 만드는 것과 같은 생각). 그런데 특성을 너무 많이 만들면 학습 데이터를 외워 버립니다.

0.830차수 1 (특성 2개): 시험 정확도, 경계가 너무 단순
0.910차수 3 (특성 9개): 시험 정확도
1.000 / 0.860차수 10 (특성 65개): 학습 / 시험. 외워 버림
0.940 / 0.930차수 10 + L1 규제 (C = 10): 학습 / 시험, 65개 중 55개 계수가 0

쉽게 말하면특성 공학은 요리 전 재료 손질과 같습니다. 통감자를 채 썰면 볶음 요리가 가능해집니다. 하지만 재료를 수십 가지로 늘리면 요리사가 기출 레시피를 외우듯 학습 데이터에만 맞춘 요리를 만듭니다. 규제는 여행 가방 무게 제한처럼 "짐이 무거울수록 추가 요금"을 매겨 꼭 필요한 짐만 챙기게 합니다. L2(릿지)는 모든 짐을 조금씩 가볍게, L1(라쏘)은 필요 없는 짐을 아예 빼게 합니다.

②원리와 수식
(x₁, x₂) → (x₁, x₂, x₁², x₁x₂, x₂², x₁³, …),  특성 수 = d+2C2 − 1차수 d까지의 다항 특성. d = 3이면 9개, d = 10이면 65개.
LL2(w) = L(w) + λ Σj wj²     LL1(w) = L(w) + λ Σj |wj|원래 손실 L에 가중치 크기만큼 벌점을 더합니다. λ가 클수록 강한 규제.
scikit-learn: C = 1 / λLogisticRegression의 C는 λ의 역수입니다. C가 작을수록 규제가 강합니다.
수식 한 조각씩 읽기
  1. λ (람다)벌점의 무게. 0이면 규제 없음, 너무 크면 가중치가 다 작아져 과소적합이 됩니다.
  2. Σ wj²L2 벌점. 큰 계수일수록 제곱으로 크게 벌받아 고르게 작아집니다. 0 근처에서는 벌점이 거의 없어 정확히 0이 되지는 않습니다.
  3. Σ |wj|L1 벌점. 계수가 작아도 같은 비율로 벌받기 때문에, 쓸모없는 계수는 정확히 0이 됩니다. 그래서 특성 선택 효과가 있습니다.
  4. 릿지 · 라쏘원래 선형 회귀에 규제를 붙인 모델의 이름입니다. 분류에서는 "L2·L1 규제 로지스틱 회귀"라고 부르는 것이 정확합니다.
③움직여 보기 · 특성을 늘리고 규제 걸기

학습 데이터 50개(큰 점)로 다항 특성 로지스틱 회귀를 이 페이지에서 직접 학습합니다. 흐린 작은 점 400개는 시험 데이터입니다. 차수를 올리며 경계가 어떻게 변하는지, 규제를 켜면 계수 막대가 어떻게 바뀌는지 보세요.

규제
바로 보기:
③움직여 보기 · 입체 지형으로 보는 극소, 극대, 안장점

학습이란 가중치 (w₁, w₂)를 바닥에 두고 그 위에 손실을 높이로 쌓은 지형에서 가장 낮은 곳을 찾는 일입니다. 규제를 켜면 지형 자체가 바뀌어 바닥이 원점 쪽으로 옮겨 갑니다. 네 가지 지형을 골라 공을 굴려 보세요. 드래그하거나 회전 슬라이더로 돌려 볼 수 있습니다.

∇f = 0  에서   D = fw₁w₁ fw₂w₂ − fw₁w₂²D > 0이고 fw₁w₁ > 0이면 극소(그릇), D > 0이고 fw₁w₁ < 0이면 극대(봉우리), D < 0이면 안장점(말안장). 기울기가 0인 곳이라고 모두 최솟값은 아닙니다.
지형
규제

색은 높이를 나타냅니다(낮을수록 보라, 높을수록 노랑). 오른쪽 등고선의 점선은 규제의 모양입니다: L2는 원, L1은 마름모. L1의 마름모는 꼭짓점이 축 위에 있어서 최솟값이 축에 걸리기 쉽고, 그때 한 계수가 정확히 0이 됩니다.

④함께 점검할 것 · 차수와 λ를 어떻게 정할까 (하이퍼파라미터 탐색)

차수 d와 규제 강도 λ(또는 C)는 데이터로 저절로 정해지지 않는 하이퍼파라미터입니다. 후보 조합을 모두 교차검증으로 비교해 가장 좋은 것을 고르는 것이 그리드 서치입니다. 1장의 교차검증이 여기서 쓰입니다. 시험 세트는 이 선택이 끝난 뒤 한 번만 봅니다.

아래 코드의 마지막 부분이 차수 4가지 × C 5가지 = 20개 조합을 5겹 교차검증으로 비교합니다.

⑤코드와 실제 결과
# 3. 특성 생성 → 과적합 → 규제(L2 릿지형, L1 라쏘형)
import warnings; warnings.filterwarnings("ignore")
import numpy as np
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split, GridSearchCV, StratifiedKFold
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.linear_model import LogisticRegression

X, y = make_moons(n_samples=200, noise=0.3, random_state=1)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.5, random_state=1, stratify=y)

def poly_logit(degree, penalty=None, C=1.0):
    clf = (LogisticRegression(C=1e6, max_iter=20000) if penalty is None else
           LogisticRegression(penalty=penalty, C=C, solver="liblinear", max_iter=20000))
    return make_pipeline(PolynomialFeatures(degree, include_bias=False), StandardScaler(), clf)

print("특성을 늘리면 (규제 없음)")
for d in [1, 3, 10]:
    m = poly_logit(d).fit(X_tr, y_tr)
    print(f"  차수 {d:>2}: 특성 {m[0].n_output_features_:>2}개  학습 {m.score(X_tr, y_tr):.3f}  테스트 {m.score(X_te, y_te):.3f}")

print("차수 10에 규제를 걸면 (C = 1/λ, 작을수록 강한 규제)")
for C in [10, 0.3]:
    for pen in ["l2", "l1"]:
        m = poly_logit(10, pen, C=C).fit(X_tr, y_tr)
        w = m[-1].coef_.ravel()
        print(f"  C={C:<4} {pen.upper()}: 학습 {m.score(X_tr, y_tr):.3f}  테스트 {m.score(X_te, y_te):.3f}  "
              f"0이 된 계수 {np.sum(np.abs(w) < 1e-8)}/{w.size}")

# 규제 강도와 차수를 교차검증으로 고르기 (하이퍼파라미터 탐색)
grid = GridSearchCV(poly_logit(3, "l2"),
                    {"polynomialfeatures__degree": [1, 3, 5, 10],
                     "logisticregression__C": [0.01, 0.1, 1, 10, 100]},
                    cv=StratifiedKFold(5, shuffle=True, random_state=0))
grid.fit(X_tr, y_tr)
print(f"최적 조합 {grid.best_params_}  교차검증 {grid.best_score_:.3f}  테스트 {grid.score(X_te, y_te):.3f}")

실행 결과 (scikit-learn 1.8, CPU)

특성을 늘리면 (규제 없음)
  차수  1: 특성  2개  학습 0.850  테스트 0.830
  차수  3: 특성  9개  학습 0.920  테스트 0.910
  차수 10: 특성 65개  학습 1.000  테스트 0.860
차수 10에 규제를 걸면 (C = 1/λ, 작을수록 강한 규제)
  C=10   L2: 학습 0.940  테스트 0.920  0이 된 계수 0/65
  C=10   L1: 학습 0.940  테스트 0.930  0이 된 계수 55/65
  C=0.3  L2: 학습 0.880  테스트 0.850  0이 된 계수 0/65
  C=0.3  L1: 학습 0.850  테스트 0.800  0이 된 계수 62/65
최적 조합 {'logisticregression__C': 10, 'polynomialfeatures__degree': 10}  교차검증 0.920  테스트 0.920
🐟물고기로 보면 · 꼬리 길이 비율: 특성 하나가 모델 교체보다 강하다

농어와 로치는 몸길이와 높이만으로는 어떤 모델도 0.8을 넘기 어렵습니다(규칙 기반부터 딥러닝까지 여덟 모델의 시험 정확도 0.26~0.79). 모델 대신 특성을 바꿔 봅니다. "꼬리 비율 = (length3 − length2) ÷ length2", 즉 꼬리지느러미가 몸길이에 비해 얼마나 긴가입니다.

체형 비율과 꼬리 비율농어는 꼬리 비율이 0.08 이하, 로치는 0.09 이상으로 거의 겹치지 않는다꼬리 비율 ≈ 0.0870.200.250.300.350.000.050.100.15체형 비율 = 높이 ÷ 몸길이 →↑ 꼬리 비율 = (length3 − length2) ÷ length2농어로치 (진한 점 = 원본)특성 조합별 로지스틱 회귀 정확도 (물고기 단위 5겹)특성 조합별 로지스틱 회귀 정확도 (물고기 단위 5겹)길이·높이 (2개)0.772+ 체형 비율 (높이 ÷ 길이)0.835+ 꼬리 비율1.000원래 특성 6개0.9176개 + 비율 3개 (9개)0.901기준선 0.7450.700.770.850.931.00

읽는 법. 농어의 꼬리 비율은 0.026~0.081, 로치는 0.093~0.155로 겹치지 않습니다. 원본 158마리만 봐도 같습니다. 그래서 특성 하나를 더한 로지스틱 회귀가 1.000에 이릅니다. 반면 원래 특성 6개를 그대로 다 넣으면 0.917이고, 비율 3개를 더 넣은 9개는 0.901로 오히려 조금 낮아졌습니다. 많이 넣는 것보다 맞는 것을 넣는 것이 중요합니다.

규제와의 관계. 9개 특성에 L1 규제(C = 10)를 걸면 교차검증 1.000이 나오는데, 계수를 보면 length2에 −73, length3에 +73이 붙어 있습니다. 모델이 "length3 − length2", 즉 꼬리 길이를 스스로 만들어 쓴 것입니다. 기본 L2 규제(C = 1)는 이런 큰 계수를 벌점으로 막아 0.901에 머뭅니다. 규제가 과적합을 막아 주지만, 쓸모 있는 신호까지 눌러 버릴 수 있다는 뜻입니다. 사람이 꼬리 비율을 직접 만들어 주면 큰 계수 없이도 같은 결과를 얻어, 모델이 더 단순하고 해석하기 쉬워집니다.

코드 보기 (물고기 데이터, scikit-learn)
import pandas as pd, numpy as np, warnings; warnings.filterwarnings("ignore")
# 데이터 불러오기와 정리: 무게 0인 행과 완전히 같은 행을 뺀다
df = pd.read_csv("fish_market_x10.csv")
df = df[df["weight_g"] > 0].drop_duplicates().reset_index(drop=True)
FEATS = ["weight_g", "length1_cm", "length2_cm", "length3_cm", "height_cm", "width_cm"]
X, y, groups = df[FEATS], df["species"], df["seed_id"]   # seed_id: 같은 물고기에서 만든 복제본 묶음

# 3장 · 물고기 데이터로 보는 특성 공학과 L1 규제: 농어 vs 로치
from sklearn.model_selection import StratifiedGroupKFold, cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

d = df[df["species"].isin(["Perch", "Roach"])].copy()
# 새 특성 만들기 (특성 공학): 크기와 상관없는 "체형" 비율
d["height_ratio"] = d["height_cm"] / d["length2_cm"]           # 높이 ÷ 길이: 통통한 정도
d["width_ratio"] = d["width_cm"] / d["length2_cm"]             # 두께 ÷ 길이
d["plumpness"] = np.cbrt(d["weight_g"]) / d["length2_cm"]      # 무게의 세제곱근 ÷ 길이: 살집
d["tail_ratio"] = (d["length3_cm"] - d["length2_cm"]) / d["length2_cm"]   # 꼬리 길이 ÷ 몸길이
yy, gg = d["species"], d["seed_id"]
cv = StratifiedGroupKFold(5, shuffle=True, random_state=0)
logit = lambda **kw: make_pipeline(StandardScaler(), LogisticRegression(max_iter=5000, **kw))

sets = {"길이·높이 (2개)": ["length2_cm", "height_cm"],
        "길이·높이 + 체형 비율": ["length2_cm", "height_cm", "height_ratio"],
        "길이·높이 + 꼬리 비율": ["length2_cm", "height_cm", "tail_ratio"],
        "원래 특성 6개": FEATS,
        "6개 + 비율 3개 (9개)": FEATS + ["height_ratio", "width_ratio", "plumpness"]}
print(f"기준선 (모두 농어라고 답하기): {(yy == 'Perch').mean():.3f}")
for name, cols in sets.items():
    s = cross_val_score(logit(), d[cols], yy, cv=cv, groups=gg)
    print(f"{name:<22} {s.mean():.3f} ± {s.std():.3f}")

cols = sets["6개 + 비율 3개 (9개)"]
print("\nL1 규제 강도에 따른 계수 (C가 작을수록 강한 규제)")
for C in [10, 1, 0.1]:
    m = logit(penalty="l1", C=C, solver="liblinear").fit(d[cols], yy)
    w = m[-1].coef_.ravel()
    kept = [f"{c}:{v:+.2f}" for c, v in zip(cols, w) if abs(v) > 1e-6]
    acc = cross_val_score(logit(penalty="l1", C=C, solver="liblinear"), d[cols], yy, cv=cv, groups=gg).mean()
    print(f"C={C:<4} 남은 특성 {len(kept)}/9  교차검증 {acc:.3f}  {', '.join(kept)}")

실행 결과 (scikit-learn 1.8, CPU)

기준선 (모두 농어라고 답하기): 0.745
길이·높이 (2개)             0.772 ± 0.033
길이·높이 + 체형 비율          0.835 ± 0.014
길이·높이 + 꼬리 비율          1.000 ± 0.000
원래 특성 6개               0.917 ± 0.017
6개 + 비율 3개 (9개)        0.901 ± 0.029

L1 규제 강도에 따른 계수 (C가 작을수록 강한 규제)
C=10   남은 특성 6/9  교차검증 1.000  weight_g:-1.52, length2_cm:-73.16, length3_cm:+73.15, width_cm:-1.76, height_ratio:+1.20, plumpness:-0.19
C=1    남은 특성 6/9  교차검증 0.997  weight_g:-2.04, length2_cm:-32.21, length3_cm:+33.38, width_cm:-1.51, height_ratio:+1.07, plumpness:-0.05
C=0.1  남은 특성 3/9  교차검증 0.877  weight_g:-1.49, height_ratio:+1.76, width_ratio:-0.52
✓확인 문제

Q1. 학습 정확도 1.00, 시험 정확도 0.86인 모델에 먼저 해 볼 조치는?

Q2. L1 규제가 L2와 달리 "특성 선택" 효과를 내는 이유는?

4

여러 알고리즘 혼합하기Voting · Bagging · Random forest · Boosting

여러 모델의 판단을 묶어 흔들림과 치우침을 줄입니다.

①무엇이 문제인가

모델 하나는 오차가 한쪽으로 치우치거나(편향, 식 w·x + b의 편향 항 b와는 다른 뜻), 데이터가 조금만 바뀌어도 판단이 크게 흔들립니다(분산). 여러 모델의 판단을 묶으면 이 약점을 줄일 수 있습니다.

0.842 ± 0.035깊이 제한 없는 의사결정나무 1그루 (5겹 교차검증)
0.878 ± 0.014랜덤 포레스트 100그루: 점수는 오르고 흔들림은 줄어듦
0.887 ± 0.011KNN 단독: 이 데이터에서는 앙상블과 비슷. 앙상블이 늘 이기는 것은 아님

쉽게 말하면보팅은 전공이 다른 전문가들의 투표, 배깅은 같은 과목을 서로 다른 모의고사 세트로 공부한 학생들의 다수결, 랜덤 포레스트는 그 학생들에게 과목도 일부씩만 무작위로 공부하게 해 서로 덜 닮게 만든 것, 부스팅은 앞사람이 틀린 문제를 다음 사람이 집중해서 푸는 오답노트 릴레이입니다.

②세 갈래로 정리하기
앙상블의 세 갈래앙상블은 보팅, 배깅, 부스팅으로 나뉘고, 랜덤 포레스트는 배깅에, AdaBoost와 그래디언트 부스팅은 부스팅에 속한다앙상블보팅서로 다른 모델 · 동시에배깅같은 모델 · 다른 표본 · 동시에부스팅약한 모델 · 차례로 보완랜덤 포레스트AdaBoost그래디언트 부스팅 (XGBoost 등)주로 줄이는 것: 개별 실수분산 ↓편향 ↓
소프트 보팅: p̂(y | x) = (1/M) Σm pm(y | x)M개 모델의 확률을 평균합니다. 하드 보팅은 예측 클래스의 다수결입니다.
배깅: f̂(x) = (1/M) Σm fm(x),   fm은 부트스트랩 표본 m으로 학습같은 모델을 다른 표본으로 여러 번 학습해 평균하면, 표본에 따라 흔들리는 부분(분산)이 상쇄됩니다.
부스팅: FM(x) = Σm αm hm(x),   αm = ½ ln((1 − errm) / errm)AdaBoost. 오차율 errm이 낮은 모델일수록 큰 발언권 αm을 얻고, 틀린 점의 가중치는 eαm배로 커져 다음 모델이 집중합니다.
③움직여 보기 · 보팅

성격이 다른 세 모델(직선 경계, 거리 기반, 계단 경계)을 같은 데이터로 학습해 투표시킵니다. 네 번째 그림이 합친 결과입니다. 시험 정확도는 이 페이지에서 계산한 값입니다. 보팅은 가장 약한 모델보다 낫지만, 이 예시처럼 가장 좋은 단독 모델을 늘 넘지는 않습니다. 약한 모델이 섞이면 평균이 끌려 내려가기 때문입니다.

투표 방식
③움직여 보기 · 배깅, 랜덤 포레스트, 부스팅

나무를 1개에서 60개까지 늘려 봅니다(학습 200개, 시험 500개, 이 페이지에서 직접 학습). 부스팅에서는 점의 크기가 "다음 나무가 신경 쓸 가중치"입니다. 앞 나무가 틀린 점이 점점 커지는 것을 보세요.

방법

④언제 무엇을 쓸까
방법잘 맞는 상황주의할 점
보팅성격이 다른 괜찮은 모델이 이미 몇 개 있을 때비슷한 모델끼리 묶으면 효과가 작습니다. 소프트 보팅은 각 모델의 확률이 믿을 만해야 합니다.
배깅 · 랜덤 포레스트깊은 나무처럼 흔들림(분산)이 큰 모델, 표 형태 데이터의 첫 강력한 기준선원래 편향이 큰 단순 모델(예: 로지스틱 회귀)은 배깅해도 크게 좋아지지 않습니다.
부스팅표 형태 데이터에서 최고 성능을 노릴 때 (그래디언트 부스팅, XGBoost, LightGBM)차례로 학습해 느리고, 단계 수·학습률을 잘못 잡으면 과적합됩니다. 조기 종료를 함께 씁니다.
⑤코드와 실제 결과
# 4. 여러 알고리즘 혼합: 보팅, 배깅, 랜덤 포레스트, 부스팅
import warnings; warnings.filterwarnings("ignore")
from sklearn.datasets import make_moons
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import (VotingClassifier, BaggingClassifier,
                              RandomForestClassifier, GradientBoostingClassifier)

X, y = make_moons(n_samples=600, noise=0.35, random_state=2)
cv = StratifiedKFold(5, shuffle=True, random_state=0)

logit = make_pipeline(StandardScaler(), LogisticRegression())
knn   = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=15))
tree  = DecisionTreeClassifier(random_state=0)                 # 깊이 제한 없음: 과적합하기 쉬움

models = {
    "로지스틱 회귀 (단독)":     logit,
    "KNN (단독)":             knn,
    "의사결정나무 (단독)":       tree,
    "보팅 (위 세 모델, soft)":   VotingClassifier([("lr", logit), ("knn", knn),
                               ("tree", DecisionTreeClassifier(max_depth=5, random_state=0))], voting="soft"),
    "배깅 (나무 100그루)":       BaggingClassifier(DecisionTreeClassifier(), n_estimators=100, random_state=0),
    "랜덤 포레스트 (100그루)":    RandomForestClassifier(n_estimators=100, random_state=0),
    "그래디언트 부스팅 (100단계)": GradientBoostingClassifier(n_estimators=100, max_depth=2, random_state=0),
}
for name, m in models.items():
    s = cross_val_score(m, X, y, cv=cv)
    print(f"{name:<22} {s.mean():.3f} ± {s.std():.3f}")

실행 결과 (scikit-learn 1.8, CPU)

로지스틱 회귀 (단독)           0.832 ± 0.036
KNN (단독)               0.887 ± 0.011
의사결정나무 (단독)            0.842 ± 0.035
보팅 (위 세 모델, soft)      0.883 ± 0.014
배깅 (나무 100그루)          0.870 ± 0.020
랜덤 포레스트 (100그루)        0.878 ± 0.014
그래디언트 부스팅 (100단계)      0.878 ± 0.020

읽는 법. 가장 흔들리는 의사결정나무 1그루(0.842 ± 0.035)가 배깅·랜덤 포레스트·부스팅으로 0.870~0.878로 오르고 표준편차도 줄었습니다. 반면 이 데이터에서는 KNN 단독(0.887)이 앙상블과 비슷합니다. 앙상블은 약하거나 불안정한 모델을 보강하는 도구이지, 언제나 이기는 방법은 아닙니다.

🐟물고기로 보면 · 7종 전체 분류에서 앙상블은 얼마나 도움이 될까

이번에는 7종 전체를 원래 특성 6개로 가릅니다(물고기 단위 5겹 교차검증).

7종 분류 정확도와 표준편차7종 분류 정확도와 표준편차의사결정나무 (단독)0.872KNN (단독)0.900로지스틱 회귀 (단독)0.899보팅 (세 모델, soft)0.899배깅 (나무 200그루)0.899랜덤 포레스트 (200그루)0.909그래디언트 부스팅0.903로지스틱 + 비율 특성 2개0.980랜덤 포레스트 + 비율 특성 2개0.9840.800.850.900.951.00

읽는 법. 나무 한 그루(0.872)는 배깅·랜덤 포레스트·부스팅으로 0.899~0.909까지 오릅니다. 앙상블이 불안정한 나무를 보강한 것입니다. 하지만 KNN과 로지스틱 회귀 단독(약 0.900)과의 차이는 가로선(표준편차) 안에 들어가, 이 데이터에서는 "확실히 더 좋다"고 말하기 어렵습니다. 남은 오류의 대부분은 서로 비슷하게 생긴 종(농어·로치·흰송어) 사이에서 나오므로, 3장의 체형 비율과 꼬리 비율을 더해 봤습니다. 특성 8개에서 로지스틱 회귀 단독이 0.980, 랜덤 포레스트가 0.984입니다. 모델을 묶어서 얻은 +0.01~0.04보다, 맞는 특성 두 개를 더해서 얻은 +0.08이 훨씬 큽니다.

코드 보기 (물고기 데이터, scikit-learn)
import pandas as pd, numpy as np, warnings; warnings.filterwarnings("ignore")
# 데이터 불러오기와 정리: 무게 0인 행과 완전히 같은 행을 뺀다
df = pd.read_csv("fish_market_x10.csv")
df = df[df["weight_g"] > 0].drop_duplicates().reset_index(drop=True)
FEATS = ["weight_g", "length1_cm", "length2_cm", "length3_cm", "height_cm", "width_cm"]
X, y, groups = df[FEATS], df["species"], df["seed_id"]   # seed_id: 같은 물고기에서 만든 복제본 묶음

# 4장 · 물고기 7종 분류에서 여러 알고리즘 혼합 (물고기 단위 5겹 교차검증)
from sklearn.model_selection import StratifiedGroupKFold, cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import VotingClassifier, BaggingClassifier, RandomForestClassifier, GradientBoostingClassifier

cv = StratifiedGroupKFold(5, shuffle=True, random_state=0)
logit = make_pipeline(StandardScaler(), LogisticRegression(max_iter=5000))
knn = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=5))
models = {
    "의사결정나무 (단독)": DecisionTreeClassifier(random_state=0),
    "KNN (단독)": knn,
    "로지스틱 회귀 (단독)": logit,
    "보팅 (위 세 모델, soft)": VotingClassifier([("tree", DecisionTreeClassifier(max_depth=5, random_state=0)), ("knn", knn), ("lr", logit)], voting="soft"),
    "배깅 (나무 200그루)": BaggingClassifier(DecisionTreeClassifier(), n_estimators=200, random_state=0),
    "랜덤 포레스트 (200그루)": RandomForestClassifier(n_estimators=200, random_state=0),
    "그래디언트 부스팅": GradientBoostingClassifier(random_state=0),
}
for name, m in models.items():
    s = cross_val_score(m, X, y, cv=cv, groups=groups)
    print(f"{name:<18} {s.mean():.3f} ± {s.std():.3f}")

# 모델을 묶는 대신 특성을 더하면? (3장의 체형 비율과 꼬리 비율)
d = df.copy()
d["height_ratio"] = d["height_cm"] / d["length2_cm"]
d["tail_ratio"] = (d["length3_cm"] - d["length2_cm"]) / d["length2_cm"]
cols = FEATS + ["height_ratio", "tail_ratio"]
print("\n특성 8개 (원래 6개 + 비율 2개)")
for name in ["로지스틱 회귀 (단독)", "랜덤 포레스트 (200그루)"]:
    s = cross_val_score(models[name], d[cols], d["species"], cv=cv, groups=d["seed_id"])
    print(f"{name:<18} {s.mean():.3f} ± {s.std():.3f}")

실행 결과 (scikit-learn 1.8, CPU)

의사결정나무 (단독)        0.872 ± 0.017
KNN (단독)           0.900 ± 0.033
로지스틱 회귀 (단독)       0.899 ± 0.036
보팅 (위 세 모델, soft)  0.899 ± 0.031
배깅 (나무 200그루)      0.899 ± 0.026
랜덤 포레스트 (200그루)    0.909 ± 0.034
그래디언트 부스팅          0.903 ± 0.026

특성 8개 (원래 6개 + 비율 2개)
로지스틱 회귀 (단독)       0.980 ± 0.014
랜덤 포레스트 (200그루)    0.984 ± 0.014
✓확인 문제

Q1. 랜덤 포레스트와 가장 가까운 관계는?

Q2. 부스팅이 다음 모델을 학습할 때 하는 일은?

5

한 장으로 정리Checklist

순서대로 점검하면 대부분의 개선 기회를 놓치지 않습니다.

★순서대로 점검하기
  1. 기준선을 잰다 · 다수 클래스 답하기, 기본 로지스틱 회귀의 층화 교차검증 점수를 적어 둡니다.
  2. 평가부터 바로잡는다 · stratify, 층화 K겹 교차검증, 전처리는 Pipeline 안에, 불균형이면 재현율·F1도 봅니다.
  3. 학습 과정을 확인한다 · 손실 곡선이 매끄럽게 줄어드는지 보고, 학습률·배치·조기 종료를 조정합니다.
  4. 특성을 만들고 규제한다 · 학습·시험 점수 차이를 보며 특성을 늘리고, λ(또는 C)는 그리드 서치로 고릅니다.
  5. 그래도 부족하면 묶는다 · 랜덤 포레스트나 그래디언트 부스팅, 또는 성격이 다른 모델의 보팅을 시도합니다.
  6. 시험 세트는 맨 마지막에 한 번 · 모든 선택이 끝난 뒤 한 번만 보고 그 점수를 보고합니다.
★핵심 세 줄
1. 점수를 믿을 수 없으면 어떤 개선도 착시입니다. 층화 교차검증과 누수 점검이 첫 단계입니다.
2. 특성을 늘리면 경계가 유연해지지만 과적합이 따라옵니다. 규제(L2·L1)와 교차검증으로 그 사이의 균형점을 찾습니다.
3. 학습은 손실 지형에서 가장 낮은 곳을 찾는 일이고, 앙상블은 여러 모델의 판단을 묶어 편향과 분산을 줄이는 일입니다.

읽을 때 주의할 점. 움직이는 그림의 데이터와 점수는 원리를 보여 주기 위한 작은 예시이며, 코드 결과표는 scikit-learn 1.8, CPU에서 실행한 값입니다. 실제 데이터에서는 크기와 모양에 따라 효과의 크기가 달라집니다. 3장 입체 지형의 곡면은 설명용 함수로 그렸습니다.

AI

AI와 함께 바꿔 보기 — 풀버전 프롬프트Prompt

내려받은 코드를 내 데이터로 바꾸거나 다른 모델로 바꾸고 싶을 때, 아래 프롬프트를 복사해 AI 도우미에게 붙여 넣으세요. [ ] 칸을 채우고, 해당하지 않는 항목은 지우면 됩니다.

# 역할
너는 파이썬(pandas, scikit-learn)으로 분류 모델을 가르치는 친절한 데이터 과학 튜터이자 코드 리뷰어다.

# 배경
나는 "물고기 분류" 해설 페이지(또는 실험실)에서 코드를 내려받았다. 이 코드를 [데이터 변환 / 모델 변환 / 둘 다] 하고 싶다.
- 지금 데이터: [파일 이름], 행 수 [ ]
  - 기본 열: species(정답), weight_g, length1_cm, length2_cm, length3_cm, height_cm, width_cm, source, seed_id
  - 내 데이터라면 열 이름과 뜻을 여기에 적는다: [ ]
- 실행 환경: [코랩 / 주피터 / 로컬 파이썬 3.x], scikit-learn 버전 [ ](모르면 "모름")
- 내 수준: [입문 / 중급 / 실무]

# 하고 싶은 일 (해당하는 번호만 남기고 [ ]를 채운다)
## A. 데이터 변환
A1. 다른 데이터로 바꾸기: 파일 [경로], 정답 열 [ ], 특성 열 [ ]
A2. 정리: 결측치 [삭제 / 평균 대체 / 중앙값 대체], 이상치 [예: 무게 0인 행 삭제], 완전히 같은 행 [삭제 / 유지]
A3. 새 특성 만들기: [예: 체형 비율 = height_cm / length2_cm, 꼬리 비율 = (length3_cm - length2_cm) / length2_cm]
A4. 변환: 범주형 [원-핫 / 순서형], 스케일링 [표준화 / 최소-최대 / 없음], 로그 변환할 열 [ ]
A5. 데이터 증강: 원본마다 복제본 [n]개, 크기 배율 ~ 정규분포(평균 1, 표준편차 [0.12]), 무게는 배율의 세제곱에 비례, 모양 비율에는 표준편차 [3%]의 잡음. 원본 ID 열(seed_id)을 반드시 만든다.
A6. 클래스 고르기·합치기: [예: 농어와 로치 두 종만 / 흰송어와 로치를 한 클래스로]
## B. 모델 변환
B1. 모델 바꾸기: 지금 [ ] → [KNN / 의사결정나무 / 로지스틱 회귀 / 선형 SVM / 커널 SVC / MLP / 랜덤 포레스트 / 그래디언트 부스팅]
B2. 하이퍼파라미터: 직접 지정 [ ] 또는 탐색 [GridSearchCV / RandomizedSearchCV], 탐색 범위 [ ]
B3. 학습 과정: 학습률 [ ], 에포크 [ ], 배치 크기 [ ], 조기 종료 [사용 / 안 함]
B4. 규제: [L1 / L2], 강도 [C 또는 alpha 값]
B5. 앙상블: [보팅(모델 목록) / 배깅(기본 모델, 개수) / 부스팅(종류, 단계 수)]
B6. 여러 모델 비교표: [비교할 모델 목록]

# 반드시 지킬 것
1. 분할: 층화(stratify)로 나눈다. 증강·복제 데이터라면 원본 ID(seed_id) 단위로 나눈다(StratifiedGroupKFold 또는 GroupShuffleSplit). 이유를 주석 한 줄로 적는다.
2. 누수 방지: 스케일링, 특성 선택, 결측치 처리는 Pipeline 안에서 학습 데이터로만 fit한다. 시험 세트는 모든 선택이 끝난 뒤 한 번만 쓴다.
3. 재현성: random_state=0처럼 난수를 고정한다.
4. 기준선: 가장 많은 클래스만 답할 때의 정확도를 함께 출력한다.
5. 출력: 교차검증 정확도(평균 ± 표준편차), 시험 정확도, 혼동행렬, classification_report(클래스별 정밀도·재현율·F1).
6. 원래 코드에서 바뀐 줄에는 "# [변경]" 주석을 단다.
7. 주석과 설명은 한국어로, 입문자도 따라올 수 있게 쓴다.
8. 코랩에서 바로 실행되도록 import를 모두 넣고, 필요하면 파일 올리기 칸(google.colab.files.upload)을 포함한다.

# 답변 형식
1. 무엇을 왜 바꿨는지 3~5줄 요약
2. 그대로 실행되는 전체 코드 한 덩어리 (주피터 칸으로 나눌 곳에 "# %%" 표시)
3. 결과를 읽는 법과 주의할 점 (과적합·누수·클래스 불균형 신호)
4. 다음에 해 볼 만한 실험 2~3가지

# 코드
[여기에 내려받은 .py 코드나 노트북 코드를 붙여 넣는다]

프롬프트의 "반드시 지킬 것"은 이 자료에서 다룬 원칙(층화 분할, 원본 단위 분할, 누수 방지, 기준선 비교)을 그대로 옮긴 것입니다. AI가 만든 코드도 이 원칙을 지켰는지 확인하세요.

Aa

용어 사전 — 비유로 보기Glossary

본문에서 점선 밑줄이 있는 단어를 누르면 같은 비유가 뜹니다. 여기서는 한꺼번에 훑어볼 수 있습니다.

본문에서 점선 밑줄이 있는 단어는 처음 나올 때 눌러서(또는 마우스를 올려서) 같은 비유를 볼 수 있습니다.

평가 설계

기준선

비유달리기 기록을 재기 전에 재 두는 "지금 내 기록". 이것이 있어야 좋아졌는지 알 수 있습니다.

뜻가장 단순한 방법(예: 다수 클래스만 답하기, 기본 로지스틱 회귀)으로 먼저 잰 점수.

학습·검증·시험 세트

비유교과서(학습), 모의고사(검증), 수능(시험). 수능 문제는 공부하는 동안 절대 미리 보면 안 됩니다.

뜻가중치를 배우는 데이터, 설정을 고르는 데이터, 마지막에 한 번만 성능을 재는 데이터.

층화 추출

비유여론조사에서 연령대 비율을 실제 인구 비율과 똑같이 맞춰 표본을 뽑는 것.

뜻나눈 뒤에도 각 조각의 클래스 비율이 전체와 같도록 나누는 방법.

교차검증

비유문제지를 바꿔 가며 모의고사를 여러 번 보고 평균 내기. 한 번 운 좋게 잘 본 점수에 속지 않습니다.

뜻데이터를 K조각으로 나눠 돌아가며 한 조각을 시험용으로 쓰고 점수를 평균 내는 방법.

데이터 누수

비유시험 전에 답안지를 슬쩍 본 것. 점수는 높지만 실력이 아닙니다.

뜻시험용 데이터의 정보가 학습 과정에 섞여 들어가 점수가 부풀려지는 실수.

클래스 불균형

비유100명 중 1명만 걸리는 병의 검사. "모두 음성"이라고만 답해도 99점이 나옵니다.

뜻클래스별 데이터 수가 크게 차이 나는 상태. 정확도가 실력을 가립니다.

혼동행렬

비유성적표의 "맞힌 문제/틀린 문제"를 유형별로 나눈 오답 분석표.

뜻실제 클래스와 예측 클래스를 가로세로로 놓고 개수를 센 표.

정밀도

비유화재경보가 울렸을 때 진짜 불이 났을 확률. 헛경보가 많으면 낮아집니다.

뜻양성이라고 예측한 것 중 실제 양성의 비율. TP / (TP + FP).

재현율

비유실제로 난 불 중에서 경보가 울린 비율. 놓친 불이 많으면 낮아집니다.

뜻실제 양성 중 모델이 찾아낸 비율. TP / (TP + FN).

F1 점수

비유정밀도와 재현율 두 과목의 균형 점수. 한 과목만 잘하면 낮게 나옵니다.

뜻정밀도와 재현율의 조화 평균. 2PR / (P + R).

학습 과정

손실

비유다트가 과녁 중심에서 빗나간 거리. 멀수록 나쁩니다.

뜻예측이 정답과 얼마나 다른지를 숫자 하나로 잰 것. 학습은 이 값을 줄이는 과정입니다.

경사하강법

비유짙은 안개 속 산에서 발밑의 기울어짐만 느끼며 한 걸음씩 아래로 내려가 골짜기를 찾는 것.

뜻손실이 줄어드는 방향(기울기의 반대)으로 가중치를 조금씩 옮기는 방법.

학습률

비유산을 내려갈 때의 보폭. 너무 크면 골짜기를 건너뛰고, 너무 작으면 해가 질 때까지 못 내려갑니다.

뜻한 걸음에 가중치를 얼마나 크게 옮길지 정하는 값. 기호는 η(에타).

에포크

비유문제집 한 권을 처음부터 끝까지 한 번 푸는 것.

뜻학습 데이터 전체를 한 바퀴 다 보는 단위.

배치

비유길을 물을 때 몇 사람에게 물어볼지. 40명 모두에게 물으면 정확하지만 느리고, 한 명에게 물으면 빠르지만 틀릴 수 있습니다.

뜻가중치를 한 번 고칠 때 쓰는 데이터 묶음. 크기를 B로 씁니다.

확률적 경사하강법

비유지나가는 사람 한 명에게만 길을 묻고 바로 한 걸음 옮기기. 자주 움직이지만 갈지자로 걷습니다.

뜻배치 크기 1(넓게는 작은 배치)로 매번 가중치를 고치는 경사하강법.

모멘텀

비유내리막을 굴러가던 공의 관성. 한 번 붙은 속도가 작은 요철에 쉽게 멈추지 않습니다.

뜻이전 걸음의 방향을 일부 이어 가는 경사하강법의 보조 장치. 흔들림을 줄이고 수렴을 빠르게 합니다.

Adam

비유길마다 보폭을 스스로 조절하는 등산객. 험한 방향은 조심조심, 평탄한 방향은 성큼성큼.

뜻가중치마다 학습률을 자동으로 조절하고 모멘텀도 쓰는 경사하강법의 변형. 신경망의 기본 최적화 방법입니다.

조기 종료

비유모의고사 점수가 더 오르지 않으면 그만 외우고 시험장에 가는 것.

뜻검증 점수가 몇 에포크 동안 나아지지 않으면 학습을 멈추는 방법.

극소점·극대점

비유주변에서 가장 낮은 골짜기 바닥, 가장 높은 봉우리 꼭대기.

뜻주변보다 값이 가장 작은(큰) 점. 그 자리에서는 기울기가 0입니다.

지역 최솟값

비유산 중턱의 작은 웅덩이. 내려오다 갇히면 진짜 가장 낮은 계곡을 모릅니다.

뜻주변에서는 가장 낮지만 전체에서 가장 낮지는 않은 점.

안장점

비유말안장의 가운데. 앞뒤로는 꼭대기, 좌우로는 바닥입니다.

뜻기울기가 0이지만 극소도 극대도 아닌 점.

특성과 규제

특성 공학

비유요리 전 재료 손질. 통감자를 채 썰면 볶음 요리가 가능해지듯, 데이터를 모델이 쓰기 좋게 바꿉니다.

뜻기존 변수로 새 변수(제곱, 곱, 비율 등)를 만들어 모델에 넣는 작업.

과적합

비유기출문제 답을 통째로 외운 학생. 기출은 만점, 처음 보는 문제는 못 풉니다.

뜻학습 데이터에는 잘 맞지만 새 데이터에서 성능이 떨어지는 상태.

과소적합

비유교과서를 대충 훑어본 학생. 기출도 새 문제도 못 풉니다.

뜻모델이 너무 단순해 학습 데이터조차 잘 맞히지 못하는 상태.

규제

비유여행 가방 무게 제한. 무거울수록 추가 요금(벌점)을 물리면 꼭 필요한 짐만 챙기게 됩니다.

뜻가중치가 커질수록 손실에 벌점을 더해 모델을 단순하게 만드는 방법.

L2 규제 (릿지)

비유모든 짐의 무게를 조금씩 줄이기. 버리는 짐은 없습니다.

뜻λ Σ w² 벌점. 계수를 고르게 작게 만들지만 0으로 만들지는 않습니다.

L1 규제 (라쏘)

비유필요 없는 짐은 아예 빼기. 가방에 남는 물건 수가 줄어듭니다.

뜻λ Σ |w| 벌점. 일부 계수를 정확히 0으로 만들어 특성을 고르는 효과가 있습니다.

하이퍼파라미터

비유요리사가 미리 정하는 불 세기와 조리 시간.

뜻학습 전에 사람이 정하는 설정값. 차수, λ(또는 C), 학습률, 나무 깊이 등.

그리드 서치

비유불 세기 3단계 × 조리 시간 4가지 = 12가지를 모두 요리해 보고 맛 평가(교차검증)가 가장 좋은 조합 고르기.

뜻하이퍼파라미터 후보의 모든 조합을 교차검증으로 비교해 가장 좋은 것을 고르는 방법.

여러 모델 혼합

앙상블

비유오케스트라. 악기 하나보다 여럿이 함께 낼 때 소리가 풍성하고 실수가 묻힙니다.

뜻여러 모델의 예측을 묶어 하나의 예측을 만드는 방법.

편향과 분산

비유과녁 맞히기. 편향은 화살이 한쪽으로 몰리는 것, 분산은 화살이 사방으로 흩어지는 것.

뜻모델 오차의 두 원인. 단순한 모델은 편향이, 복잡한 모델은 분산이 커지기 쉽습니다. ※ 식 w·x + b의 b를 부르는 "편향(편향 항)"과는 다른 뜻입니다. 여기서는 "예측이 한쪽으로 치우친 정도"를 말합니다.

보팅

비유전문가 패널의 투표. 서로 다른 전공의 의견을 모읍니다.

뜻서로 다른 모델들의 예측을 다수결(하드) 또는 확률 평균(소프트)으로 합치는 방법.

부트스트랩

비유주머니에서 공을 꺼내 기록하고 다시 넣는 뽑기를 n번. 같은 공이 두 번 나올 수도 있습니다.

뜻원래 데이터에서 중복을 허용해 같은 크기의 표본을 다시 뽑는 방법.

배깅

비유같은 과목을 서로 다른 모의고사 세트로 공부한 학생들의 다수결.

뜻부트스트랩 표본마다 같은 종류의 모델을 학습해 평균(다수결)하는 방법. 분산을 줄입니다.

랜덤 포레스트

비유그 학생들에게 과목도 일부씩만 무작위로 공부하게 해서 서로 덜 닮게 만들기.

뜻배깅에 "갈림길마다 특성을 무작위로 일부만 보기"를 더한 의사결정나무 앙상블.

부스팅

비유오답노트 릴레이. 앞사람이 틀린 문제를 다음 사람이 집중해서 공부합니다.

뜻앞 모델이 틀린 데이터에 가중치를 더 주며 모델을 차례로 더해 가는 방법. 편향을 줄입니다.

E-1 저자 소개

안상선 ((주)M-Robo 대표)

한국전기안전공사, 매일경제신문을 거쳐 현재 매일경제 사외벤처 (주)M-Robo를 창업해 대표로 재직하고 있다. (주)M-Robo는 언론사 사내 벤처로 시작한 미디어 벤처로, 국민일보에 로봇기자(스톡봇)을 서비스 했으며, 한국인터넷진흥원, 한국언론진흥재단, 한국방송통신전파진흥원 등에서 다수의 프로젝트를 진행했다. 이후 자체 A.I 뉴스 신문인 Money-Robo를 서비스 중이다. 대표 재직 중 아시아오픈 데이터 해커톤 우승, 데이터창업 분야 공로로 행정안전부 장관상을 2회 수상했으며, 인공지능 및 블록체인 분야에 대한 5개 특허 발명 및 등록자이다. 저서로는 “AI범죄와 자기방어”가 있으며, 핀테크분야, 통계학, 인공지능 분야에 대해서 한국기술교육대학교 온라인평생교육원 이러닝 콘텐츠 개발을 진행했다. 최종학위는 경제학 박사(동국대, 미시수리경제학)이다. 국민대학교, 호서대학교 등 여러 대학교에서 데이터 및 인공지능과 관련한 과목을 강의하고 있다. 자세한 프로필

강의

  • 안상선 (2025). 「비즈니스를 위한 데이터 과학」 강의안. 한국열린사이버대학교 인공지능융합학과.
  • 안상선 (2025). [K-디지털] 인공지능 자연어 처리. STEP(스마트직업훈련플랫폼), 한국기술교육대학교 온라인평생교육원.
  • 안상선 (2024). [K-디지털] 핀테크 데이터 분석 기초. STEP(스마트직업훈련플랫폼), 한국기술교육대학교 온라인평생교육원.

STEP 강의 바로가기: link.step.or.kr/1qyVoJ · link.step.or.kr/M9na9N · link.step.or.kr/brqEyc
사이트 개편으로 접속이 원활하지 않을 경우에는 www.step.or.kr로 접속 바랍니다.

논문과 발표

  • 안상선 (2025). 국민노후보장패널데이터를 활용한 가구지출 예측 모델 연구: 인공지능 모델 기반 접근. 「미래사회」, 16(2). 원문
  • 안상선 (2025). 국민노후보장패널데이터를 활용한 가구지출 예측 모델 연구: 인공지능 접근. 국민노후보장패널 학술대회 발표자료(2025.05.13), 국민연금공단 국민연금연구원. 발표자료
  • 안상선 (2025). SNS 텍스트 기반 마약 의심 거래 탐지 알고리즘에 대한 연구: 패턴 및 맥락 분석을 중심으로. 「미래사회」, 16(1). 원문
  • 안상선 (2024). 금융분야의 이상탐지 모델 보안을 위한 통계 기반의 적대적 데이터 대응 방안: 실증적 분석과 시뮬레이션 결과. 「미래사회」, 15(3). 원문
  • 안상선 (2024). 인공지능을 활용한 가상 캐릭터 특성 기반 SNS 포스팅 탐지 솔루션. 2024 Summer Newmedia Party 포스터 발표(2024.06.19), 서울미디어대학원대학교.
  • 안상선 (2024). 산업재해 패널 데이터를 활용한 머신러닝 모델. 제10회 산재보험패널 학술대회 포스터 발표(2024.05.17), 근로복지공단 근로복지연구원.
  • 안상선 (2024). 가상 자산 시계열 데이터의 분석과 예측: 시계열 모델 및 인공지능 모델의 비교, 개선점 제시. 「미래사회」, 15(1). 원문

수고하셨습니다!

「2 모델을 바꾸기 전에, 성능을 올리는 네 가지 방법」 편을 끝까지 읽었어요. 연구실 식구들이 한마디씩 정리해 드립니다.

박사
박사모델은 그대로 두고 점수를 올리는 네 가지 길을 걸었어요.
  • 형제 물고기를 섞어 나누면 KNN(k = 1)은 0.959, 묶어서 나누면 0.920이다
  • 표준화하면 같은 학습률(η = 0.01)에서 시험 정확도가 0.331에서 0.874로 오른다
  • 꼬리 비율 하나로 농어·로치 정확도가 0.772에서 1.000이 된다
조수
조수앙상블보다 특성 하나가 더 세네요!
초파리 연구원
초파리 연구원점수가 높으면 나누는 법부터 의심하라고.