이번 Chapter에 대부분의 주제는 신경망을 이해, 구축, 훈련시키는데 필수적입니다.
간단한 선형 회귀부터 비선형 데이터셋에 훈련 가능한 다항 회귀, 학습 곡선을 사용해 과대적합을 감소시킬 수 있는 규제 기법과 분류 작업에 널리 사용되는 로지스틱/소프트맥스 회귀에 대하여 알아보겠습니다.
※ 해당 장에서는 선형대수, 미분 기호를 사용한 수학 방정식이 많이 나옵니다.
벡터와 행렬, 전치(Transpose), 점곱, 역행렬(Inverse Matrix), 편미분(Parital Derivative)에 대해 알아야 합니다.
이 부분은 선형 대수와 미분에 대하여 다시 공부하여 포스팅 하도록 하겠습니다.
목차는 아래와 같습니다.
Chap4 목차
- 정규방정식
- 계산 복잡도
- 배치 경사 하강법(Batch Gradient Descent)
- 확률적 경사하강법(SGD; Stochastic Gradient Descent)
- 미니배치 경사 하강법(Mini-batch Gradient Descent)
3. 다항 회귀(Polynomial Regression)& 학습 곡선
- 다항 회귀
- 학습 곡선
- 릿지 회귀
- 라쏘 회귀
- 엘라스틱넷
- 조기종료
- 확률 추정
- 훈련과 비용 함수
- 결정 경계
- 소프트맥스 회귀
1. 선형 회귀
아래는 1장에서 본 삶의 만족도에 대한 간단한 선형회귀 모델입니다.
이 모델은 입력 특성(1인당_GDP)에 대한 선형 함수로 theta_0과 theta_1이 모델 파라미터
[모델의 공식]
$$ S = \theta_0 + \theta_1 * GDP per Capita $$
더 일반적으로 선형 모델은 아래의 식과 같이 입력 특성의 가중치 합과 편향(bias; 절편: Intercept) 상수를 더해 예측 생성
$$ \hat{y} = \theta_0 + \theta_1X_1 + \theta_2X_2 + ... + \theta_nX_n $$
$$ \hat{y} : 예측값 , n : 특성 수 , x_{i} : i 번째 특성값 , \theta_j : j 번째 모델 파라미터 $$
위의 식을 벡터 형태로 더 간단하게 사용 가능
$$ \hat{y} = h_\theta(x) = \theta\cdot x $$
$$ \theta : 편향 \theta_{0}과 \theta_{1}에서 \theta_{n} 까지의 특성 가중치를 담은 모델의 파라미터 벡터 $$
$$ x : x_{0} 에서 x_{n}까지 담은 샘플의 특성 벡터로 x_{0} = 1 $$
$$ \theta\cdot x : 벡터 \theta와 x 의 점곱 $$
$$ \theta\cdot x = \theta_0X_0 + \theta_1X_1 + \theta_2X_2 + ... + \theta_nX_n $$
$$ h_\theta : \theta 를 사용한 가설 함수 $$
모델 훈련 = 모델이 훈련 세트에 잘 맞도록 모델 파라미터를 설정하는 것
회귀에서 널리 사용되는 성능 측정 지표 = RMSE
So, 선형 회귀 모델을 훈련시키려면 RMSE를 최소화하는 theta를 찾아야함
선형 회귀 모델의 MSE 비용 함수
:훈련 세트 X에 대한 선형 회귀 가설(h_theta)의 MSE 계산식
[표기법의 경우 2장에서도 설명이 있음]
$$ MSE(X,h_\theta)= \frac{1}{m}\sum_{i=1}^{m}(\theta^{T}x^{(i)} - y^{i})^{2} $$
※ 2장과의 하나의 차이는 모델이 파라미터 벡터를 가진다는 것을 명확히 하기 위해 h_theta를 사용한 것 뿐으로
간단하게 표시한다면 MSE(X,h_theta)대신 MSE(theta)라고만 사용
1-1 정규 방정식
: 비용 함수를 최소화하는 theta 값을 찾기 위한 해석적인 방법(수학 공식)
[정규 방정식 공식]
$$ \hat{\theta}=(X^{T}X)^{-1}X^{T}y, \hat{\theta}: 비용함수를 최소화하는 \theta값 $$
$$ y = y^{(1)} 부터 y^{(m)}까지 포함하는 타깃 벡터 $$
※ 유사 역행렬 자체는 SVD(Singular Value Decomposition; 특잇값 분해)라는 표준 행렬 분해 기법을 사용해 계산
정규방정식을 계산하는 것보다 유사역행렬을 사용하는 것이 효율적이며, 극단적인 값을 처리 가능
[Python]
- 무작위로 생성한 선형 데이터셋
데이터 생성에 사용한 함수 : y = 4 + 3x_1 + 가우시안 잡음
import numpy as np
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)
# 시각화
plt.plot(X, y, "b.")
plt.xlabel("$x_1$", fontsize=18)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.axis([0, 2, 0, 15])
plt.show()

- 정규방정식을 사용해 hat(theta)를 계산
: 역행렬 계산(np.linalg 모듈에 있는 inv() 함수를 사용) → 행렬 곱셈(dot() 메서드 사용)
theta_0 = 4.215 와 theta_1 = 2.77 대신 theta_0 = 4, theta_1 = 3을 기대했지만
잡음으로 인해 원래 함수의 파라미터를 정확하게 재현하지 못함
X_b = np.c_[np.ones((100, 1)), X] # 모든 샘플에 x0 = 1을 추가합니다.
theta_best = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y)
theta_best
# result
# array([[4.21509616], [2.77011339]])
- hat(theta)를 사용한 예측
X_new = np.array([[0], [2]])
X_new_b = np.c_[np.ones((2, 1)), X_new] # 모든 샘플에 x0 = 1을 추가
y_predict = X_new_b.dot(theta_best)
y_predict
# 시각화
plt.plot(X_new, y_predict, "r-", linewidth=2, label="Predictions")
plt.plot(X, y, "b.")
plt.xlabel("$x_1$", fontsize=18)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.legend(loc="upper left", fontsize=14)
plt.axis([0, 2, 0, 15])
plt.show()

- 사이킷런에서 선형회귀 수행
※ 사이킷런은 가중치(coef_)와 편향(intercept_)를 분리하여 저장
※ LinearRegression 클래스는 scipy.linalg.lstsq() 함수를 기반으로 하여 직접 호출이 가능
이 함수는 hat(theta) = X^{+}y를 계산, X^{+}는 X의 유사역행렬(Pseudoinverse: 무어-펜로즈 역행렬)
np.linalg.pinv() 함수를 사용해 유사역행렬을 구할 수 있음
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X, y)
lin_reg.intercept_, lin_reg.coef_
# result : (array([4.21509616]), array([[2.77011339]]))
# scipy.linalg.lstsq() 함수를 직접 호출
lin_reg.predict(X_new) # result : array([[4.21509616], [9.75532293]])
# 유사 역행렬 호출
theta_best_svd, residuals, rank, s = np.linalg.lstsq(X_b, y, rcond=1e-6)
theta_best_svd # result : array([[4.21509616], [2.77011339]])
1-2 계산 복잡도(Computational Complexity)
정규방정식은 (n+1) X (n+1) 크기가 되는 (아래 식)의 역행렬을 계산(n= 특성수)
$$ X^{T}X $$
- 역행렬을 계산하는 계산 복잡도 : 일반적으로 O(n^2.4) ~ O(n^3) 사이
즉, 특성 수가 두 배로 늘어나면 계산 시간이 대략 5.3(2^2.4) ~ 8(2^3)배로 증가
$$ LinearRegression 클래스를 사용하는 SVD 방법의 계산 복잡도 : O(n^{2}) $$
- 정규 방정식으로 훈련된 선형 회귀 모델은 예측이 빠름
why? 예측 계산 복잡도는 샘플 수와 특성 수에 선형적이기 때문
2. 경사 하강법(Gradient Descent; GD)
: 여러 종류의 문제에서 최적의 해법을 찾을 수 있는 일반적인 최적화 알고리즘
※ 경사하강법을 사용할 때는 반드시 모든 특성이 같은 스케일을 같도록 만들어야 함(e.g. StandardScaler 사용)
Default Idea : 비용 함수를 최소화하기 위해 반복해서 파라미터를 조정
- 파라미터 벡터(theta)에 대해 비용 함수의 현재 gradient를 계산 → 0이 되면 최솟값에 도달한 것
- theta를 임의의 값으로 시작해서(= 무작위 초기화) 한 번에 조금씩 비용 함수(e.g. MSE)가 감소되는 방향으로 진행하여
알고리즘이 최솟값에 수렴할 때 까지 점진적으로 향상시킴
- 경사하강법에서 중요한 파라미터: 스텝의 크기로 학습률(Learning Rate) 하이퍼파라미터로 결정
- 학습률이 너무 작으면 알고리즘이 수렴하기 위해 많은 반복을 진행해야 하기에 시간이 오래 걸림
- 학습률이 너무 크면 알고리즘이 더 큰 값으로 발산하게 만들어 적절한 해법을 찾지 못하게 됨
- 아래의 그림을 보면 경사하강법의 두 가지 문제점이 나옴
● 무작위 초기화로 인해 알고리즘이 왼쪽에서 시작하면 전역 최솟값보다 덜 좋은 지역 최솟값에 수렴
● 알고리즘이 오른쪽에서 시작하면 평탄한 지역을 지나기 위해 시간이 오래 걸리고 일찍 멈춰 전역 최솟값에 도달 못함
- 선형 회귀를 위한 MSE 비용 함수는 곡선에서 어떤 두 점을 선택해 선을 그어도 곡선을 가로지르지 않는 볼록 함수(Convex Funtion)
- 이는 지역 최솟값이 없고, 하나의 전역 최솟값만 있다는 의미
- 연속된 함수이고 기울기가 갑자기 변하지 않음
기술적으로 보면 이 함수의 도함수가 립시츠 연속(Lipschitz Continuous).
어떤 함수의 도함수가 일정한 범위 안에서 변할 때 이 함수를 립시츠 연속함수라고 함
e.g. sin(x)는 립시츠 연속함수이지만, 루트(x)는 x=0 일 때 기울기가 무한대가 되므로 립시츠 연속 함수가 아님
MSE는 x가 무한대일 때 기울기가 무한대가 되므로 국부적인(Locally) 립시츠 함수라고 함
2-1 배치 경사 하강법(Batch Gradient Descent)
: 매 경사 하강법 스템에서 전체 훈련 세트 X에 대해 계산하기에 배치 경사 하강법(Batch Gradient Descent)이라 함
- 매 스텝에서 훈련 데이터 전체를 사용(≒ 전체 경사 하강법)
- 경사하강법을 구현하려면 각 모델 파라미터 theta_j에 대해 비용 함수의 그레디언트를 계산해야 함
(= theta_j가 조금 변경될 때 비용 함수가 얼마나 바뀌는지 계산해야 함; 편도 함수: Partial Derivative)
[파라미터 theta_j에 대한 비용 함수의 편도 함수]
$$ \frac{\partial}{\partial\theta_j}MSE(\theta)=\frac{2}{m}\sum_{i=1}^{m}(\theta^{T}x{(i)} - y^{(i)})x_{j}^{(i)} $$
[비용 함수의 그레디언트 벡터]
- 아래 그레디언트 벡터 수식는 비용 함수의 편도함수를 모두 담고 있음
$$ \triangledown_{\theta}MSE(\theta)=\begin{pmatrix}\frac{\partial}{\partial\theta_0}MSE(\theta)\\\frac{\partial}{\partial\theta_1}MSE(\theta)\\...\\\frac{\partial}{\partial\theta_n}MSE(\theta)\end{pmatrix} = \frac{2}{m}X^{T}(X\theta - y) $$
IF 위로 향하는 그레디언트 벡터가 구해지면? 반대 방향인 아래로 가야함
(즉, theta에서 \triangledown_{theta}MSE(theta)를 빼야한다는 말로 여기서 학습률인 eta를 사용)
내려가는 스텝의 크기를 결정하기 위해 그레디언트 벡터에 eta를 곱해줌
※ eta : 그리스 7번째 문자로 갈고리 모양
[경사하강법의 스텝]
$$ \theta^{next step}=\theta - \eta \triangledown_{\theta} MSE(\theta) $$
[python 구현 코드]
- 알고리즘 구현
eta = 0.1 # 학습률
n_iterations = 1000
m = 100
theta = np.random.randn(2,1) # 랜덤 초기화
for iteration in range(n_iterations):
gradients = 2/m * X_b.T.dot(X_b.dot(theta) - y)
theta = theta - eta * gradients
theta # result : array([[4.21509616], [2.77011339]]) =
- 학습률(eta)를 변경하여 진행한 경사하강법 스텝 처음 10개 시각화
theta_path_bgd = []
# 함수
def plot_gradient_descent(theta, eta, theta_path=None):
m = len(X_b)
plt.plot(X, y, "b.")
n_iterations = 1000
for iteration in range(n_iterations):
if iteration < 10:
y_predict = X_new_b.dot(theta)
style = "b-" if iteration > 0 else "r--"
plt.plot(X_new, y_predict, style)
gradients = 2/m * X_b.T.dot(X_b.dot(theta) - y)
theta = theta - eta * gradients
if theta_path is not None:
theta_path.append(theta)
plt.xlabel("$x_1$", fontsize=18)
plt.axis([0, 2, 0, 15])
plt.title(r"$\eta = {}$".format(eta), fontsize=16)
np.random.seed(42)
theta = np.random.randn(2,1) # random initialization
# 시각화
plt.figure(figsize=(10,4))
plt.subplot(131); plot_gradient_descent(theta, eta=0.02)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.subplot(132); plot_gradient_descent(theta, eta=0.1, theta_path=theta_path_bgd)
plt.subplot(133); plot_gradient_descent(theta, eta=0.5)
plt.show()

[적절한 학습률 찾는 방법: Grid Search 사용]
GridSearch는 시간이 많이 걸리기에 반복 횟수를 제한해야함
너무 작으면 최적점에 도달하기 전에 알고리즘이 멈추고, 너무 크면 모델 파라미터가 더 변하지 않는 동안 시간을 낭비
How? 반복 횟수를 크게 지정 + 그레디언트 벡터가 아주 작아지면(= 벡터 노름이 허용오차보다 작아지면) 중지
2-2 확률적 경사 하강법(Stochastic Gradient Descent)
배치하강법에서 매 스텝에서 전체 훈련 세트를 사용해 그레디언트를 계산하기에 속도에 문제가 존재
따라서 매 스텝에서 한 개의 샘플을 무작위로 선택하고 하나의 샘플에 대한 그레디언트를 계산하는 방법을 사용
But, 확률적이므로 훨씬 불안정함
→ 비용 함수가 최솟값에 다다를 때까지 부드럽게 감소하지 않고 위 아래로 요동치며 평균적으로 감소
시간이 지나면 최솟값에 매우 근접하겠지만 요동이 지속되면서 최솟값에 안착 못할 수 있음
알고리즘이 멈출 때 좋은 파라미터가 구해지겠지만 이것이 최적치는 아님
[아래 사진 참고]
[사진]과 같이 비용 함수가 매우 불안정한 경우 알고리즘이 지역 최솟값을 건너 뛰도록 도와주므로 확률적 경사하강법이 전역 최솟값을 찾을 가능성이 높음
- 무작위성이 지역 최솟값에서 탈출시켜주는 것은 좋지만 전역 최솟값에 다다르지 못하게 한다는 점이 단점
- 해결방법 : 학습률을 점진적으로 감소시키는 것
● 시작할 때는 학습률을 크게(수렴을 빠르게 = 지역 최솟값에 빠지는 것을 방지)
● 점차 학습률을 작게 줄이기(전역 최솟값에 도달할 수 있도록)
- 학습 스케줄 : 매 반복에서 학습률을 결정하는 함수
※ 이는 금속공학 분야에 어닐링(풀림) 과정에서 영감을 얻은 담금질 기법(Simulated Annealing)알고리즘과 유사
※ Annealing : 가열한 금속을 천천히 냉각하는 기법
[학습 스케줄을 사용한 확률적 경사하강법 Python 코드]
: SGD 방식으로 선형 회귀를 사용하려면 기본값으로 제곱 오차 비용 함수를 최적화하는 함수 사용(SGDRegressor)
- 배치 경사 하강법이 1,000번 반복하는 동안 훈련 세트에서 50번만 반복하여도 좋은 값에 도달
n_epochs = 50 # 50번
t0, t1 = 5, 50 # 학습 스케줄 하이퍼파라미터
def learning_schedule(t):
return t0 / (t + t1)
theta = np.random.randn(2,1) # 랜덤 초기화
for epoch in range(n_epochs):
# m(훈련 세트에 있는 샘플 수)만큼 반복하고
for i in range(m):
if epoch == 0 and i < 20:
y_predict = X_new_b.dot(theta)
style = "b-" if i > 0 else "r--"
plt.plot(X_new, y_predict, style)
random_index = np.random.randint(m)
xi = X_b[random_index:random_index+1]
yi = y[random_index:random_index+1]
gradients = 2 * xi.T.dot(xi.dot(theta) - yi)
eta = learning_schedule(epoch * m + i)
theta = theta - eta * gradients
theta_path_sgd.append(theta)
theta # result : array([[4.21076011], [2.74856079]])
# 시각화
plt.plot(X, y, "b.")
plt.xlabel("$x_1$", fontsize=18)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.axis([0, 2, 0, 15])
plt.show()

- 다른 방식으로 좋은 값 얻기
- 최대 1,000번 에포크 동안(max_iter=1000) 실행
- 한 에포크에서 0.001보다 적게 손실이 줄어들때까지 실행(tol=1e-3)
- 학습률 : 0.1(eta0 = 0.1)
from sklearn.linear_model import SGDRegressor
sgd_reg = SGDRegressor(max_iter=1000, tol=1e-3, penalty=None, eta0=0.1, random_state=42)
sgd_reg.fit(X, y.ravel())
sgd_reg.intercept_, sgd_reg.coef_ # result : (array([4.24365286]), array([2.8250878]))
확률적 경사 하강법을 사용할 때 훈련 샘플이 IID(Independent and Identically Distributed)를 만족해야 평균적으로 파라미터가 전역 최적점을 향해 진행한다고 보장 가능 : 간단한 방법은 훈련하는 동안 샘플을 섞는 것
만약 레이블 순서대로 정렬된 샘플처럼 섞지 않고 사용하면 확률적 경사 하강법이 먼저 한 레이블에 최적화하고 그 다음 두번째 레이블을 최적화하는 식으로 진행하여 결국 최적점에 가깝게 도달하지 못할 것
2-3 미니배치 경사 하강법(Mini-batch Gradient Descent)
미니배치라 부르는 임의의 작은 샘플 세트에 대해 그레디언트를 계산
- SGD에 비교했을 때 장점
: 행렬연산에 최적화된 하드웨어(GPU)를 사용해서 얻는 성능 향상
미니 배치를 어느 정도 크게 하면 파라미터 공간에서 SGD보다 덜 불규칙하게 움직임(= 최솟값에 더 가까이 도달)
But 지역 최솟값에서 빠져나오기 힘들 수 있음
[미니배치 경사 하강법 Python 코드]
theta_path_mgd = []
n_iterations = 50
minibatch_size = 20
np.random.seed(42)
theta = np.random.randn(2,1) # 랜덤 초기화
t0, t1 = 200, 1000
def learning_schedule(t):
return t0 / (t + t1)
t = 0
for epoch in range(n_iterations):
shuffled_indices = np.random.permutation(m)
X_b_shuffled = X_b[shuffled_indices]
y_shuffled = y[shuffled_indices]
for i in range(0, m, minibatch_size):
t += 1
xi = X_b_shuffled[i:i+minibatch_size]
yi = y_shuffled[i:i+minibatch_size]
gradients = 2/minibatch_size * xi.T.dot(xi.dot(theta) - yi)
eta = learning_schedule(t)
theta = theta - eta * gradients
theta_path_mgd.append(theta)
theta # result : array([[4.25214635], [2.7896408 ]])
[세 가지 경사 하강법이 훈련 과정 동안 파라미터 공간에서 움직인 경로]
theta_path_bgd = np.array(theta_path_bgd)
theta_path_sgd = np.array(theta_path_sgd)
theta_path_mgd = np.array(theta_path_mgd)
plt.figure(figsize=(7,4))
plt.plot(theta_path_sgd[:, 0], theta_path_sgd[:, 1], "r-s", linewidth=1, label="Stochastic")
plt.plot(theta_path_mgd[:, 0], theta_path_mgd[:, 1], "g-+", linewidth=2, label="Mini-batch")
plt.plot(theta_path_bgd[:, 0], theta_path_bgd[:, 1], "b-o", linewidth=3, label="Batch")
plt.legend(loc="upper left", fontsize=16)
plt.xlabel(r"$\theta_0$", fontsize=20)
plt.ylabel(r"$\theta_1$ ", fontsize=20, rotation=0)
plt.axis([2.5, 4.5, 2.3, 3.9])
plt.show()

3. 다항 회귀(Polynomial Regression)& 학습 곡선
비선형 데이터를 학습하는 데 선형 모델을 사용 가능
How? 각 특성의 거듭제곱을 새로운 특성으로 추가 → 확장된 특성을 포함한 데이터셋에 선형 모델 훈련
이러한 방법이 다항 회귀(Polynomial Regression)
특성이 여러 개일 때 다항 회귀는 이 특성 사이의 관계를 찾을 수 있음(일반적인 선형 모델에서는 불가)
why? PolynomialFeatures가 주어진 차수까지 특성 간의 모든 교차항을 추가하기 때문
IF 두 개의 특성 a, b가 존재 && degree= 3 && PolynomialFeatures 적용
$$ THEN features = [a^{2}, a^{3}, b^{2}, b^{3}, ab, a^{2}b, ab^{2}] $$
[주의]
PolynomialFeatures(degree=d) 특성이 n개인 배열을 특성이 ①개인 배열로 변환. 특성 수가 교차항을 포함하여 엄청나게 늘어날 수 있음[n!(팩토리얼)이기 때문에]
[① 공식]
$$ \frac{(n+d)!}{d!n!} $$
[① 공식을 중복을 허락한 조합의 공식으로 표현]
$$ \frac{(n+d)!}{d!n!}=\binom{n+d}{d} = \binom{n+1=d-1}{d} = \left (\binom{n+1}{d}\right ) $$
[조합의 공식을 점화식으로]
$$ \left (\binom{n+1}{d}\right )=\left ( \binom{n}{d} \right ) = \left ( \binom{n+1}{d-1} \right ) $$
[두 번째 항에 점화식을 계속하여 적용]
$$ \frac{(n+d)!}{d!n!}=\left (\binom{n+1}{d}\right)=\left(\binom{n}{d}\right )+\left(\binom{n}{d-1}\right ) + \left(\binom{n}{d-2}\right )+...+\left(\binom{n}{1}\right ) + \left(\binom{n+1}{0}\right ) $$
$$ \therefore \frac{(n+d)!}{n!d!}는 n개의 특성에서 0부터 d까지 뽑을 수 있는 중복 조합의 합이 됨 $$
[다항 회귀 모델 예측 예시 코드]
- 아래 코드에서 실제 원래 함수
$$ y = 0.5x_1^{2}+1.0x_{1}+2.0+ GausianNoise $$
- 예측된 모델
$$ y = 0.56x_1^{2}+0.93x_{1}+1.78 $$
- PolynomialFeatures를 사용해 훈련 데이터를 변환
- 훈련 세트에 있는 각 특성을 제곱(2차 다항)하여 새로운 특성으로 추가
- X_poly = 원래 특성 X와 이 특성의 제곱을 포함
- 확장된 훈련 데이터에 Linear Regression 적용
import numpy as np
import numpy.random as rnd
np.random.seed(42)
m = 100
X = 6 * np.random.rand(m, 1) - 3
y = 0.5 * X**2 + X + 2 + np.random.randn(m, 1)
# 잡음이 포함된 비선형 데이터셋 시각화
plt.plot(X, y, "b.")
plt.xlabel("$x_1$", fontsize=18)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.axis([-3, 3, 0, 10])
plt.show()
from sklearn.preprocessing import PolynomialFeatures
poly_features = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly_features.fit_transform(X)
X[0] # result : array([-0.75275929])
X_poly[0] # result : array([-0.75275929, 0.56664654])
lin_reg = LinearRegression()
lin_reg.fit(X_poly, y)
lin_reg.intercept_, lin_reg.coef_ # result : (array([1.78134581]), array([[0.93366893, 0.56456263]]))
# 다항 회귀 모델의 예측 시각화
X_new=np.linspace(-3, 3, 100).reshape(100, 1)
X_new_poly = poly_features.transform(X_new)
y_new = lin_reg.predict(X_new_poly)
plt.plot(X, y, "b.")
plt.plot(X_new, y_new, "r-", linewidth=2, label="Predictions")
plt.xlabel("$x_1$", fontsize=18)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.legend(loc="upper left", fontsize=14)
plt.axis([-3, 3, 0, 10])
plt.show()


3-2 학습 곡선
고차 다항 회귀를 적용하면 보통의 선형 회귀에서보다 훨씬 더 훈련 데이터에 잘 맞추려 할 것
얼마나 복잡한 모델을 사용할지, 어떻게 모델이 데이터에 과대/과소 적합되었는지 알까?
[방법 1 : 교차 검증]
[2장]에서는 모델의 일반화 성능을 추정하기 위해 교차 검증을 사용
- 훈련 데이터에서 성능이 좋지만 교차 검증 점수가 낮다? 모델이 과대 적합
- 모두 낮다? 과소적합
[방법 2 : 학습 곡선]
학습곡선은 훈련 세트와 검증 세트의 모델 성능을 훈련 세트 크기(| 훈련 반복)의 함수로 나타냄
How To make this graph? 훈련 세트에서 크기가 다른 서브 세트를 만들어 모델을 여러번 훈련 시키기
[훈련 데이터에서 모델의 학습 곡선을 그리는 함수]
- 학습 곡선을 그리는 함수
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
def plot_learning_curves(model, X, y):
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=10)
train_errors, val_errors = [], []
for m in range(1, len(X_train) + 1):
model.fit(X_train[:m], y_train[:m])
y_train_predict = model.predict(X_train[:m])
y_val_predict = model.predict(X_val)
train_errors.append(mean_squared_error(y_train[:m], y_train_predict))
val_errors.append(mean_squared_error(y_val, y_val_predict))
plt.plot(np.sqrt(train_errors), "r-+", linewidth=2, label="train")
plt.plot(np.sqrt(val_errors), "b-", linewidth=3, label="val")
plt.legend(loc="upper right", fontsize=14)
plt.xlabel("Training set size", fontsize=14)
plt.ylabel("RMSE", fontsize=14)
- 시각화
lin_reg = LinearRegression()
plot_learning_curves(lin_reg, X, y)
plt.axis([0, 80, 0, 3])
plt.show()
※ 과소적합 모델의 전형적인 모습
- 훈련 데이터의 성능
: 훈련 세트에 샘플이 추가됨에 따라 잡음 + 비선형이기에 모델이 훈련 데이터를 완벽히 학습하는 게 불가능해짐
- 검증 데이터의 성능
: 적은 수의 훈련 샘플로 훈련될때는 일반화되기 어려워 검증 오차가 초기에 매우 큼
모델에 훈련 샘플이 추가됨에 따라 학습이 되고 검증 오차가 천천히 감소
But 선형 회귀의 직선은 데이터를 잘 모델링 불가함 So, 오차의 감소가 완만해져 훈련 세트의 그래프와 가까워짐

- 10차 다항 회귀 모델의 학습 곡선
from sklearn.pipeline import Pipeline
polynomial_regression = Pipeline([
("poly_features", PolynomialFeatures(degree=10, include_bias=False)),
("lin_reg", LinearRegression()),
])
plot_learning_curves(polynomial_regression, X, y)
plt.axis([0, 80, 0, 3])
plt.show()

두 곡선 그래프의 차이점
- 훈련 데이터의 오차가 선형 회귀 모델보다 훨씬 낮음
- 두 곡선 사이의 공간이 존재(= 훈련 데이터에서의 모델 성능이 검증 데이터에서보다 훨씬 낮다 = 과대적합)
But 더 큰 훈련 세트를 사용하면 두 곡선이 점점 가까워짐
※ 과소적합된 첫 번째 모델은 훈련 샘플을 추가해도 효과가 없음 So, 더 복잡한 모델 | 더 나은 특성 선택하기
4. 규제가 있는 선형 모델
규제 : 과대 적합을 줄이는 효과적인 방법, 자유도를 줄이면 과대적합되기 어려워짐
- 다항 회귀 모델을 규제하는 방법? 다항식 차수 감소
- 선형 회귀 모델을 규제하는 방법? 모델의 가중치 제한
모델의 가중치를 제한하는 방법 : 릿지/라쏘 회귀, 엘라스틱넷
4-1 릿지 회귀(Ridge; 티호노프 규제: Tikhonov)
- 규제가 추가된 선형 회귀 버전
- 규제항이 비용 함수에 추가됨
- 규제항은 훈련하는 동안에만 비용 함수에 추가되고 훈련이 끝나면 모델의 성능을 규제가 없는 성능 지표로 평가
- 규제항
$$ \alpha\sum_{i=1}^{n}\theta_{i}^{2} $$
- 하이퍼파라미터 ∝는 모델을 얼마나 규제할지 조절 IF ∝ = 0: 릿지회귀 == 선형 회귀
ElseIf ∝ 가 아주 큼 : 모든 가중치가 거의 0에 근접 & 데이터의 평균을 지나는 수평선이 됨
- 릿지 회귀의 비용 함수
$$ J(\theta) = MSE(\theta)+ \alpha\frac{1}{2}\sum_{i=1}^{n}\theta_{i}^{2} $$
편향 theta_0은 규제 되지 않음 (Sigma i=0이 아니라 i= 1에서 시작)
w를 특성의 가중치 벡터(theta_1 ~ theta_n)라고 하면 규제항은 아래와 같음
$$ \frac{1}{2}(\left\|w \right\|_{2})^{2} $$
여기서 ||·||_2 가 가중치 벡터의 L2 거리(노름).
경사 하강법에 적용하려면 MSE 그레디언트 벡터에 aw를 더하면 됨(공식)
일반적으로 훈련하는 동안 사용되는 비용 함수와 테스트에서 사용되는 성능 지표는 다름
규제를 떠나 이들이 다른 이유는 훈련에 사용되는 비용 함수는 최적화를 위해 미분 가능해야 하기 때문
반면 테스트에 사용되는 성능 지표는 최종 목표에 가능한 한 가까워야 함.
로그 손실과 같은 비용 함수를 사용해 훈련시킨 분류기를 정밀도/재현율을 사용해 평가하는 것이 좋은 예
선형 데이터에 몇 가진 다른 ∝ 를 사용해 릿지 모델을 훈련시킨 결과
[릿지 회귀의 정규 방정식]
$$ \hat{\theta}=(X^{T}X+\alpha A)^{-1}X^{T}y $$
[python 코드]
from sklearn.linear_model import Ridge
# 정규방정식을 사용한 릿지 회귀 적용
ridge_reg = Ridge(alpha=1, solver="cholesky", random_state=42)
ridge_reg.fit(X, y)
ridge_reg.predict([[1.5]])
# SGD 사용방법
ridge_reg = Ridge(alpha=1, solver="sag", random_state=42)
ridge_reg.fit(X, y)
ridge_reg.predict([[1.5]])
# 동일한 방법
# sgd_reg = SGDRegressor(penalty="12")
# sgd_reg.fit(X, y.ravel())
# sgd_reg.predict([[1.5]])
# 시각화
from sklearn.linear_model import Ridge
def plot_model(model_class, polynomial, alphas, **model_kargs):
for alpha, style in zip(alphas, ("b-", "g--", "r:")):
model = model_class(alpha, **model_kargs) if alpha > 0 else LinearRegression()
if polynomial:
model = Pipeline([
("poly_features", PolynomialFeatures(degree=10, include_bias=False)),
("std_scaler", StandardScaler()),
("regul_reg", model),
])
model.fit(X, y)
y_new_regul = model.predict(X_new)
lw = 2 if alpha > 0 else 1
plt.plot(X_new, y_new_regul, style, linewidth=lw, label=r"$\alpha = {}$".format(alpha))
plt.plot(X, y, "b.", linewidth=3)
plt.legend(loc="upper left", fontsize=15)
plt.xlabel("$x_1$", fontsize=18)
plt.axis([0, 3, 0, 4])
plt.figure(figsize=(8,4))
plt.subplot(121)
plot_model(Ridge, polynomial=False, alphas=(0, 10, 100), random_state=42)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.subplot(122)
plot_model(Ridge, polynomial=True, alphas=(0, 10**-5, 1), random_state=42)
plt.show()
- 왼쪽 그래프는 평범한 릿지모델을 사용한 선형적 예측
- 오른쪽은 PolynomialFeatures(degree=10)을 사용해 데이터 확장 후 StandardScaler를 사용해 스케일 조정 후 적용
(= 릿지 규제를 적용한 다항회귀)
- ∝ 를 증가 시킬수록 직선에 가까워짐(즉, 모델의 분산은 줄지만 편향은 커짐)
4-2 라쏘(Lasso;Least Absolute Shrinkage and Selection Operator) 회귀
- 릿지처럼 비용 함수에 규제항을 더하지만, L2 노름의 제곱을 2로 나눈 것 대신 가중치 벡터 L1 노름을 사용
- 덜 중요한 특성의 가중치를 제거하려고 한다(즉 가중치 = 0)
- 자동으로 특성 선택을 하고 희소 모델(sparse model)을 생성
[라쏘 회귀의 비용 함수]
$$ J(\theta)=MSE(\theta)+\alpha\sum_{i=1}^{n}\left| \theta_{i}\right| $$
[그림으로 이해하기]
○ 두 축 : 모델 파라미터 두 개
○ 배경의 등고선 : 각기 다른 손실 함수
● 왼쪽 위 등고선 : L1 손실 ( |theta_1| + | theta_2| ), 축에 가까워지면서 선형적으로 감소
- e.g. theta_1 = 2, theta_2 = 0.5로 초기화 후 경사 하강법을 실행하면?
- 두 파라미터가 동일하게 감소(노란 점선) → So, theta_2가 먼저 0에 도달
● 오른쪽 위 그래프의 등고선 : 라쏘 손실 함수(= L1손실을 더한 MSE 손실 함수)
- 하얀 작은원 : 경사 하강법이 theta_1=0.25, theta_2=-1로 초기화된 모델 파라미터를 최적화하는 과정을 보여줌
- 여기서도 theta_2가 빠르게 줄고 축을 따라 진동하면서 전역 최적점(빨간 사각형)에 도달
- IF ∝ 증가 : 전역 최적점이 노란 점선을 따라 왼쪽으로 이동
- Elif ∝ 감소 :전역 최적점이 오른쪽으로 이동
○ 아래 사진의 예에서 MSE의 최적 파라미터
● theta_1 : 2
● theta_2 : 0.5
아래 두 개의 그래프도 동일하지만 L2 규제를 사용
● 왼쪽 아래 그래프 : L2 손실은 원점에 가까울 수록 줄어듬 So, 경사하강법이 원점까지 직선 경로
● 오른쪽 아래 그래프 : 릿지 회귀의 비용 함수(= L2 손실을 더한 MSE 손실 함수)
● 릿지와 라쏘와 다른점
- 파라미터가 전역 최적점에 가까워질 수록 그레디언트가 작아짐
→ 경사 하강법이 자동으로 느려지고 수렴에 도움이 됨(진동이 없음)
- ∝ 를 증가시킬수록 최적의 파라미터(빨간 사각형)가 원점에 가까워짐(But 완전한 0은 안됨)
[위 그림 코드]
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
t1a, t1b, t2a, t2b = -1, 3, -1.5, 1.5
t1s = np.linspace(t1a, t1b, 500)
t2s = np.linspace(t2a, t2b, 500)
t1, t2 = np.meshgrid(t1s, t2s)
T = np.c_[t1.ravel(), t2.ravel()]
Xr = np.array([[1, 1], [1, -1], [1, 0.5]])
yr = 2 * Xr[:, :1] + 0.5 * Xr[:, 1:]
J = (1/len(Xr) * np.sum((T.dot(Xr.T) - yr.T)**2, axis=1)).reshape(t1.shape)
N1 = np.linalg.norm(T, ord=1, axis=1).reshape(t1.shape)
N2 = np.linalg.norm(T, ord=2, axis=1).reshape(t1.shape)
t_min_idx = np.unravel_index(np.argmin(J), J.shape)
t1_min, t2_min = t1[t_min_idx], t2[t_min_idx]
t_init = np.array([[0.25], [-1]])
def bgd_path(theta, X, y, l1, l2, core = 1, eta = 0.05, n_iterations = 200):
path = [theta]
for iteration in range(n_iterations):
gradients = core * 2/len(X) * X.T.dot(X.dot(theta) - y) + l1 * np.sign(theta) + l2 * theta
theta = theta - eta * gradients
path.append(theta)
return np.array(path)
fig, axes = plt.subplots(2, 2, sharex=True, sharey=True, figsize=(10.1, 8))
for i, N, l1, l2, title in ((0, N1, 2., 0, "Lasso"), (1, N2, 0, 2., "Ridge")):
JR = J + l1 * N1 + l2 * 0.5 * N2**2
tr_min_idx = np.unravel_index(np.argmin(JR), JR.shape)
t1r_min, t2r_min = t1[tr_min_idx], t2[tr_min_idx]
levelsJ=(np.exp(np.linspace(0, 1, 20)) - 1) * (np.max(J) - np.min(J)) + np.min(J)
levelsJR=(np.exp(np.linspace(0, 1, 20)) - 1) * (np.max(JR) - np.min(JR)) + np.min(JR)
levelsN=np.linspace(0, np.max(N), 10)
path_J = bgd_path(t_init, Xr, yr, l1=0, l2=0)
path_JR = bgd_path(t_init, Xr, yr, l1, l2)
path_N = bgd_path(np.array([[2.0], [0.5]]), Xr, yr, np.sign(l1)/3, np.sign(l2), core=0)
ax = axes[i, 0]
ax.grid(True)
ax.axhline(y=0, color='k')
ax.axvline(x=0, color='k')
ax.contourf(t1, t2, N / 2., levels=levelsN)
ax.plot(path_N[:, 0], path_N[:, 1], "y--")
ax.plot(0, 0, "ys")
ax.plot(t1_min, t2_min, "ys")
ax.set_title(r"$\ell_{}$ penalty".format(i + 1), fontsize=16)
ax.axis([t1a, t1b, t2a, t2b])
if i == 1:
ax.set_xlabel(r"$\theta_1$", fontsize=16)
ax.set_ylabel(r"$\theta_2$", fontsize=16, rotation=0)
ax = axes[i, 1]
ax.grid(True)
ax.axhline(y=0, color='k')
ax.axvline(x=0, color='k')
ax.contourf(t1, t2, JR, levels=levelsJR, alpha=0.9)
ax.plot(path_JR[:, 0], path_JR[:, 1], "w-o")
ax.plot(path_N[:, 0], path_N[:, 1], "y--")
ax.plot(0, 0, "ys")
ax.plot(t1_min, t2_min, "ys")
ax.plot(t1r_min, t2r_min, "rs")
ax.set_title(title, fontsize=16)
ax.axis([t1a, t1b, t2a, t2b])
if i == 1:
ax.set_xlabel(r"$\theta_1$", fontsize=16)
plt.show()
라쏘의 비용 함수는 theta_i = 0(i=1, 2, ... , n일때)에서 미분 가능하지 않지만, theta_i = 0 일때, 서브그레디언트 벡터 g 를 사용하면 경사하강법을 적용하는 데 문제가 없음
※ 서브그레디언트 벡터 : 미분이 불가능한 지점 금방 그레디언트들의 중간값으로 생각 가능
※ L1 노름의 절댓값 함수 |X|는 원점을 꼭짓점으로 하는 V자형 그래프로 원점에서 미분이 불가
[라쏘 회귀의 서브그레디언트 벡터]
$$ g(\theta, J)= \triangledown_{\theta}MSE(\theta) + \alpha\begin{pmatrix} sign(\theta_{1})\\ sign(\theta_{2})\\...\\sign(\theta_{n})\end{pmatrix} $$
$$ 여기서 sign(\theta_{i})= \begin{cases}-1 & \theta_{i}<0\\0 & \theta_{i}=0\\ 1 & \theta_{i}>0 \end{cases} $$
[Lasso 클래스를 사용한 사이킷런 예제]
from sklearn.linear_model import Lasso
# Lasso 대신 SGDRegressor(penalthy="l1") 을 써도 됨
lasso_reg = Lasso(alpha=0.1)
lasso_reg.fit(X, y)
lasso_reg.predict([[1.5]]) # array([1.53788174])
4-3 엘라스틱넷(Elastic Net)
: 릿지와 라쏘를 절충한 모델
○ 규제항 : 릿지와 회귀의 규제항을 단순히 더해서 사용
○ 혼합 정도 : 혼합 비율 r을 사용해 조절
○ IF r = 0 : 엘라스틱 넷 = 릿지회귀, Elif r = 1: 엘라스틱넷 = 라쏘 회귀
○ 적어도 규제가 약간 있는 것이 대부분 좋음 So, 평범한 선형 회귀는 피하는 것이 좋음
○ 릿지가 기본이 되지만 쓰이는 특성이 몇 개뿐이라고 의심되면 라쏘 | 엘라스틱넷이 더 좋다.
→ 불필요한 특성의 가중치를 0으로 만들어 줌
○ 특성 수가 훈련 샘플 수보다 많거나 특성 몇 개가 강하게 연관되어 있을때는 보통 라쏘가 문제를 일으킴
Som 라쏘보다는 엘라스틱넷을 선호하는 편
라쏘는 특성 수가 샘플 수(n)보다 많으면 최대 n개의 특성을 선택함
또한, 여러 특성이 강하게 연관되어 있으면 이들 중 임의의 특성 하나를 선택함
[엘라스틱넷 비용 함수]
$$ J(\theta) = MSE(\theta)+r\alpha \sum_{i=1}^{n}\left| \theta_{i} \right| + \frac{1-r}{2} \alpha \sum_{i=1}^{n}\theta_{i}^{2} $$
[엘라스틱넷을 사용한 간단한 예제]
※ l1_ratio = 혼합비율(r)
from sklearn.linear_model import ElasticNet
elastic_net = ElasticNet(alpha=0.1, l1_ratio=0.5, random_state=42)
elastic_net.fit(X, y)
elastic_net.predict([[1.5]]) # array([1.54333232])
4-4 조기 종료
: 검증 에러가 최솟값에 도달하면 바로 훈련을 중지시키는 것
○ 아래 사진은 배치 경사 하강법으로 훈련시킨 복잡한 모델(고차원 다항 회귀 모델)을 보여줌
- 에포크가 진행됨에 따라 알고리즘이 점차 학습 → 훈련 세트에 대한 예측 에러(RMSE)와 검증 세트에 대한 예측 에러가 감소 → 감소하던 검증 에러가 멈췄다가 다시 상승(= 모델이 훈련 데이터에 과대적합되기 시작)
[조기 종료를 위한 기본 구현 코드]
- warm_start = True로 지정하면 fit() 메서드가 호출될 때 처음부터 다시 시작하지 않고 이전 모델 파라미터에서 훈련을 이어감
np.random.seed(42)
m = 100
X = 6 * np.random.rand(m, 1) - 3
y = 2 + X + 0.5 * X**2 + np.random.randn(m, 1)
X_train, X_val, y_train, y_val = train_test_split(X[:50], y[:50].ravel(), test_size=0.5, random_state=10)
from copy import deepcopy
poly_scaler = Pipeline([
("poly_features", PolynomialFeatures(degree=90, include_bias=False)),
("std_scaler", StandardScaler())
])
X_train_poly_scaled = poly_scaler.fit_transform(X_train)
X_val_poly_scaled = poly_scaler.transform(X_val)
sgd_reg = SGDRegressor(max_iter=1, tol=None, warm_start=True,
penalty=None, learning_rate="constant", eta0=0.0005, random_state=42)
minimum_val_error = float("inf")
best_epoch = None
best_model = None
for epoch in range(1000):
sgd_reg.fit(X_train_poly_scaled, y_train) # 중지된 곳에서 다시 시작합니다
y_val_predict = sgd_reg.predict(X_val_poly_scaled)
val_error = mean_squared_error(y_val, y_val_predict)
if val_error < minimum_val_error:
minimum_val_error = val_error
best_epoch = epoch
best_model = deepcopy(sgd_reg)
- 시각화
sgd_reg = SGDRegressor(max_iter=1, tol=None, warm_start=True, penalty=None, learning_rate="constant", eta0=0.0005, random_state=42)
n_epochs = 500
train_errors, val_errors = [], []
for epoch in range(n_epochs):
sgd_reg.fit(X_train_poly_scaled, y_train)
y_train_predict = sgd_reg.predict(X_train_poly_scaled)
y_val_predict = sgd_reg.predict(X_val_poly_scaled)
train_errors.append(mean_squared_error(y_train, y_train_predict))
val_errors.append(mean_squared_error(y_val, y_val_predict))
best_epoch = np.argmin(val_errors)
best_val_rmse = np.sqrt(val_errors[best_epoch])
plt.annotate('Best model',
xy=(best_epoch, best_val_rmse),
xytext=(best_epoch, best_val_rmse + 1),
ha="center",
arrowprops=dict(facecolor='black', shrink=0.05),
fontsize=16,
)
best_val_rmse -= 0.03 # just to make the graph look better
plt.plot([0, n_epochs], [best_val_rmse, best_val_rmse], "k:", linewidth=2)
plt.plot(np.sqrt(val_errors), "b-", linewidth=3, label="Validation set")
plt.plot(np.sqrt(train_errors), "r--", linewidth=2, label="Training set")
plt.legend(loc="upper right", fontsize=14)
plt.xlabel("Epoch", fontsize=14)
plt.ylabel("RMSE", fontsize=14)
plt.show()
5. 로지스틱 회귀(Logistic Regression; Logit Regression)
: 샘플이 특정 클래스에 속할 확률을 추정하는데 널리 사용
- 추정 확률이 50%가 넘으면 모델은 그 샘플이 해당 클래스에 속한다고 예측(레이블이 '1'인 양성 클래스)
- 아니라면 클래스에 속하지 않는다고 예측(레이블이 '0'인 음성 클래스)
=> 이진 분류기
5-1 확률 추정
: 선형회귀 모델과 같이 입력 특성의 가중치 합을 계산(+ 편향)
But 결과를 바로 출력하지 않고 결과값의 Logistic을 출력
※ logistic을 sigma(·)로 표시 0 ~ 1 사이의 값을 출력하는 시그모이드 함수
[로지스틱 회귀 모델의 확률 추정(벡터 표현식)]
$$ \hat{p} = h_{\theta}(x) = \sigma(\theta^{T}x) $$
[로지스틱 함수]
$$ \sigma(t)= \frac{1}{1+exp(-t)} $$
[로지스틱 회귀 모델이 샘플 x가 양성 클래스에 속할확률]
$$ \hat{p}=h_{\theta}(x) $$
이에 대한 예측 \hat{y}
[로지스틱 회귀 모델 예측]
$$ \hat{y} = \begin{cases} 0 & \hat{p}<0.5 \\ 1 & \hat{p}\geq 0.5 \end{cases} $$
t < 0 이면 시그마(t) < 0.5 이고, t ≥ 0 이면 시그마(t) ≥ 0.5 이므로 로지스틱 회귀모델은 아래와 같이 예측
$$ \begin{cases}1 & \theta^{T}x > 0\\ 0 & \theta^{T}x < 0 \end{cases} $$
t를 종종 로짓(logit)이라고 부름.
logit(p) = log(p / (1-p)) 로 정의되는 로짓 함수가 로지스틱 함수의 역함수라는 사실에서 따옴
실제로 추정 확률 p의 로짓을 계산하면 t값을 얻을 수 있음
로짓을 로그-오즈 라고도 부름
로그-오즈는 양성 클래스 추정 확률과 음성 클래스 추정 확률 사이의 로그 비율이기 때문
5-2 훈련과 비용 함수
○ 훈련 목적 : 양성 샘플(y = 1)에 대해서는 높은 확률을 추정하고 음성 샘플(y = 0)에 대해서는 낮은 확률을 추정하는 모델 파라미터 벡터 theta를 찾는 것
[하나의 훈련 샘플에 대한 비용 함수]
$$ c(\theta) = \begin{cases} -log(\hat{p}) & y = 1 \\ -log(1- \hat{p}) & y = 0 \end{cases} $$
전체 훈련 세트에 대한 비용 함수 : 모든 훈련 샘플의 비용을 평균한 것(= log loss)
[로지스틱 회귀의 비용 함수(로그 손실)]
※ 이 비용 함수의 최솟값을 계산하는 알려진 해는 없음
※ 이 비용 함수는 볼록 함수이므로 경사 하강법이 전역 최솟값을 찾는 것을 보장
$$ J(\theta) = -\frac{1}{m}\sum_{i=1}^{m}\begin{bmatrix}y^{(i)}log(\hat{p}^{(i)})+(1 - y^{(i)})log(1-\hat{p}^{(i)}) \end{bmatrix} $$
위의 로그 손실의 j 번째 모델 파라미터 theta_j 에 대해 편미분을 하면 아래와 같다.
[로그 손실을 편미분한 식]
※ 아래 식은 비용 함수의 편도 함수와 유사해 보임
$$ \frac{\partial}{\partial\theta_{j}}J(\theta)= \frac{1}{m}\sum_{i=1}^{m}(\sigma(\theta^{T}x^{(i)})-y^{(i)})x_j^{(i)} $$
○ 각 샘플에 대해 예측 오차를 계산하고 j 번째 특성값을 곱해 모든 훈련 샘플에 대해 평균을 구함
○ 모든 편도 함수를 포함한 그레디언트 벡터를 만들면 배치 경사 하강법을 사용 가능
5-3 결정 경계
○ 데이터셋 : 붓꽃 데이터셋
※ 붓꽃 데이터셋
: Iris-Setosa, Irs-Versicolor, Iris-Virginica에 속하는 붓꽃 150개의 꽃잎(petal)과 꽃받침(sepal)의 너비 길이를 포함
[Python 코드]
- 데이터 로드
from sklearn import datasets
iris = datasets.load_iris()
list(iris.keys())
print(iris.DESCR)

X = iris["data"][:, 3:] # 꽃잎 너비
y = (iris["target"] == 2).astype(int) # Iris virginica이면 1 아니면 0
- 모델 훈련
from sklearn.linear_model import LogisticRegression
log_reg = LogisticRegression(solver="lbfgs", random_state=42)
log_reg.fit(X, y)
- 꽃잎의 너비가 0 ~ 3 cm인 꽃에 대해 모델의 추정 확률 계산
X_new = np.linspace(0, 3, 1000).reshape(-1, 1)
y_proba = log_reg.predict_proba(X_new)
decision_boundary = X_new[y_proba[:, 1] >= 0.5][0]
plt.figure(figsize=(8, 3))
plt.plot(X[y==0], y[y==0], "bs")
plt.plot(X[y==1], y[y==1], "g^")
plt.plot([decision_boundary, decision_boundary], [-1, 2], "k:", linewidth=2)
plt.plot(X_new, y_proba[:, 1], "g-", linewidth=2, label="Iris virginica")
plt.plot(X_new, y_proba[:, 0], "b--", linewidth=2, label="Not Iris virginica")
plt.text(decision_boundary+0.02, 0.15, "Decision boundary", fontsize=14, color="k", ha="center")
plt.arrow(decision_boundary[0], 0.08, -0.3, 0, head_width=0.05, head_length=0.1, fc='b', ec='b')
plt.arrow(decision_boundary[0], 0.92, 0.3, 0, head_width=0.05, head_length=0.1, fc='g', ec='g')
plt.xlabel("Petal width (cm)", fontsize=14)
plt.ylabel("Probability", fontsize=14)
plt.legend(loc="center left", fontsize=14)
plt.axis([0, 3, -0.02, 1.02])
plt.show()

- 결과 분석
○ Iris-Veginica(삼각형)의 꽃잎 너비 : 1.4 ~ 2.5 cm에 분포
○ 다른 꽃(사각형)의 꽃잎 너비 : 0.1 ~ 1.8cm에 분포
So, 해당 Logistic 모델은
IF 꽃잎 너비가 2cm 이상인 꽃 : Iris-Verginica라고 예측
Elif 1cm 아래 : Iris-Verginica가 아니라고 예측
여기서 양쪽 확률이 똑같이 50%가 되는 1.6 cm 근방에서 결정 결계(decision boundary)가 생성
○ 꽃잎 너비가 1.6cm보다 크면 분류기는 Iris-Verginica로 분류
○ 그보다 작으면 아니라고 예측
decision_boundary # array([1.66066066])
log_reg.predict([[1.7], [1.5]]) # array([1, 0])
아래 그림은 같은 데이터셋을 꽃잎의 너비, 꽃잎 길이 두 개의 특성으로 보여줌
○ 점선 : 모델이 50% 확률을 추정하는 지점으로, 모델의 결정 경계(경계는 선형)
○ 15%(왼쪽 아래)부터 90%(오른쪽 위)까지 나란한 직선들은 모델이 특정 확률을 출력하는 포인트
○ 모델은 맨 오른쪽 위의 직선을 넘어서 있는 꽃들을 90% 이상 확률로 Iris-Virginica라 판단할 것
○ 다른 선형 모델과 같이 로지스틱 회귀 모델도 l1, l2 페널티를 사용해 규제 가능
※ 사이킷런은 l2 페널티가 default
from sklearn.linear_model import LogisticRegression
X = iris["data"][:, (2, 3)] # petal length, petal width
y = (iris["target"] == 2).astype(int)
log_reg = LogisticRegression(solver="lbfgs", C=10**10, random_state=42)
log_reg.fit(X, y)
x0, x1 = np.meshgrid(
np.linspace(2.9, 7, 500).reshape(-1, 1),
np.linspace(0.8, 2.7, 200).reshape(-1, 1),
)
X_new = np.c_[x0.ravel(), x1.ravel()]
y_proba = log_reg.predict_proba(X_new)
plt.figure(figsize=(10, 4))
plt.plot(X[y==0, 0], X[y==0, 1], "bs")
plt.plot(X[y==1, 0], X[y==1, 1], "g^")
zz = y_proba[:, 1].reshape(x0.shape)
contour = plt.contour(x0, x1, zz, cmap=plt.cm.brg)
left_right = np.array([2.9, 7])
boundary = -(log_reg.coef_[0][0] * left_right + log_reg.intercept_[0]) / log_reg.coef_[0][1]
plt.clabel(contour, inline=1, fontsize=12)
plt.plot(left_right, boundary, "k--", linewidth=3)
plt.text(3.5, 1.5, "Not Iris virginica", fontsize=14, color="b", ha="center")
plt.text(6.5, 2.3, "Iris virginica", fontsize=14, color="g", ha="center")
plt.xlabel("Petal length", fontsize=14)
plt.ylabel("Petal width", fontsize=14)
plt.axis([2.9, 7, 0.8, 2.7])
plt.show()

5-4 소프트맥스 회귀(Softmax Regression)
: 로지스틱 회귀 모델은 여러 개의 이진 분류기를 훈련시켜 연결하지 않고 직접 다중 클래스를 지원하도록 일반화 가능한데, 이를 소프트맥스 회귀(Multinomial Logistic Regression:다항 로지스틱 회귀)라고 함
개념 : 샘플 x가 주어지면 소프트맥스 회귀 모델이 각 클래스 k에 대한 점수 s_k(x)를 계산하고 그 점수에 소프트맥스 함수(| 정규화된 지수 함수)를 적용해 각 클래스의 확률을 추정
[클래스 k에 대한 소프트맥스 점수]
$$ s_k(x) = (\theta^{(k)})^{T}x $$
각 클래스마다 자신만의 파라미터 벡터 theta^(k)가 있음 이 벡터들은 파라미터 행렬에 행으로 저장
※ LogisticRegression 모델의 coef_ 속성은 (클래스 수, 특성 수) 크기인 2차원 배열.
intercept_ 속성은 클래스 수와 크기가 같은 1차원 배열
샘플 x에 대해 각 클래스의 점수 계산되면 소프트맥스 함수를 통과시켜 클래스 k에 속할 확률(hat{p}_{k})를 추정 가능
해당 함수는 지수 함수를 적용한 후 정규화함(모든 지수 함수 결과의 합으로 나눔)
[소프트맥스 함수]
$$ \hat{p}_{k} = \sigma(s(x))_{k} = \frac{exp(s_{k}(x))}{\sum_{j=1}^{k}exp(s_{j}(x))} $$
○ k = 클래스 수
○ s(x) = 샘플 x에 대한 각 클래스의 점수를 담은 벡터
○ 시그마(s(x))_{k} = 샘플 x에 대한 각 클래스의 점수가 주어졌을 때 이 샘플이 클래스 k에 속할 추정 확률
로지스틱 회귀분류기와 마찬가지로 소프트맥스 회귀 분류기는 아래와 같이 추정 확률이 가장 높은 클래스를 선택
[소프트맥스 회귀 분류기의 예측]
※ argmax 연산 : 함수를 최대화하는 변수의 값을 반환
$$ \hat{y} = \textup(argmax)\sigma(s(x))_{k} = \textup{argmax}s_k(x)=\textup{argmax}((\theta^{(k)})^{T}x) $$
소프트맥스 회귀 분류기는 한 번에 하나의 클래스만 예측(즉, 다중 클래스지 다중 출력은 아님)
종류가 다른 붓꽃 같이 상호 배타적인 클래스에서만 사용해야함
하나의 사진에서 여러 사람의 얼굴을 인식하는데는 사용 불가
- 모델이 타깃 클래스에 대해서는 높은 확률을 추정하도록 해야함
- 크로스 엔트로피(Cross Entropy) 비용 함수를 최소화하는 것은 타깃 클래스에 대해 낮은 확률을 예측하는 모델을 억제함
So, 이 목적에 부합함.
- 크로스 엔트로피는 추정된 클래스의 확률이 타깃 클래스에 얼마나 잘 맞는지 측정하는 용도로 종종 사용됨
[크로스 엔트로피 비용 함수]
J(Theta)에서 안에 있는 Theta = 파라미터 행렬
$$ J(\Theta) = -\frac{1}{m}\sum_{i=1}^{m}\sum_{k=1}^{K}y_{k}^{(i)}log(\hat{p}_{k}^{(i)}) $$
딱 두개의 클래스가 있을 때(K= 2), 비용 함수는 로지스틱 회귀의 비용 함수와 동일함
[이 비용 함수의 theta^(k)에 대한 그레디언트 벡터]
$$ \triangledown_{\theta^{(k)}}J(\Theta) = \frac{1}{m}\sum_{i=1}^{m}(\hat{p}_{k}^{(i)}-y_{k}^{(i)})x^{(i)} $$
[소프트맥스 회귀를 사용해 붓꽃을 세 개의 클래스로 분류]
LogisticRegression는 클래스가 둘 이상일 때 기본적으로 OvA(일대다)전략을 사용
But multi_class 매개변수를 "multinomial"로 바꾸면 소프트맥스 회귀 사용 가능
X = iris["data"][:, (2, 3)] # 꽃잎 길이, 꽃잎 너비
y = iris["target"]
softmax_reg = LogisticRegression(multi_class="multinomial",solver="lbfgs", C=10, random_state=42)
softmax_reg.fit(X, y)
- 시각화
○ 결정 경계를 배경색으로 하여 구분하여 나타냄
○ 클래스 사이의 결정 경계가 모두 선형
○ Iris-Versicolor 클래스에 대한 확률을 곡선으로 나타냄
○ 모든 결정 경계가 만나는 지점에서는 모든 클래스가 동일하게 33%의 추정 확률을 가짐
x0, x1 = np.meshgrid(
np.linspace(0, 8, 500).reshape(-1, 1),
np.linspace(0, 3.5, 200).reshape(-1, 1),
)
X_new = np.c_[x0.ravel(), x1.ravel()]
y_proba = softmax_reg.predict_proba(X_new)
y_predict = softmax_reg.predict(X_new)
zz1 = y_proba[:, 1].reshape(x0.shape)
zz = y_predict.reshape(x0.shape)
plt.figure(figsize=(10, 4))
plt.plot(X[y==2, 0], X[y==2, 1], "g^", label="Iris virginica")
plt.plot(X[y==1, 0], X[y==1, 1], "bs", label="Iris versicolor")
plt.plot(X[y==0, 0], X[y==0, 1], "yo", label="Iris setosa")
from matplotlib.colors import ListedColormap
custom_cmap = ListedColormap(['#fafab0','#9898ff','#a0faa0'])
plt.contourf(x0, x1, zz, cmap=custom_cmap)
contour = plt.contour(x0, x1, zz1, cmap=plt.cm.brg)
plt.clabel(contour, inline=1, fontsize=12)
plt.xlabel("Petal length", fontsize=14)
plt.ylabel("Petal width", fontsize=14)
plt.legend(loc="center left", fontsize=14)
plt.axis([0, 7, 0, 3.5])
plt.show()

[더 deep 하게]
크로스 엔트로피
- 예를 들어 8가지 정보(맑음, 비 등)가 있다면 2^3 = 8이므로 선택 사항을 3비트를 사용해 인코딩 가능
- But 거의 대부분의 날이 맑음이다? '맑음'을 하나의 비트(0)로 인코딩 후 나머지 7개 선택 사항을 (1로 시작하는) 4비트로 표현하는 것이 가능
- 크로스 엔트로피는 선택 사항마다 전송한 평균 비트 수를 측정
- IF 날씨에 대한 가정이 완벽하면 날씨 자체의 엔트로피와 동일할 것(예측 불가능한 고유 성질)
-하지만 가정이 틀렸다면(비가 자주온다면?) 크로스 엔트로피는 쿨백-라이블러 발산(Kullback-Lebler divergence)만큼 양이 커질 것
- 두 확률 분포 p와 q 사이의 크로스 엔트로피는 아래와 같음
[두 확률 분포 p와 q 사이의 크로스 엔트로피]
$$ H(p, q)= -\sum_{x}p(x)logq(x) $$
- 맑음은 1비트, 다른 날씨는 4비트로 전송된다고 하고 맑은 날의 비율이 80%라면 평균 전송 비트수 : 1.6
- 맑은 날의 비율이 50%라면 평균 전송 비트 수는 2.5로 늘어남
- 이 두 엔트로피의 차이 : 이상적인 확률 분포와 이에 근사하는 확률 분포 사이의 차이를 나타내는 쿨백-라이블러 발산
'Networks > Hands-On 머신러닝 정리' 카테고리의 다른 글
Hands-On Machine Learning 정리 - 머신러닝(Chapter 6: 결정 트리) (0) | 2024.10.21 |
---|---|
Hands-On Machine Learning 정리 - 머신러닝(Chapter 5: SVM) (4) | 2024.10.20 |
Hands-On Machine Learning 정리 - 머신러닝(Chapter 3: 분류) (0) | 2024.10.19 |
Hands-On Machine Learning 정리 - 머신러닝(Chapter 2: 프로젝트) (3) | 2024.10.18 |
Hands-On Machine Learning 정리 - 머신러닝(Chapter 1: 한눈에 보는 머신러닝) (6) | 2024.10.18 |