머신러닝&딥러닝/기초정리

경사하강법 이해(1)

Like_Me 2019. 12. 30. 21:42
반응형

  인공지능 모델의 전체적인 과정을 보면 다음과 같다.

데이터 입력 => 파라미터(weight, bias)를 통한 output 도출 => loss값 생성(label값과 prediction값의 차이 이용) => loss를 줄기 위해 기울기를 이용한 최적화(경사 하강법 적용, parameter 갱신)

  *  loss를 구하고 경사하강법을 적용하는 것이 모델 학습의 핵심이다. 파라미터 weight과 bias를 이용하여 x를 input으로 주었을 때, H(x)=Wx+b의 output을 내게 된다. 이때 H(x) 값과 Label값을 비교하여 Loss를 생성한다. 대표적인 Loss 함수 Mean Squared Error를 이용한다고 했을 때, 식이 다음과 같다.

MSE = Mean((H(x)-Label)^2)/2  = Mean((Wx+b - Label)^2)/2

H(x)는 W와 b에 따라 변하므로 loss는 두 파라미터에 의해 변하게 된다. loss값을 줄이는 것이 최적화의 핵심이며 그것을 만족하는 Weight과 bias를 찾는 것이 경사 하강법이다!

미분을 이용한 최적해 찾기

그림으로 보면 위와 같이 되는데 이때 x값이 Parameter가 되고 f(x)가 Loss function(이 경우엔 MSE) 이 된다.

미분의 정의를 수식으로 나타내면 아래와 같이 되며 실제 구현 시 사용하게 된다.

미분의 정의 = x가 매우 작게 변했을 때 f(x)의 변화량.

미분 값(dw, db)을 구하고 나면 weight과 bias를 업데이트해가며 최적화를 해준다. weight = weight-alpha*dw,

bias = bias-alpha*db 가 되며 alpha는 학습률(learning rate)가 된다.

 

  신경망을 구현한다고 할 때, 입력 계층(10) => 은닉 계층(32) => 출력 계층(2) 이면, 각각의 fully connect layer에서의 weight의 크기가 달라진다. 괄호 안의 숫자는 node 숫자이다. 입력 계층과 은닉 계층을 연결하는 layer에서의 weight은 (10,32)의 행렬이고 bias는 (32,1)의 행렬이며, 은닉 계층과 출력 계층을 연결하는 layer에서의 weight은 (32,2)의 행렬이고 bias는 (2,1)의 행렬이다. Back Propagation을 사용하지 않고 경사 하강법을 한다고 했을 때, 각각의 weight과 bias의

행, 열의 변화에 따른 미분 값을 하나하나 계산하고 최적화를 시켜줘야 한다. 그렇기 때문에 연산량이 매우 많아지게 된다.

 

경사 하강법을 구현하며 bias의 최적화에 대해 처음에 헷갈렸던 부분이 있다. bias는 output의 shape을 가지는 벡터가 되는데 입력값의 인덱스가 아닌 출력 값의 인덱스(혹은 은닉 계층 node의 인덱스)에 따라 달라지는 값을 가진다. weight은 모든 (입력 노드, 출력 노드)에 따라 다른 값을 가지지만 bias는 그렇지 않고 출력 노드에 따라서만 달라지므로 입력 노드가 달라져도 같은 출력 노드로 연결되면 같은 bias를 사용한다는 점이 이해가 잘 가지 않았는데, 구현을 해보며 이해가 가게 됐다. 실제 구현 시에 bias값을 각 input 노드에 대해 weight을 업데이트할 때, 같이 계속 업데이트해주면 되는 것이다. 그렇게 되면 모든 입력값에서 나오는 bias에 대한 최적 값을 찾을 수 있고, 같은 bias를 사용하는 입력 노드에 대해서 각각의 입력 값을 고려한 최적값을 사용하게 된다. 다음의 내용만을 구현하면 아래의 코드와 같다.

def cal_gradient(self, x, y, loss_func):
        def get_new_sequence(layer_id, new_neuron):
            self.new_sequence = []
            # self.sequence는 미리 구해놓은 dense layer의 집합이다.
            for idx, layer in enumerate(self.sequence):
                if idx == layer_id:
                    self.new_sequence.append(new_neuron)
                else:
                    self.new_sequence.append(layer)
		# 미분을 할 때 f(x+h)를 구하기위해 사용된다.
        def eval_new_seqeunce(x):
            for layer in self.new_sequence:
                x = layer(x)
            return x
        loss = loss_func(self(x), y)
        for layer_id, layer in enumerate(self.sequence):
            for wi, w in enumerate(layer.W):
            	# weight의 (행,열)의 성분을 하나하나 구해주며 미분을 구한다.
                for wj, ww in enumerate(w):
                    W = np.copy(layer.W)
                    W[wi, wj] = ww+epsilon
                    new_neuron = Neuron(W, layer.b, layer.a)
                    new_seq = get_new_sequence(layer_id, new_neuron)
                    h = eval_new_seqeunce(x)
                    num_grad = (loss_func(h, y)-loss)/epsilon
                    # dW를 계속 업데이트 해준다.
                    layer.dW[wi, wj] = num_grad
				# for문이 w의 행(입력 index)아래에서 수행되어 
                # 모든 입력 값을 고려한 bias를 구할 수 있다.
                for bi, bb in enumerate(layer.b):
                    b = np.copy(layer.b)
                    b[bi] = bb+epsilon
                    new_neuron = Neuron(layer.W, b, layer.a)
                    new_seq = get_new_sequence(layer_id, new_neuron)
                    h = eval_new_seqeunce(x)
                    num_grad = (loss_func(h, y)-loss)/epsilon
                    layer.db[bi] = num_grad

 

반응형