정지홍 2023. 1. 14. 09:59

신경망의 층이 많아지면 우리는 이것을 심층신경망이라고 부르며 이를 딥러닝이라고 한다.(정확히 몇 개 이상 있어야 딥러닝이라고 부르는 기준은 아직은 없으나 여러층으로 구성된 신경망으로 학습을 시키면 딥러닝이라고 함)

 

신경망 층 수↑ , 표현력↑ , 학습도 복잡 , 오차 전파시키기 어렵 , 계산량↑

 

해결할 문제들

  • 국소 최적해 함정(극소점에 빠져서 최적해에 도달x  or  기울기가 거의 없는 지점에서 학습 진행x인 경우) 
  • 과적합(훈련 데이터에 과도하게 적합되서 새로 입력되는 데이터에 대해서 바르게 추정 못하는 경우)
  • 기울기 손실(기울기가 0에 가까워 지는 경우)
  • 장기간의 학습기간(오래걸리는 학습 기간)

 

해결 방안들

  • 하이퍼 파라미터 최적화(뉴런수 , 신경망 수,학습률등 학습하기 전에 미리 설정하는 값들 설정을 최적값으로 설정, ex) if 은닉층 뉴런수↑ => 표현력↑ , 과적합 발생 확률↑)
  • 규제화(가중치에 제한을 둔다=> 가중치가 극단적인 값이 되어 국소최적해에 빠지는 것을 방지)
  • 조기종료(학습 중간에 중단시킴)
  • 데이터 확장(샘플의 수가 적으면 과적합 확률↑되니 샘플의 수를 증가시키는 방법)
  • 데이터 전처리(입력 데이터를 미리 다루기 쉽게 조작하며 이로 인해 학습 속도↑) (정규화: 데이터를 특정범위 안에 들어가게 한다.)   (표준화: 데이터 평균을 0으로하고 표준편차가 1이 되도록하여 대부분의 데이터가 -1부터 1사이에 포함되게한다.) (무상관화: 데이터 각 성분의 상관관계를 제거 시키는 것.) (백색화:조건에 맞지 않는 데이터를 재가공하는 것.)
  • 드롭 아웃(출력층 이외의 뉴런을 일정확률로 무작위 제거, 제거되는 뉴런은 가중치와 편향을 수정할때 마다 바꾼다. 덕분에 신경망의 규모를 줄일수 있으며 규모는 작아져도 여러개의 모형을 조합하니 표현력은 그대로다.) 

#붓꽃 품종 구별
#신경망은 입력층,출력층 사이에 2개의 은닉칭을 놓아서 4층 신경망으로
#데이터는 꽃의 갯수는 3개이며 붓꽃의 데이터 측정 부위는 꽃받침 길이와 폭 , 꽃잎의 길이와 폭
#입력값은 데이터 측정 부위 갯수에 따라 4개
#출력값은 꽃의 갯수에 따라 3개
#은닉층 활성화 함수 ReLu
#출력층 활성화함스 소프트맥스
#손실함수는 교차 엔트로피 오차

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

#데이터 입력 및 전처리
iris_data=datasets.load_iris()#사이킷런의 데이터세트 불러옴
input_data=iris_data.data#측정값
correct=iris_data.target#품종 인덱스
n_data=len(correct)#데이터의 수

#입력 데이터를 표준화(데이터 전처리 부분)
ave_input=np.average(input_data , axis=0)   #input_data표준화를 위한 분자부분
std_input=np.std(input_data , axis=0)#분모 부분
input_data=(input_data-ave_input)/std_input #인력데이터를 -1과 1사이로 표준화 시킨다

#붓꽃 데이터를 원핫 인코딩으로
correct_data=np.zeros((n_data,3))#zeros함수로 nx3행렬 만들고
for i in range(n_data):
    correct_data[i,correct[i]]=1.0#각 해당되는 정답의 값을 1 아니면 0으로 원핫 인코딩으로

#데이터를 절반씩 훈련데이터 테스트 데이터로 나눈다
index=np.arange(n_data)
index_train=index[index%2==0]#훈련데이터로 ㄱㄱ
index_test=index[index%2!=0]#테스트데이터로 ㄱㄱ

input_train=input_data[index_train , :]#흔련 데이터 인풋
correct_train=correct_data[index_train,:]#흔련 데이터의 정답
input_test=input_data[index_test, :] #테스트 데이터의 인풋
correct_test=correct_data[index_test, :]#테스트 데이터의 정답

n_train=input_train.shape[0]#훈련 데이터 샘플 수
n_test=input_test.shape[0]#테스트 데이터 샘플 수
n_in=4
n_mid=25
n_out=3
wb_width=0.1
eta=0.01
epoch=1000
batch_size=8
interval=100

#각 층마다 공통되는 부분은 상속을 하기위해서 부모클래스 생성
class BaseLayer:
    def __init__(self,n_upper,n):
        self.w=wb_width*np.random.rand(n_upper , n)
        self.b=wb_width*np.random.randn(n)
   
    def update(self, eta):
        self.w-= eta*self.grad_w
        self.b-= eta*self.grad_b

class MiddleLayer(BaseLayer):

    def forward(self,x):
        self.x=x
        self.u=np.dot(x,self.w)+self.b
        self.y=np.where(self.u<=0,0,self.u)#ReLu함수

    def backward(self,grad_y):
        delta=grad_y*np.where(self.u<=0,0,1)
        self.grad_w=np.dot(self.x.T , delta)
        self.grad_b=np.sum(delta,axis=0)
        self.grad_x=np.dot(delta,self.w.T)

class OutPutLayer(BaseLayer):

    def forward(self,x):
        self.x=x
        u=np.dot(x,self.w)+self.b
        self.y=np.exp(u)/np.sum(np.exp(u),axis=1,keepdims=True)
   
    def backward(self,t):
        delta=self.y-t
        self.grad_w=np.dot(self.x.T,delta)
        self.grad_b=np.sum(delta,axis=0)
        self.grad_x=np.dot(delta,self.w.T)

#각 층을 초기화 시킨다.
middle_layer_1 = MiddleLayer(n_in, n_mid)
middle_layer_2 = MiddleLayer(n_mid, n_mid)
output_layer = OutPutLayer(n_mid, n_out)

def forward_propagation(x):#순전파
    middle_layer_1.forward(x)
    middle_layer_2.forward(middle_layer_1.y)
    output_layer.forward(middle_layer_2.y)


def backpropagation(t):#역전파
    output_layer.backward(t)
    middle_layer_2.backward(output_layer.grad_x)
    middle_layer_1.backward(middle_layer_2.grad_x)


def uppdate_wb():#가중치 및 편향을 수정한다
    middle_layer_1.update(eta)
    middle_layer_2.update(eta)
    output_layer.update(eta)


def get_error(t, batch_size):#분류문제이니 오차를 계산하는 함수도 만든다
    return -np.sum(t * np.log(output_layer.y+ 1e-7)) / batch_size

train_error_x = []
train_error_y = []
test_error_x = []
test_error_y = []

n_batch = n_train // batch_size#1에포크에 해당하는 배치수를 구한다.  
for i in range(epoch):

    forward_propagation(input_train)
    error_train = get_error(correct_train, n_train)
    forward_propagation(input_test)
    error_test = get_error(correct_test, n_test)
   
    test_error_x.append(i)
    test_error_y.append(error_test)
    train_error_x.append(i)
    train_error_y.append(error_train)
   
    if i%interval == 0:
        print("Epoch:" + str(i) + "/" + str(epoch),
              "Error_train:" + str(error_train),
              "Error_test:" + str(error_test))

    index_random = np.arange(n_train)
    np.random.shuffle(index_random)  
    for j in range(n_batch):
       
        mb_index = index_random[j*batch_size : (j+1)*batch_size]
        x = input_train[mb_index, :]
        t = correct_train[mb_index, :]
       
        forward_propagation(x)
        backpropagation(t)
        uppdate_wb()

plt.plot(train_error_x, train_error_y, label="Train")
plt.plot(test_error_x, test_error_y, label="Test")
plt.legend()
plt.xlabel("Epochs")
plt.ylabel("Error")
plt.show()
이 결과를 보면 학습이 진행되면서 훈련과 테스트의 오차의 차이가 벌어지며 과적합이 발생하였다. 학습이 진행되면서 신경망이 훈련데이터에 과도하게 적응한 것 같다.

위의 문제를 해결하기 위해서는 2가지 방법이 있다. 하나는 확률적 경사 하강법에서 아드그라드로 변경하는것, 다른 하나는 드롭 아웃을 도입하는 것이다.

이러면 4가지 방식의 조합이 나온다

  • 확률적 경사하강법
  • 아다그라드(확률적 경사 하강법에 비해 과적합은 덜 발생)
  • 확률적 경사하강법 + 드롭아웃(과적합이 감소 및 정답률 향상)
  • 아다그라드 + 드롭아웃(과적합이 가장 덜 발생 및 정답률도 제일 높았다)

드롭아웃의 구현?

-입력층->은닉층->드롭아웃층->은닉층->드롭아웃층->출력층