빅데이터 | 머신러닝 | 딥러닝/딥러닝

[딥러닝 기초] Neural Network (L1, L2 규제)

냠냠:) 2020. 4. 25. 18:32

[이 글은 "Do it 딥러닝 입문" 책을 보고 공부한 내용을 복습하고자 정리한 글입니다.]

 

목표 - 과대적합, 과소적합을 이해하고 L1규제와 L2규제에 대해 이해한다.

 

[배운 점]

 

과대적합

- 모델이 훈련 세트에서는 좋은 성능을 내지만 검증 세트에서는 낮은 성능을 내는 경우.

- 정확도/Epochs 그래프를 그려보면 측정한 성능의 간격이 크다. 이를 '분산이 크다'라고도 말한다.

- 훈련세트의 다양성이 없어서 나오는 경우, 훈련 세트를 더 모으지 못한다면, 모델이 훈련세트에 집착하지 않도록 가중치 제한을 둘 수 있다. 이를 '모델의 복잡도를 낮춘다'라고 말한다.

 

과소적합

- 모델이 훈련세트와 검증세트에서 많은 성능차이를 보이진 않지만 둘 다 성능이 저조한 경우.

- 정확도/Epochs 그래프에서 성능 간격은 적지만 성능이 낮다.  이를'편향이 크다'라고 말한다.

- 해결방법은 복잡도를 더 높은 모델을 사용하거나 가중치의 규제를 완화하는 것이다.

 

*모델 복잡도 : 모델이 가진 학습 가능한 가중치 개수를 말함.

 

적절한 편향-분산 트레이드오프를 선택

-과대적합, 과소 적합 사이에서의 절충점을 찾는다.

 

모델의 일반화

- 모델이 몇 개의 데이터에 집착하면 새로운 데이터에 적응하지 못하므로 좋은 성능을 가졌다고 할 수 없다. 이를 '모델이 일반화되지 않았다.' 라고 한다.

 

L1 규제

- L1규제는 순실 함수에 가중치의 절댓값인 L1노름을 추가한다.

- L1노름은 다음과 같이 정의된다.

L1 노름
로지스틱 손실 함수
L1규제

- o값은 L1규제의 양을 조절하는 하이퍼파라미터이다. 

- o값이 커지만 규제가 강해지고(w값의 합이 작아지니까) 작아지면 규제가 약해진다.(w값의 합이 커지니까)

손실 함수의 도함수
w값 업데이트

- n은 학습률이고 이 식을 이용해서 w값을 업데이트 시켜주면 된다.

**회귀 모델에서 L1규제를 추가한 것을 라쏘 모델이라고 한다.

 

 

L2규제

- L2 규제는 손실 함수에 가중치에 대한 L2노름의 제곱을 더한다.

- L2 규제는 다음과 같이 정의된다.

L2 노름
L2 규제
손실함수의 도함수
w값 업데이트

- L2 규제는 그레이디언트 계산에 가중치의 값 자체가 포함되므로 가중치의 부호만 사용하는 L1규제보다 조금 더 효과적이다.

- L2 규제는 가중치를 완전히 0으로 만들지 않는다.

**회귀 모델에서 L2규제를 적용한 것을 릿지 모델이라고 한다.

 

아래 코드는 L1, L2 규제를 사용했을 때 훈련세트의 손실과 검증세트의 손실을 분석하고, 그래프가 언제 과대적합, 과소적합이 나타나는지 확인해서 어느정도 규제 강도를 줘야할지 판단하는 코드이다. 

 

L1L2_2020_4_25
In [1]:
#모듈 및 데이터 준비,전처리
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)
Out[1]:
0.9230769230769231
In [2]:
w = [1,2,3,-4,5]  #np.sign()은 각 값들의 부호를 반환한다.
print(np.sign(w))
[ 1  1  1 -1  1]
In [3]:
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)
In [4]:
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()
In [5]:
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)
Out[5]:
0.978021978021978
In [6]:
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()
In [7]:
layer2 = SingleLayer(l2=0.01)      #적당한 L2규제의 강도는 0.01이다.
layer2.fit(x_train_scaled, y_train)
layer2.score(x_val_scaled,y_val)
Out[7]:
0.978021978021978
In [8]:
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)
Out[8]:
0.978021978021978
In [ ]:
 
반응형