복습 음성 · 12분 22초

정칙화는 모델의 가중치에 제한을 주는 방법이다. 학습 데이터의 오차를 줄이는 과정에서 새 데이터에 대한 성능도 함께 확인한다.

Regularization(정칙화)과 Normalization(정규화)은 이름이 비슷해 무엇을 바꾸는지부터 구분했다. L1과 L2가 가중치에 주는 차이는 작은 회귀 예제로 확인했다. 위 음성은 강의 복습용이고, 본문의 숫자와 코드는 별도로 만든 예제다.

1. 정칙화와 정규화는 어디에 적용할까

  • 정칙화: 모델이 학습할 수 있는 방식이나 가중치를 조절한다. L1·L2 벌점, Dropout, 조기 종료가 여기에 들어간다.
  • 정규화: 데이터나 중간 값의 크기·범위를 조절한다. Min-Max Scaling이나 벡터 길이 조정이 예다.
  • 표준화: 평균과 표준편차를 기준으로 값의 크기를 조절한다. StandardScaler에서 사용하는 방법이다.

정규화라는 번역은 문맥마다 넓게 쓰인다. 용어만 외우기보다 어떤 값에 어떤 계산을 하는지 확인하는 편이 덜 헷갈린다. Batch Normalization도 신경망 내부의 값을 조정하는 층이다. 정칙화 효과가 생길 수 있지만 L1·L2와 같은 가중치 벌점을 뜻하지는 않는다.

Standardization(표준화)도 전처리에서 사용했던 방법이다. 두 식을 다시 적어둔다.

Min-Max: (x - 학습 데이터의 최솟값) / (최댓값 - 최솟값)
Standardization: (x - 학습 데이터의 평균) / 학습 데이터의 표준편차

표준화했다고 데이터가 정규분포로 바뀌는 것은 아니다. 평균과 퍼짐을 조정할 뿐, 분포의 모양까지 바꾸지는 않는다. Min-Max도 학습 데이터의 범위를 기준으로 하므로 새 값이 그 범위를 벗어나면 변환 결과가 0보다 작거나 1보다 커질 수 있다.

평균과 최댓값을 구하는 대상은 학습 데이터다. 검증·테스트 데이터에는 그 기준을 그대로 적용한다. 이 순서는 정칙화 모델을 쓸 때도 같다.

2. 학습 오차만 줄이면 생길 수 있는 일

Overfitting(과적합)은 학습 데이터에 너무 맞춰져서 새 데이터에 대한 성능이 떨어지는 상태다. 정답에 공통으로 나타나는 관계뿐 아니라 우연한 잡음까지 따라갈 수 있다.

학습 손실은 내려가는데 검증 손실이 다시 올라가는 흐름이라면 과적합을 의심할 수 있다. 다만 곡선 하나만 보고 원인을 확정하지는 않는다. 분할 방법, 데이터 누수, 검증 데이터의 크기와 분포도 함께 확인해야 한다.

정칙화는 모델에 추가 조건을 주어 지나치게 복잡한 해를 선택하는 경향을 줄인다. 가중치에 벌점을 넣는 경우에는 예측 오차와 가중치 크기 사이에서 타협점을 찾게 된다.

학습 목적함수 = 데이터에 대한 손실 + 정칙화 강도 × 가중치 벌점

정칙화를 강하게 하면 학습 데이터의 손실이 조금 커질 수 있다. 그 대신 검증 성능이 좋아지는지를 보는 것이다. 너무 강하게 제한해서 학습·검증 양쪽 모두 잘 맞추지 못하면 Underfitting(과소적합)이 될 수 있다.

데이터를 더 모으거나 적절히 증강하는 방법, 모델 크기를 줄이는 방법, Early Stopping(조기 종료)도 함께 생각할 수 있다. L1·L2 하나만으로 모든 과적합 문제를 해결하는 것은 어렵다.

3. L1과 L2는 무엇을 더하는가

가중치를 w1, w2, ...라고 하면 기본적인 벌점은 다음처럼 쓴다.

L1 벌점 = |w1| + |w2| + ...
L2 벌점 = w1² + w2² + ...

L1 목적함수 = 데이터 손실 + λ × L1 벌점
L2 목적함수 = 데이터 손실 + λ × L2 벌점

여기서 L2 정칙화에 쓰는 식은 L2 norm(노름)의 제곱이다. 제곱합에 다시 제곱근을 씌운 식과 구분한다. 자료에 따라 앞에 1/2를 붙이기도 하므로 기울기를 계산할 때 계수까지 확인해야 한다.

λ는 Lambda(람다)로 읽고 벌점의 강도를 조절한다. 직접 정하는 Hyperparameter(하이퍼파라미터)다. 학습 중 구하는 가중치와 역할이 다르다. 같은 목적함수에서 λ가 0이면 이 벌점이 사라지고, 크게 할수록 가중치 크기를 더 강하게 제한한다.

일반적인 선형회귀에서는 절편을 벌점에서 제외하는 경우가 많다. 라이브러리나 모델에 따라 적용 대상이 다를 수 있으니 모든 매개변수에 자동으로 같은 벌점이 붙는다고 생각하지 않는다.

그리고 오차를 제곱하는 것과 가중치를 제곱하는 것은 다른 계산이다. MSE는 예측값 - 정답의 제곱을 평균낸다. L2 벌점은 가중치 자체의 제곱합이다. MSE로 학습하는 선형회귀라고 해서 이미 L2 정칙화까지 적용한 것은 아니다.

4. L1에서는 왜 정확히 0이 나올까

L1을 이용한 선형회귀가 Lasso다. 일부 계수가 정확히 0이 되는 Sparse(희소한) 해를 만들 수 있어서 Feature Selection(특성 선택)과 연결된다.

절댓값 함수는 0에서 뾰족하게 꺾인다. 0의 양쪽에서는 기울기의 크기가 일정하고, 0에서는 보통의 미분값 하나로 표현되지 않는다. 이 부분을 다루는 최적화 방법을 쓰면 작은 계수의 최적값이 정확히 0에 놓일 수 있다.

스칼라 가중치 하나로 보면 조금 더 분명하다. 정칙화가 없을 때 w=a에서 최소가 되는 손실을 놓고 비교한다.

L1: 0.5 × (w - a)² + λ × |w|
해: sign(a) × max(|a| - λ, 0)

L2: 0.5 × (w - a)² + λ × w²
해: a / (1 + 2λ)

λ=0.5일 때 a=0.2는 L1에서 0, L2에서 0.1이 된다. a=3.0은 각각 2.5와 1.5가 된다. L1은 일정 범위 안의 작은 값을 0으로 보내고, L2는 비율에 따라 줄이는 모습을 볼 수 있다.

이 L1 계산을 Soft Thresholding(소프트 임계화)이라고 부른다. scikit-learn의 Lasso는 Coordinate Descent(좌표 하강법)를 사용한다. 신경망의 손실에 절댓값 항을 더하고 일반적인 경사하강법을 돌리는 경우까지 모든 가중치가 정확히 0이 된다고 확장해서 이해하면 안 된다. 최적화 방법도 결과에 영향을 준다.

가중치가 두 개인 그림으로 보면 L1의 제약 영역은 마름모, L2는 원 모양으로 나타낼 수 있다. L1의 꼭짓점은 한 좌표가 0인 곳이라 희소한 해를 이해하는 데 도움이 된다. 실제로 어떤 계수가 0이 되는지는 데이터와 정칙화 강도에 따라 달라진다.

5. L2에서는 큰 가중치를 더 줄인다

L2를 이용한 선형회귀가 Ridge다. λ × w²를 미분하면 2λw가 된다. 가중치의 절댓값이 클수록 벌점에서 오는 기울기도 커진다.

이 때문에 큰 계수에 제동을 걸고 가중치를 전반적으로 작게 만드는 경향이 있다. Lasso처럼 정확히 0인 계수를 많이 만드는 것이 주된 특징은 아니다. 서로 비슷한 정보를 가진 특성이 많을 때 계수가 불안정하게 커지는 현상을 줄이는 데도 쓰인다.

구분 Lasso Ridge
벌점 가중치 절댓값의 합 가중치 제곱의 합
자주 보는 결과 일부 계수가 정확히 0 계수가 전반적으로 축소
해석할 때 볼 점 어떤 특성이 선택됐는지, 선택이 안정적인지 계수 크기와 예측의 안정성

L1과 L2를 섞은 Elastic Net도 있다. 특성 선택과 계수 축소를 함께 조절하는 방법으로 이해해둔다. 어떤 방법이 더 좋은지는 검증 데이터에서 판단해야 한다.

6. 벌점을 줄 때 스케일링이 필요한 이유

같은 길이를 미터로 넣을 때와 밀리미터로 넣을 때를 생각하면 쉽다. 입력 숫자가 1,000배 커지면 같은 예측을 만드는 계수는 1,000분의 1이 될 수 있다. 예측은 같아도 가중치의 크기가 달라진다.

가중치 크기에 벌점을 주면 이렇게 단위 때문에 생긴 차이가 학습에 영향을 준다. 그래서 특성들의 크기를 맞춘 뒤 Lasso나 Ridge를 적용하는 경우가 많다. 이상치나 데이터 성격에 따라 스케일러 선택은 달라질 수 있다.

이번 예제에서는 StandardScaler와 모델을 Pipeline(파이프라인)으로 묶었다. fit(X_train, y_train) 안에서 학습 데이터의 평균과 표준편차를 구하고, 테스트 예측에는 같은 변환을 사용한다.

출력되는 계수는 표준화한 입력에 대한 계수다. 원래 단위의 관계식에 적은 계수와 숫자가 똑같을 필요는 없다. 원래 단위로 돌아가려면 각 계수를 해당 특성의 scale_로 나누고 절편도 함께 조정해야 한다.

7. 작은 회귀 데이터로 비교해보기

네 개의 특성 중 앞의 두 개만 정답 식에 사용했다. 정답에는 작은 잡음도 더했다. 뒤의 두 특성은 이번에 만든 정답 관계에 들어가지 않는다.

y = 3 + 4 × 첫 번째 특성 - 2 × 두 번째 특성 + 잡음
전체 120개 → 학습 84개 / 테스트 36개
데이터 생성 seed=42, 분할 random_state=101

Lasso의 alpha=0.15와 Ridge의 alpha=10은 계수 변화를 보기 위해 미리 정한 값이다. 최적값을 찾은 결과가 아니다. 또 두 모델의 alpha를 같은 강도로 직접 비교할 수 없다. scikit-learn은 다음처럼 데이터 손실의 배율도 다르게 정의한다. 아래 식에서는 절편 표기를 생략했다.

Lasso: 잔차 제곱합 / (2 × 샘플 수) + alpha × 가중치 절댓값 합
Ridge: 잔차 제곱합 + alpha × 가중치 제곱합

정확한 목적함수는 Lasso 문서와 Ridge 문서에서 확인했다. 다른 라이브러리로 옮길 때도 이름이 같은 alpha나 lambda만 복사하기보다 식을 먼저 비교해야 한다.

다음 코드는 아래쪽의 스칼라 계산까지 한 번에 실행한다.

import numpy as np
from sklearn.linear_model import Lasso, LinearRegression, Ridge
from sklearn.metrics import mean_absolute_error, mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

# 관계를 직접 정한 합성 회귀 자료. 뒤의 두 특성은 정답 식에 넣지 않았다.
rng = np.random.RandomState(42)
X = rng.normal(size=(120, 4))
y = 3 + X @ np.array([4.0, -2.0, 0.0, 0.0]) + rng.normal(0, 0.4, 120)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=101
)

# alpha는 효과 관찰용 고정값이다. 두 모델에서 같은 강도를 뜻하지 않는다.
models = {
    "LinearRegression": LinearRegression(),
    "Lasso(alpha=0.15)": Lasso(alpha=0.15, max_iter=10000),
    "Ridge(alpha=10)": Ridge(alpha=10),
}
fitted = {}
print("Train / Test:", X_train.shape, X_test.shape)
for name, model in models.items():
    pipeline = make_pipeline(StandardScaler(), model)
    pipeline.fit(X_train, y_train)
    prediction = pipeline.predict(X_test)
    fitted[name] = pipeline
    mse = mean_squared_error(y_test, prediction)
    print(name)
    print("  표준화 입력의 계수:", np.round(model.coef_, 4).tolist())
    print("  정확히 0인 계수 수:", int(np.sum(model.coef_ == 0)))
    print(f"  MAE={mean_absolute_error(y_test, prediction):.4f} "
          f"MSE={mse:.4f} RMSE={np.sqrt(mse):.4f}")

# 스칼라 목적함수 0.5*(w-a)**2 + penalty의 해를 비교한다.
a = np.array([0.2, 3.0])
lam = 0.5
l1_solution = np.sign(a) * np.maximum(np.abs(a) - lam, 0)
l2_solution = a / (1 + 2 * lam)
print("스칼라 예제 a:", a.tolist())
print("L1 해:", l1_solution.tolist())
print("L2 해:", l2_solution.tolist())

실행 출력이다.

Train / Test: (84, 4) (36, 4)
LinearRegression
  표준화 입력의 계수: [3.6537, -2.0145, 0.0551, 0.0284]
  정확히 0인 계수 수: 0
  MAE=0.2969 MSE=0.1533 RMSE=0.3916
Lasso(alpha=0.15)
  표준화 입력의 계수: [3.4923, -1.85, 0.0, 0.0]
  정확히 0인 계수 수: 2
  MAE=0.3403 MSE=0.1624 RMSE=0.4030
Ridge(alpha=10)
  표준화 입력의 계수: [3.2518, -1.7789, 0.0065, 0.0127]
  정확히 0인 계수 수: 0
  MAE=0.4432 MSE=0.2944 RMSE=0.5426
스칼라 예제 a: [0.2, 3.0]
L1 해: [0.0, 2.5]
L2 해: [0.1, 1.5]

8. 결과에서 읽을 수 있는 것

Lasso는 뒤의 두 계수를 정확히 0으로 만들었다. 이번 정답 식에 넣지 않았던 특성들이다. Ridge에서는 모든 계수가 남았고 앞의 큰 두 계수도 줄었다.

다만 테스트 RMSE는 일반 선형회귀가 0.3916, Lasso가 0.4030, Ridge가 0.5426이었다. 이 실행에서는 정칙화 없는 모델이 가장 작았다. 관계가 단순한 합성 데이터에서 고정한 강도를 비교한 결과다. 정칙화를 넣었다는 이유만으로 테스트 오차가 반드시 줄어드는 것은 아니라는 점도 같이 확인했다.

표의 MAE·MSE·RMSE는 테스트의 예측 오차다. 여기에 정칙화 벌점까지 더한 학습 목적함수 값을 출력한 것이 아니다. 모델마다 학습 목적함수에 들어가는 항이 다르므로 학습 중 찍힌 숫자를 비교할 때는 무엇을 포함한 값인지 봐야 한다.

계수가 0이 됐다고 그 특성이 현실에서도 쓸모없다는 결론을 내릴 수는 없다. 서로 비슷한 특성 사이에서 하나만 선택될 수도 있고, 다른 데이터나 alpha에서는 선택 결과가 바뀔 수 있다. 인과관계까지 설명해주는 결과도 아니다.

또 0인 계수가 생겼다고 저장 용량이나 계산 시간이 자동으로 크게 줄어드는 것은 아니다. 실제로 줄이려면 선택된 특성으로 다시 구성하거나 희소 연산을 활용하는 등 구현도 따라가야 한다.

9. 실습에서 헷갈리기 쉬운 부분

특성이 하나뿐인데 Lasso의 계수가 0이 됐다면 우선 alpha와 입력·정답의 크기를 본다. 특성이 하나라서 Lasso를 쓸 수 없는 것은 아니다. 현재 강도에서 그 계수를 유지하는 이득보다 벌점이 큰 상황일 수 있다.

max_iter는 최적화 반복의 상한이다. 아주 작게 설정한 뒤 수렴 경고가 나왔다면 반복 횟수와 스케일을 먼저 확인한다. 그 상태의 점수나 실행 시간만으로 L1과 L2 전체의 우열을 정하지 않는다. 이번 Lasso는 max_iter=10000을 사용했다.

신경망에서는 Weight Decay(가중치 감쇠)라는 이름도 자주 나온다. 단순한 SGD에서는 적절한 계수 정의 아래 L2 벌점과 같은 형태의 갱신이 된다. Adam 같은 적응형 최적화에서는 손실에 L2를 더하는 방식과 감쇠를 별도로 적용하는 방식이 달라질 수 있다. AdamW를 사용할 때는 이 차이를 따로 확인할 필요가 있다.

실제 모델에서 alpha를 고를 때는 학습·검증 데이터를 이용해 선택하고, 마지막에 테스트를 평가한다. 이번 코드는 효과를 보는 작은 예제여서 검증 세트나 교차검증을 통한 탐색은 하지 않았다.

10. 정칙화의 강도는 검증 성능을 보고 정한다

정규화·표준화는 값의 크기를 조정하고, 정칙화는 모델이 학습하는 해에 조건을 더한다. L1은 일부 계수를 0으로 만들 수 있고, L2는 큰 계수를 줄이는 데 쓰인다. 적용 여부와 강도는 검증 성능을 보며 정한다.

실행 환경은 Python 3.12.9, NumPy 2.5.2, scikit-learn 1.9.0, PyTorch 2.13.0 (CPU)이며 2026년 9월 28일에 위의 작은 예제를 실행했다. 본문은 강의 복습 노트와 관련 개인 메모를 바탕으로 다시 정리했다. 대규모 데이터 학습이나 최적 하이퍼파라미터 탐색은 수행하지 않았다.