[이 글은 "Do it 딥러닝 입문" 책을 보고 공부한 내용을 복습하고자 정리한 글입니다.]
목표 - 과대적합, 과소적합을 이해하고 L1규제와 L2규제에 대해 이해한다.
[배운 점]
과대적합
- 모델이 훈련 세트에서는 좋은 성능을 내지만 검증 세트에서는 낮은 성능을 내는 경우.
- 정확도/Epochs 그래프를 그려보면 측정한 성능의 간격이 크다. 이를 '분산이 크다'라고도 말한다.
- 훈련세트의 다양성이 없어서 나오는 경우, 훈련 세트를 더 모으지 못한다면, 모델이 훈련세트에 집착하지 않도록 가중치 제한을 둘 수 있다. 이를 '모델의 복잡도를 낮춘다'라고 말한다.
과소적합
- 모델이 훈련세트와 검증세트에서 많은 성능차이를 보이진 않지만 둘 다 성능이 저조한 경우.
- 정확도/Epochs 그래프에서 성능 간격은 적지만 성능이 낮다. 이를'편향이 크다'라고 말한다.
- 해결방법은 복잡도를 더 높은 모델을 사용하거나 가중치의 규제를 완화하는 것이다.
*모델 복잡도 : 모델이 가진 학습 가능한 가중치 개수를 말함.
적절한 편향-분산 트레이드오프를 선택
-과대적합, 과소 적합 사이에서의 절충점을 찾는다.
모델의 일반화
- 모델이 몇 개의 데이터에 집착하면 새로운 데이터에 적응하지 못하므로 좋은 성능을 가졌다고 할 수 없다. 이를 '모델이 일반화되지 않았다.' 라고 한다.
L1 규제
- L1규제는 순실 함수에 가중치의 절댓값인 L1노름을 추가한다.
- L1노름은 다음과 같이 정의된다.
- o값은 L1규제의 양을 조절하는 하이퍼파라미터이다.
- o값이 커지만 규제가 강해지고(w값의 합이 작아지니까) 작아지면 규제가 약해진다.(w값의 합이 커지니까)
- n은 학습률이고 이 식을 이용해서 w값을 업데이트 시켜주면 된다.
**회귀 모델에서 L1규제를 추가한 것을 라쏘 모델이라고 한다.
L2규제
- L2 규제는 손실 함수에 가중치에 대한 L2노름의 제곱을 더한다.
- L2 규제는 다음과 같이 정의된다.
- L2 규제는 그레이디언트 계산에 가중치의 값 자체가 포함되므로 가중치의 부호만 사용하는 L1규제보다 조금 더 효과적이다.
- L2 규제는 가중치를 완전히 0으로 만들지 않는다.
**회귀 모델에서 L2규제를 적용한 것을 릿지 모델이라고 한다.
아래 코드는 L1, L2 규제를 사용했을 때 훈련세트의 손실과 검증세트의 손실을 분석하고, 그래프가 언제 과대적합, 과소적합이 나타나는지 확인해서 어느정도 규제 강도를 줘야할지 판단하는 코드이다.
#모듈 및 데이터 준비,전처리
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import SGDClassifier
cancer = load_breast_cancer()
x = cancer.data
y = cancer.target
x_train_all, x_test, y_train_all, y_test = train_test_split(x,y, stratify=y, test_size = 0.2, random_state=42)
x_train, x_val, y_train, y_val = train_test_split(x_train_all, y_train_all, stratify=y_train_all, test_size=0.2, random_state=42)
#표준화 (스케일 조정하기)
train_mean = np.mean(x_train, axis=0)
train_std = np.std(x_train, axis=0)
x_train_scaled = (x_train-train_mean)/train_std
x_val_scaled = (x_val-train_mean)/train_std
sgd = SGDClassifier(loss='hinge',random_state=42)
sgd.fit(x_train,y_train)
sgd.score(x_val, y_val)
w = [1,2,3,-4,5] #np.sign()은 각 값들의 부호를 반환한다.
print(np.sign(w))
class SingleLayer():
def __init__(self, learning_rate=0.1, l1=0, l2=0):
self.w = None # 데이터 특성이 많기 때문에 가중치를 미리 초기화하지 않음.
self.b = None
self.losses = [] #훈련 세트의 손실 함수 변화치
self.w_history=[] # w값의 가중치의 변화를 보기위해
self.val_losses = [] #검증 세트의 손실 함수 변화치
self.lr=learning_rate
self.l1 = l1 #L1규제와 L2규제의 강도를 조절하는 매개변수를 추가
self.l2 = l2
def forpass(self, x):
z = np.sum(x * self.w) + self.b # 직선의 방정식 즉 hypothesis #np.sum을 사용하면 1차원 넘파이배열에서 배열의 요소끼리 사칙연산을 할 수 있음.
return z
def backprop(self, x, err):
w_grad = x * err #가중치에 대한 그레이디언트 계산
b_grad = 1 * err #절편에 대한 그레이디언트 계산
return w_grad, b_grad
def fit(self, x, y, epochs=100, x_val=None, y_val=None):
self.w = np.ones(x.shape[1]) #가중치를 초기화하는데 x의 shape과 똑같고 1채움 즉 각 행값
self.b = 0
self.w_history.append(self.w.copy()) #가중치를 기록한다.
np.random.seed(42) #무작위로 시드를 지정한다.
for i in range(epochs):
loss = 0
indexes = np.random.permutation(np.arange(len(x))) #인덱스를 섞음. 데이터가 섞여서 훈련될수록 손실 함수의 값이 효율적으로 줄어듬
for i in indexes:
z = self.forpass(x[i]) #정방향 계산
a = self.activation(z) #activation함수 적용
err = -(y[i] - a) #실제값과 예측값 오차계산
w_grad, b_grad = self.backprop(x[i], err) #역방향 계산
w_grad += self.l1*np.sign(self.w) + self.l2* self.w #그레이디언트에 패널티 항의 미분값을 더함, 동시에 진행.
self.w -= w_grad * self.lr #업데이트(학습률 적용)
self.b -= b_grad
self.w_history.append(self.w.copy()) #가중치를 기록
a = np.clip(a, 1e-10, 1-1e-10) #인잔힌 로그 계산을 위해 클리핑 한 후 손실을 누적
loss += -(y[i] * np.log(a) + (1-y[i]) * np.log(1-a)) #에포크마다 평균 손실을 저장
self.losses.append(loss/len(y)+ self.reg_loss())
self.update_val_loss(x_val,y_val)
def reg_loss(self):
return self.l1 * np.sum(np.abs(self.w)) + self.l2 / 2 * np.sum(self.w**2)
def update_val_loss(self, x_val, y_val):
if x_val is None :
return
val_loss = 0
for i in range(len(x_val)):
z = self.forpass(x_val[i])
a = self.activation(z)
a = np.clip(a, 1e-10, 1-1e-10)
val_loss += -(y_val[i]*np.log(a)+ (1-y_val[i])*np.log(1-a))
self.val_losses.append(val_loss/len(y_val) + self.reg_loss())
def activation(self, z):
a = 1 / (1 + np.exp(-z))
return a
def predict(self, x):
z = [self.forpass(x_i) for x_i in x] #hypothesis를 구해 반환
return np.array(z) > 0 #스텝 함수 적용
def score(self, x, y):
return np.mean(self.predict(x)==y)
l1_list = [0.0001, 0.001, 0.01] #l1으로 규제 후 규제강도 별로 loss와 val_loss를 구함
for l1 in l1_list:
lyr = SingleLayer(l1 = l1)
lyr.fit(x_train_scaled,y_train, x_val = x_val_scaled, y_val =y_val)
plt.plot(lyr.losses)
plt.plot(lyr.val_losses)
plt.title('Learning Curve (l1={})'.format(l1))
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train_loss','val_loss'])
plt.ylim(0,0.3)
plt.show()
plt.plot(lyr.w, 'bo')
plt.title('Weigth (l1={})'.format(l1))
plt.ylabel("value")
plt.xlabel("weight")
plt.ylim(-4,4)
plt.show()
layer = SingleLayer(l1=0.001) #0.01은 과소적합 현상이 생기므로 적당한 규제의 강도는 0.001이고 반복은 20의 에포크 회수를 준다.
layer.fit(x_train_scaled,y_train, epochs=20)
layer.score(x_val_scaled,y_val)
l2_list = [0.0001, 0.001, 0.01]
for l2 in l2_list:
lyr = SingleLayer(l2=l2)
lyr.fit(x_train_scaled, y_train, x_val = x_val_scaled, y_val= y_val)
plt.plot(lyr.losses)
plt.plot(lyr.val_losses)
plt.title('Learning Curve (l2={})'.format(l2))
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train_loss','val_loss'])
plt.ylim(0,0.3)
plt.show()
plt.plot(lyr.w, 'bo')
plt.title('Weigth (l2={})'.format(l2))
plt.ylabel("value")
plt.xlabel("weight")
plt.ylim(-4,4)
plt.show()
layer2 = SingleLayer(l2=0.01) #적당한 L2규제의 강도는 0.01이다.
layer2.fit(x_train_scaled, y_train)
layer2.score(x_val_scaled,y_val)
sgd = SGDClassifier(loss='log',penalty='l2',alpha=0.001, random_state=42) #SGD에서도 바로 사용가능
sgd.fit(x_train_scaled, y_train)
sgd.score(x_val_scaled,y_val)
'빅데이터 | 머신러닝 | 딥러닝 > 딥러닝' 카테고리의 다른 글
[딥러닝 기초] 다층 신경망 (MLPClassifier) (0) | 2020.05.08 |
---|---|
[딥러닝 기초] k-fold 교차 검증(cross validation) (0) | 2020.05.02 |
[딥러닝 기초] Neural Network (훈련 노하우) (0) | 2020.04.24 |
[딥러닝 기초] Neural Network (use cancer dataset/classfication) (0) | 2020.04.19 |
[jupyter notebook] Neural Network (use mnist dataset) (0) | 2020.04.18 |