LLM 엔지니어링2장 · 신경망과 딥러닝의 기본 원리20 / 23← 이전목차다음 →MetaContents

2장. 신경망과 딥러닝의 기본 원리

실습 6 — 기준선, 직선 모델과 견주기

신경망이 정말 쓸모 있는지 알려면 견줄 상대가 있어야 합니다. 1장처럼 기준선(늘 평균을 말하는 모델)과 직선 모델(scikit-learn 의 선형 회귀)을 같은 시험용으로 채점해, 실습 4의 신경망과 MAE, RMSE 를 견줍니다.

1단계. 실습 3과 같은 준비

Python
import numpy as np
import pandas as pd
import torch
from torch import nn
from torch.utils.data import TensorDataset, DataLoader
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_absolute_error, root_mean_squared_error

df = pd.read_csv("daily_orders.csv")
df["일차"] = range(len(df))
features = ["일차", "주말", "공휴일", "기온", "강수mm", "광고비만원", "할인행사"]
X_train, X_test, y_train, y_test = train_test_split(
    df[features], df["주문량"], test_size=0.2, random_state=42
)
scaler = StandardScaler().fit(X_train)
X_train_s, X_test_s = scaler.transform(X_train), scaler.transform(X_test)
print(X_train_s.shape, X_test_s.shape)
출력
(584, 7) (147, 7)

이 단계의 함수는 실습 3과 같습니다. 이번에는 텐서로 바꾸기 전의 NumPy 배열(X_train_s, X_test_s)로 둡니다. 직선 모델은 scikit-learn 이라 NumPy 배열을 그대로 받기 때문입니다. 함께 불러 둔 root_mean_squared_error 는 RMSE 를 재는 함수로, 4단계에서 씁니다.

2단계. 기준선과 직선 모델

Python
from sklearn.linear_model import LinearRegression

# 기준선: 학습용 주문량의 평균을 언제나 답한다
base_pred = np.full(len(y_test), y_train.mean())

# 직선 모델: 특성마다 가중치 하나씩. 은닉층 없는 신경망(뉴런 하나)과 같은 모양이다
lin = LinearRegression().fit(X_train_s, y_train)
lin_pred = lin.predict(X_test_s)
print("준비 완료")
출력
준비 완료

이 단계의 함수입니다.

코드쉬운 뜻
np.full(len(y_test), y_train.mean())시험용 건수만큼의 배열을 학습용 평균 하나로 가득 채운다. 늘 평균을 말하는 기준선의 예측이다
LinearRegression().fit(X, y)직선 모델을 만들고 학습용으로 특성마다의 가중치를 찾는다
lin.predict(X_test_s)찾은 가중치로 시험용을 예측한다

3단계. 신경망 다시 학습하기

Python
# 실습 4와 똑같은 신경망과 학습(같은 씨앗이라 같은 결과가 나온다)
torch.manual_seed(42)
model = nn.Sequential(nn.Linear(7, 32), nn.ReLU(), nn.Linear(32, 16), nn.ReLU(), nn.Linear(16, 1))
opt = torch.optim.Adam(model.parameters(), lr=0.01)
loss_fn = nn.MSELoss()
X_train_t = torch.tensor(X_train_s, dtype=torch.float32)
y_train_t = torch.tensor(y_train.to_numpy() / 100, dtype=torch.float32).view(-1, 1)
loader = DataLoader(TensorDataset(X_train_t, y_train_t), batch_size=32, shuffle=True,
                    generator=torch.Generator().manual_seed(42))
for epoch in range(200):
    for xb, yb in loader:
        opt.zero_grad()
        loss = loss_fn(model(xb), yb)
        loss.backward()
        opt.step()

with torch.no_grad():
    nn_pred = model(torch.tensor(X_test_s, dtype=torch.float32)).numpy().ravel() * 100
print("학습 완료")
출력
학습 완료

이 단계의 함수는 실습 4의 2·3단계와 같습니다. 손실을 기록하지 않아 history 가 없고, 시험용을 텐서로 바꾸는 일을 예측하는 줄 안에서 바로 했다는 점만 다릅니다.

4단계. 세 모델 채점하기

Python
# 세 모델의 MAE 와 RMSE 를 표 하나로 모은다
rows = []
for name, pred in [("기준선(평균)", base_pred), ("직선 모델", lin_pred), ("신경망", nn_pred)]:
    rows.append({"모델": name,
                 "MAE": round(mean_absolute_error(y_test, pred), 1),
                 "RMSE": round(root_mean_squared_error(y_test, pred), 1)})
print(pd.DataFrame(rows).to_string(index=False))

# 주말 할인행사 날만 골라 두 모델이 얼마나 빗나가는지 견준다
mask = ((X_test["주말"] == 1) & (X_test["할인행사"] == 1)).to_numpy()
print("주말 할인행사 날", mask.sum(), "일")
print("  직선 모델 MAE:", round(mean_absolute_error(y_test[mask], lin_pred[mask]), 1))
print("  신경망 MAE:", round(mean_absolute_error(y_test[mask], nn_pred[mask]), 1))
출력
     모델  MAE  RMSE
기준선(평균) 77.8  98.8
  직선 모델 50.9  64.7
    신경망 25.0  33.7
주말 할인행사 날 4 일
  직선 모델 MAE: 132.6
  신경망 MAE: 33.8

이 단계의 함수입니다.

코드쉬운 뜻
root_mean_squared_error(실제, 예측)RMSE. 오차를 제곱해 평균 낸 뒤 제곱근을 씌운다
rows.append({...})모델마다 결과를 사전 하나로 만들어 리스트에 모은다
pd.DataFrame(rows).to_string(index=False)사전 리스트를 표로 만들고, 행 번호 없이 글자로 찍는다
(X_test["주말"] == 1) & (X_test["할인행사"] == 1)두 조건이 모두 맞는 날만 True 인 열을 만든다. & 는 "그리고"다
.to_numpy()그 True/False 열을 NumPy 배열로 바꾼다
mask.sum()True 의 개수, 곧 조건에 맞는 날수
y_test[mask], lin_pred[mask]True 인 자리만 골라낸다. 주말 할인행사 날의 실제값과 예측만 남는다

결과를 어떻게 읽나

기준선을 잣대로 봅니다. 숫자 하나만 보면 좋은지 나쁜지 알 수 없으므로, 늘 평균을 말하는 기준선에서 얼마나 줄였는지로 읽습니다.

모델MAE기준선보다 줄인 오차평균 주문량(약 506건)에 견주면RMSE ÷ MAE
기준선(평균)77.80%약 15%1.27
직선 모델50.9약 35%약 10%1.27
신경망25.0약 68%약 5%1.35
  • 직선 모델도 기준선보다 35%쯤 낫습니다. 날씨, 광고비, 주말 같은 특성이 주문량을 맞히는 데 쓸모가 있다는 뜻입니다.
  • 신경망의 오차는 직선 모델의 0.49배, 곧 절반 남짓입니다. 하루 주문량을 평균 5%쯤 빗나가는 정도라, 10~15% 빗나가는 모델과는 재고나 인력 계획에 쓸 수 있는 정도가 다릅니다.

차이는 조건이 겹치는 날에서 납니다. 직선 모델은 전체 MAE 가 50.9건인데 주말 할인행사 날에는 132.6건으로 2.6배쯤 커집니다. 신경망은 25.0건에서 33.8건으로 1.35배쯤에 그칩니다. 직선 모델은 "주말이면 얼마, 할인행사면 얼마"를 따로 더하기만 해서, 둘이 겹칠 때 주문이 따로 더한 것보다 크게 뛰는 효과를 그릴 수 없습니다. 신경망은 은닉층과 ReLU 로 이런 조합을 배웁니다. 신경망이 이긴 까닭은 "커서"가 아니라 "곡선과 조합을 그릴 수 있어서"입니다.

RMSE 와 MAE 의 비율로 큰 실수를 봅니다. RMSE 는 크게 빗나간 날을 무겁게 치므로, RMSE ÷ MAE 가 클수록 오차가 몇몇 날에 몰려 있다는 뜻입니다. 신경망은 전체 오차가 가장 작지만 이 비율이 1.35 로 조금 더 큽니다. 남은 오차 가운데 크게 빗나간 날이 상대적으로 섞여 있다는 뜻이고, 그런 날이 어떤 날인지가 다음에 살펴볼 곳입니다.

과하게 읽지 않아야 할 점도 있습니다.

조심할 점까닭
주말 할인행사 날은 4일뿐이다132.6 과 33.8 은 하루만 달라져도 크게 흔들린다. 경향으로만 읽는다
시험이 한 번뿐이다random_state=42 로 나눈 한 가지 결과다. 나누는 씨앗을 바꾸면 숫자가 달라진다. 단단히 결론 내리려면 여러 번 나눠 평균 내는 교차 검증을 쓴다
미래를 맞히는 시험이 아니다날짜를 섞어 나눴으므로 시험용 날의 앞뒤 날이 학습용에 들어 있다. "다음 주 주문량"을 맞히려면 앞 기간으로 학습하고 뒤 기간으로 시험해야 하고, 그러면 오차가 더 커질 수 있다
일부러 만든 데이터다곡선과 조합 효과를 심어 만든 가상 데이터라 신경망에 유리하다. 실제 데이터에서는 차이가 작거나 뒤집힐 수도 있다

정리하면 이 결과는 "데이터에 곡선과 조합 효과가 있으면 신경망이 그만큼을 잡아낸다"는 것을 보여 줍니다. "신경망이 언제나 낫다"는 뜻은 아닙니다.

이 실습이 뜻하는 것

이 실습은 주문량 예측 담당자 세 명에게 같은 시험지를 주는 일과 같습니다. 같은 자료를 준비하고(1단계), 늘 평균만 말하는 사람과 "주말이면 얼마, 할인이면 얼마"를 따로 더하기만 하는 사람을 세우고(2단계), 실습 4의 신경망을 다시 가르친 뒤(3단계), 셋을 같은 시험지로 채점합니다(4단계). 여기서 평균만 말하는 사람이 기준선, 더하기만 하는 사람이 직선 모델(LinearRegression), 셋째가 신경망입니다. 점수는 MAE 와 RMSE(Root Mean Squared Error, 평균 제곱근 오차)로 매기며, RMSE 는 크게 빗나간 날을 더 무겁게 칩니다. 숫자를 읽는 자세한 방법과 조심할 점은 바로 위 "결과를 어떻게 읽나"에 있으므로 여기서는 출력의 뜻만 짧게 짚습니다.

단계출력뜻
2, 3. 준비준비 완료, 학습 완료세 모델의 예측이 모두 마련되었습니다
4. 기준선MAE 77.8, RMSE 98.8아무것도 보지 않고 평균만 말할 때의 오차입니다. 견주는 잣대입니다
4. 직선 모델MAE 50.9, RMSE 64.7기준선보다 낫습니다. 특성이 쓸모 있다는 뜻입니다
4. 신경망MAE 25.0, RMSE 33.7직선 모델의 절반 남짓만 빗나갑니다
4. 주말 할인행사 날4일, 직선 132.6, 신경망 33.8조건이 겹치는 날에 직선 모델이 크게 빗나갑니다. 다만 4일뿐이라 경향으로만 읽습니다

한 줄로 줄이면, 곡선과 조합 효과를 심어 둔 이 데이터에서는 신경망이 기준선과 직선 모델보다 분명히 덜 빗나간다는 것을 보인 실습입니다. 일부러 신경망에 유리하게 만든 가상 데이터라, "신경망이 언제나 낫다"는 뜻은 아닙니다. 같은 학습 방법을 분류 문제로 넓힌 것이 아래 프로젝트입니다.

코드 설명

  • np.full(건수, 값) 은 같은 값으로 채운 배열을 만듭니다. 기준선은 날씨도 광고도 보지 않고 평균만 말합니다.
  • LinearRegression 은 은닉층과 활성화 함수가 없는, 직선만 그릴 수 있는 모델입니다.
  • MAE 로 기준선 77.8건, 직선 모델 50.9건, 신경망 25.0건입니다. 직선 모델은 기준선보다 낫지만, 신경망이 직선 모델의 절반 남짓만 빗나갑니다. RMSE 도 98.8, 64.7, 33.7 로 같은 순서입니다.
  • 차이는 곡선과 조합에서 납니다. 시험용의 주말 할인행사 날 4일만 보면 직선 모델은 132.6건을 빗나가고 신경망은 33.8건을 빗나갑니다. 직선 모델은 "주말이면 얼마, 할인행사면 얼마"를 따로 더할 뿐이라, 둘이 겹칠 때 주문이 크게 뛰는 것을 맞히지 못합니다. 다만 4일은 적은 날수라 이 숫자는 경향으로만 읽습니다.
  • 신경망이 늘 이기지는 않습니다. 이 데이터는 일부러 곡선 관계를 넣어 만든 연습용이라 차이가 분명하게 나왔습니다.
← 이전 절2장 · 실습 5 — 학습률 바꿔 보기다음 절 →2장 · 프로젝트 1 — 손글씨 숫자 분류하기