[ Essay - Technology, Essay - Intuition ] Chat GTP시대의 도래와 생각하는 방식에 대해 : 개발자의 미래에 대해

이미지
벌써 올해도 반쯤 지나 뜨거운 여름이 다가왔다. 굉장히 빠르게 지나간듯한 느낌이 드는데  아마 의미있는 시간을 보냈다는 이야기이기 때문에  그렇게 나쁜 신호는 아닐 것 이다. 괴로운 시간이였다면, 1초가 1년 같이 느껴졌을테니 말이다. 더위에 매우 약한 나에게 있어서는 지옥과 같은 시기이기도 하지만 늘 그렇던 것 처럼 에어컨 덕분에 어찌저찌 버틸 수 있을 것 같다. 어쨋든, 이번에는 저번의 에세이 주제, Chat GTP시대의 도래와 생각하는 방식에 대한 이야기에 이어서  과연 개발자의 미래는 어떻게 될 것인가에 대해 이야기를 나누어보려고 한다. 어쩌면 모두가 인식하고 있듯이 지금 2025년 현재,  꽤나 중요한 시기에 직면하고 있는지도 모른다. 왜냐하면, 생성AI의 발전 속도가 생각보다 빠르게 발전하고 있고,  그에 따라 실제 업무에서도 빠르게 사용되어지고 있기 때문이다. 이러한 상황에서 개발자에게 있어서 가장 두려운 점은  당연히 생성AI에 의해 개발자가 대체될 것 이라는 두려움일 것 이다. 이는 개발자에게만 한정된 이야기는 아니지만 말이다. 아마 필드에서 나와 같이 일하고 있거나  개발자로서 직업을 가지려는 생각이 있는 사람이라면  한번쯤은 생각해볼법한 주제라 생각 한다. 물론 미래가 정확히 어떻게 될 지는 알 수 없으나  이런 생각을 함으로써 몇 가지 힌트는 얻게 될지도 모르니  만약 얻게 된다면 미래에 대한 방향성을 조금이나마 올바른 쪽으로 돌릴 수 있을 것 이다. 이 글을 끝맽을 때는 조금이라도 힌트에 도달하기를 바란다. 과거의 역사 이러한 의문에 대한 해결책으로서 일반적으로 자주 사용하는 방법이 있다. 바로 역사를 보는 것 이다. 물론 이러한 역사를 해결책을 찾는거에 대한 전제조건은  우리가 '구 인류'라는 전제조건이 있었을 때 의미가 있다. 그러니깐 현대인도 기원전 8세기의 고대 로마인도  본질적으로 다르지 않다는 것을 인정해야만 한다. 예컨데...

[ Neural Network, Python, Back Propagation ] Python에서 뉴럴 네트워크는 어떻게 표현되는가? : 역전파의 구현, 테스트(수직, 나선 분포 데이터)


손실 함수의 구현으로 활성화 함수 계산을 추가한
예측 값이 얼마 만큼의 오류를 가지고 있는 지에 대한 평균 값을 얻을 수 있었다.

그 다음은 역전파를 구현할 차례이다.

이번 포스팅의 코드들은 모듈화 되지 않은 
날것에 가까운 코드 이기 때문에 정렬되어 있지 않다.

이 포스팅 다음에 모듈화를 진행할 예정이다.

역전파에 대해 


앞서 이야기 했듯이 
손실을 구했다면, 당연히 예측 값을 조절해 
최대한 손실이 없게 끔 만들어야 한다.

어떻게 해야될까?

가장 일반적인 방법은 가중치를 미세하게 조절하면서
손실 함수가 최소화 될 때 까지 반복하는 것이다. 

즉, 상수 값인 기울기(가중치)를 조절하는 것이다.

역전파 프로세스는 
활성화 함수에서의 역전파(backward) 단계에서는 
순방향 전파에서 계산한 값을 가지고 
가중치와 편향을 미세 조절 하기 위한 가중치(gradient)를 결정 한다.

레이어의 역전파(backward) 단계에서
이 가중치를 레이어에서 넘겨 받아
이 값을 가지고 레이어가 가지고 있는 가중치와 편향을 업데이트 한다.

역전파는 말 그대로 역으로 값을 전달하며 
진행되기 때문에 연쇄 법칙(Chain Rule)이라고도 한다.

다만 이전 과정에서 구현했던 활성화 함수, 손실 함수를 포함해 
레이어의  역전파를 구현해야 하기 때문에 
수식에 대한 더 많은 이해와 
이전 보다 더 많은 코드 추가가 필요할 것 이다. 

그렇기에 구현 하는데 있어서 예상보다 시간이 소모되었기에 
역전파의 구현은 활성화 함수는 ReLu, Softmax를
그리고 손실 함수는 범주형 교차 엔트로피(Categorical Cross Entropy,CCE)만을 
포스팅 할 예정이다.

차후에 이진 교차 엔트로피와 다른 활성화 함수에 대한 
역전파 구현은 차후에 수학적인 내용에 들어갈때 같이 구현하기로 하겠다.

그렇다면 역전파라는 말대로, 반대로 타고 들어가보자.

역전파의 구현 : 출력 레이어

이전에 언급한대로 활성화 함수로 
소프트 맥스와 ReUL를 선택했기 때문에

이에 따라 범주형 교차 엔트로피를 구현해야 한다.


① 범주형 교차 엔트로피(SoftMax)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Loss_CategoricalCrossentropy(Loss_CCE):
    def CCE_forward(self, y_pred, y_true):
        #오버플로우 방지를 위해 값을 조정
        y_pred_clipped = np.clip(y_pred, 1e-71-1e-7)
 
        loss_cce = -np.sum(y_true*np.log(y_pred_clipped))
 
        return loss_cce
 
    def CCE_backward(self,y_pred, y_true):
        n = y_true.shape[0]
 
        # 실제 확률 분포가 one-HotEncoding화 되어있지 않을때
        if len(y_true.shape) == 1:
            gradient = y_pred
            gradient[range(n),y_true] -= 1
            gradient = gradient/n
        #실제 확률 분포가 one-HotEncoding화 되어있을때
        elif len(y_true.shape) == 2:
            gradient = (y_pred-y_true)/n
 
        return gradient
cs

범주형 교차 엔트로피의 경우 각 요소의 합이 1이기 때문에 간단하다.

예측된 확률 분포와 실제 확률 분포의 차이 값에 전체 갯수를 나누어주면 된다.

계산되어 기울기(gradient)에 저장된 값은 SoftMax에 전달된다.

shape 매소드를 활용해 실제 확률 분포 변수인 y_true가
one-HotEncdoing화가 되어 있는지를 판단하게 하였다.

정상적인 y_true 값이라면, 모두 동일한 값이 산출 된다.

역전파의 구현 : 활성화 함수




① 소프트 맥스 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Activation_Softmax(Layer):
    def __init__(self, output_cnt):
 
        self.units = output_cnt
        self.type = 'Softmax'
 
    def forward(self,inputs):
        #오버플로우를 방지하기 위해 최대값을 빼줌
        exp_values = np.exp(inputs - np.max(inputs, axis=1, keepdims=True))
        probabilities = exp_values / np.sum(exp_values, axis=1, keepdims=True)
 
        self.output = probabilities
 
        return self.output
 
    def backward(self,gradient):
        forward_soft = self.output
        self.output_back = forward_soft * (gradient -( gradient * forward_soft).sum(axis=1, keepdims=True))
 
        return self.output_back
cs

위에서 잠깐 언급한대로 레이어에 전달할 기울기를 산출 한다.

이 뉴럴 네트워크 코드에서 
소프트 맥스의 역전파(backward) 단계에서 매개변수로 받는 기울기는 
범주형 교차 엔트로피의 역전파에서 산출된 값이다.

② ReUL


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Activation_ReLU(Layer):
    def __init__(self, output_cnt):
 
        self.units = output_cnt
        self.type = 'ReLU'
 
    def forward(self,inputs):
        self.output = np.maximum(0, inputs)
        return self.output
 
    def backward(self,gradient):
        foward_ReLU = self.output
 
        self.output_back = gradient * np.where(foward_ReLU<=001)
        #self.output_back = gradient * np.heaviside(foward_ReLU,0)
 
        return self.output_back
cs


np.where 함수는 ReLU 순방향 전파로 얻은 각 요소가 
0보다 작거나 같을 경우 0으로 값을 바꾸며,
0보다 클 경우 1로 바꾸는 작업을 한다.

밑에 주석 처리한 코드와 정확히 동일한 작업을 한다.

역전파의 구현 : 레이어



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Layer:
    def __init__(self):
        pass
 
    def forward(self):
        pass
 
    def backward(self):
        pass
 
class Layer_Dense(Layer):
    def __init__(self, n_inputs, n_neurons):
        #randn : 0으로 중심으로한 가우스 분포를 무작위 생성
        #실제 randn 함수는 1을 초과한 값을 출력하기 때문에 0.10을 곱함
        self.weights = 0.10 * np.random.randn(n_inputs, n_neurons)
        #뉴런 수만 큼 무작위로 편향(값) 생성
        self.biases = np.random.rand(1,n_neurons)
 
    def forward(self, inputs):
        self.input = inputs
        #순방향 전파 수식을 표현
        self.output = np.dot(inputs, self.weights) + self.biases
 
        return self.output
 
    def backward(self, d_activation):
        learning_rate = 0.15
        #delta
        d_input = np.dot(d_activation,self.weights.T)
        #가중치 오류률 산출
        d_weight = np.dot(self.input.T,d_activation)
        #편향 오류률 산출
        d_bias = d_activation.mean(axis=0,keepdims=True)
 
        #가중치, 편향 업데이트
        self.weights -= learning_rate * d_weight
        self.biases -= learning_rate * d_bias
 
        return d_input
cs

후에 모듈화 작업을 하기 위해 
먼저 Layer라는 인터페이스를 만들었다. 

레이어의 역전파(backward)는 활성화 함수 기울기 값을 이용해 
가중치, 편향의 오류률을 산출하고
오류률을 사용해 업데이트 할 상수를 계산해 
레이어가 가지고 있는 가중치와 편향을 업데이트 한다.

변수 learning_rate는 모듈화 할때 매개 변수로 사용할 것이다.

테스트 : 학습


학습의 전체적인 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#X, y = spiral_data(samples=100, classes=3)
X, y = vertical_data(samples=100, classes=3)
 
NN_tools = NN_tools()
 
y_true = NN_tools.oneHotEncoding(y)
 
dense1 = Layer_Dense(2,4)
activation1 = Activation_ReLU(4)
 
dense2 = Layer_Dense(4,3)
activation2 = Activation_Softmax(3)
 
loss_function = Loss_CategoricalCrossentropy()
 
for iteration in range(100):
 
    #순방향 전파 프로세스
    dense1.forward(X)
    activation1.forward(dense1.output)
    dense2.forward(activation1.output)
    activation2.forward(dense2.output)
 
    #손실 계산
    loss = loss_function.forward(activation2.output, y_true)
    print("Loss:", loss)
 
    #손실의 기울기 계산
    back_CCE = loss_function.backward(activation2.output, y_true)
 
    #역전파 프로세스(산출된 손실 기울기를 기반으로 반복)
    for iteration_back in range(100):
 
        act2_back = activation2.backward(back_CCE)
        dense2_back = dense2.backward(act2_back)
 
        act1_back = activation1.backward(dense2_back)
        dense1_back = dense1.backward(act1_back)
 
#역전파로 조정된 가중치, 편향으로 다시 한번 순전파 프로세스를 실행
dense1.forward(X)
activation1.forward(dense1.output)
 
dense2.forward(activation1.output)
activation2.forward(dense2.output)
 
loss = loss_function.forward(activation2.output, y_true)
 
print("predict Loss:", loss)
 
cs

이제 수직, 나선 분포의 데이터를 가지고 결과값을 살펴보자.

테스트 : 수직 분포 데이터의 경우 경우


데이터의 분포는 보통 2가지로 예상해볼 수 있을 것이다.

수직(vertical)형과 나선(Spiral)형이다.

사용하고 있는 훈련 데이터의 수직형은 아래와 같은 분포로 데이터가 위치해 있다.


한눈에 봐도 같이 색이 되도록 주변에 분포되어 있기 때문에 분류하기 매우 편해보인다.

최적화가 최대한 이루어진 경계선이 정확히는 알 수 없으나
대략적으로 추측이 가능하기 때문이다.


아마 최적화된 경계선은 위와 같은 선이 될 가능성이 크다.

이 데이터를 가지고 훈련을 해보면 아래와 같은 결과가 나온다.

훈련 시작 부분

위의 사진으로 보면 약 손실 값이 1.1에서 눈에 띌 정도로
줄어드는 것을 확인할 수 있다.

이를 통해 구현이 제대로 이루어 진것을 알 수 있다. 


훈련 끝 부분

끝 부분에서 오류률이 0.44까지 내려가며 
다시 순전파를 진행할 때 0.46의 값이 나옴으로써 
역전파를 통해 레이어의 가중치와 편향이 이전보다 오류률이 줄은 것을 알수 있다.

다만, 값을 보면 값이 증가했다가 줄어들었다하는 패턴을 보이는 것을 
볼 수 있는데, 이는 학습 비율(변수 learning_rate)이 크기 때문이다.

이 값을 적절하게 조절하면 안정적이게 
손실 값이 줄어드는 것을 확인할 수 있을 것이다.

테스트 : 나선 분포 데이터의 경우 경우

나선 분포는 아래와 같이 데이터가 위치해 있다.


수직 분포와는 다르게 
분류에 따른 데이터가 산발적으로 분포되어 있기 때문에
적절한 경계를 정하기가 어려워 보인다.

결과 값을 살펴보자.

훈련 시작 부분


오류값이 1.1부터 시작해 수직 분포의 경우보다 
오류률이 눈에 띄게 줄어들지 않는 것을 확인할 수 있다.


훈련 종료 부분

훈련이 종료될 시점에는 약 1.08로 
오류률이 고작 0.02밖에 줄어들지 않은 것을 확인할 수 있다.

따라서 가지고 있는 데이터의 분포에 따라
최적의 오류률을 계산하기 위한 비용이 증가할 수도 있다는 것을 알 수 있다.

마치며

이제 뉴럴 네트워크의 전체적인 소스 코드는 사실상 완성되었다.

하지만, 이 코드는 좋은 코드라고 보기는 힘들며
뉴럴 네트워크를 이해하기 위한 
하나의 예로서 바라보는 것이 좋을 것이다.

Pytorch라던가 이전 순방향 전파의 결과 값을 확인할 때 
잠깐 사용했던 구글의 tensorflow 등의 훌륭한 프레임 워크가 있기 때문이다.

지금까지는 함수 하나하나를 호출해서 
레이어를 구성하는 등의 호출 했지만,
여기서 좀 더 나아간다면 모듈화를 통해
클래스로 호출하는 것이 더욱 적절할 것 이다.

다음에는 새로운 클래스를 하나 구현해 
이 곳을 통해 레이어의 추가 하고, 
순전파를 비롯해 역전파를 통한 뉴럴 네트워크 훈련 할 필요가 있을 것이다.

이 블로그의 인기 게시물

[ Web ] 웹 애플리케이션 아키텍처 (Web Application Architecture)

[ Web ] 서버 사이드(Sever Side) ? 클라이언트 사이드(Client Side)? 1 [서론, 클라이언트 사이드(Client Side)]

[ Web ] 웹 애플리케이션 서버 아키텍처의 정의 및 유형 ( Define and Types of Web Application Server Architecture )