본문 바로가기
파이썬 프로그래밍/Numpy 딥러닝

9. 다중 분류 구현하기(심화실습)

by Majestyblue 2022. 1. 9.

이전시간에 언급한 대로, Iris 꽃 데이터 중 일부(30개)를 불러와 학습해 볼 것이다. 

 

IRIS_tiny_onehot.csv

 

아래 파일을 임포트한다. 사용하기 쉽게 미리 원-핫 인코딩을 하였다.

IRIS_tiny_onehot.csv
0.00MB

 

 

1. 데이터 준비

파일을 임포트하고 슬라이스를 통해 입력데이터 input, 목표값 데이터 target으로 분리한다.

import numpy as np
from numpy import genfromtxt

np.random.seed(220106)

data = genfromtxt('IRIS_tiny_onehot.csv', delimiter=',', skip_header=1)
input = data[:, 0:4]
target = data[:, 4:7]

W = np.random.randn(3, 4)
B  = np.random.randn(3, 1)

learning_rate = 0.001

 

 

 

2. 순방향 연산

이전 예제들처럼, 단순히 누적이 되어 계산되기 힘든 구조이다. 빈 배열을 선언하고, 하나씩 계산한 다음에 누적시켰다. 무엇이 다른지 이전 자료와 비교해 보자.

def softmax_forward(X, Y, W, B):
    
    arr_pred = []
    arr_loss = []
    
    for i in range(len(X)):
        
        x = [X[i]]
        y = [Y[i]]

        # preprocessing data for calculate
        x = np.transpose(x )
        WXB = np.dot(W, x) + B 
    
        e_WXB1 = np.exp(WXB[0])
        e_WXB2 = np.exp(WXB[1])
        e_WXB3 = np.exp(WXB[2])
    
        sum_e_WXB = e_WXB1 + e_WXB2 + e_WXB3
    
        smax_WXB1 = e_WXB1 / sum_e_WXB
        smax_WXB2 = e_WXB2 / sum_e_WXB
        smax_WXB3 = e_WXB3 / sum_e_WXB
    
        softmax = np.array([smax_WXB1[0], smax_WXB2[0], smax_WXB3[0]])
        
        arr_pred.append(softmax)
        
        # Y가 1인 softxmax 출력만 남기기 위해 요소곱을 시행한다.
        
        y_target = np.sum(softmax * y, axis = 1, keepdims=True)
    
        loss = -np.log(y_target)
        arr_loss.append(loss)
        
    preds = np.array(arr_pred)
    losses = np.sum(np.array(arr_loss))

    return preds, losses

 

 

 

3. 역방향 연산

도함수 ∂loss(W,B)/∂W, ∂loss(W,B)/∂B은 for 문을 돌리면서 각각의 데이터에서 계산한 기울기를 누적시켰다. 누적시키기 위해 도함수 행렬의 모양 (3, 4), (3, 1) 과 같은 0인 행렬을 만들고 for문 마지막에 더하였다.

 

def loss_gradient(X, Y, W, B):
    
    add_dL_dW = np.zeros((3, 4))
    add_dL_dB = np.zeros((3, 1))
    for i in range(len(X)):
        
        x = [X[i]]
        y = [Y[i]]

        # preprocessing data for calculate
        x = np.transpose(x )
        WXB = np.dot(W, x) + B 
    
        e_WXB1 = np.exp(WXB[0])
        e_WXB2 = np.exp(WXB[1])
        e_WXB3 = np.exp(WXB[2])
    
        sum_e_WXB = e_WXB1 + e_WXB2 + e_WXB3
    
        smax_WXB1 = e_WXB1 / sum_e_WXB
        smax_WXB2 = e_WXB2 / sum_e_WXB
        smax_WXB3 = e_WXB3 / sum_e_WXB
    
        softmax = np.array([smax_WXB1[0], smax_WXB2[0], smax_WXB3[0]])
    
        # Y가 1인 softxmax 출력만 남기기 위해 요소곱을 시행한다.
        y_target = np.sum(softmax * y, axis = 1, keepdims=True)
    
        #∂L(smax(g(W, B))) / ∂smax(g(W,B))
        dL_dsmax = -1 / y_target
    
        #smax 도함수 행렬(3x3) 
        dsmax_dg_matrix = np.array([[(smax_WXB1*(1-smax_WXB1))[0], -(smax_WXB1*smax_WXB2)[0], -(smax_WXB1*smax_WXB3)[0]], 
                                    [-(smax_WXB1*smax_WXB2)[0], (smax_WXB2*(1-smax_WXB1))[0], -(smax_WXB2*smax_WXB3)[0]],
                                    [-(smax_WXB1*smax_WXB3)[0], -(smax_WXB2*smax_WXB3)[0], (smax_WXB2*(1-smax_WXB1))[0]]])
    
        #∂smax(g(W, B)) / ∂g(W,B) -> smax 도함수 행렬(3x3)에 target Y를 곱하여 해당 값 추출
        dsmax_dg = np.dot(dsmax_dg_matrix, np.transpose(y, (1, 0)))
        #∂g(W, B) / ∂W
        dg_dW = np.transpose(x, (1, 0))
        
        # ∂smax(g(W, B)) / ∂W = ∂smax(g(W, B)) / ∂g(W,B)  * ∂g(W, B) / ∂W
        dsmax_dW = np.dot(dsmax_dg, dg_dW)
        
        # ∂L(g(W, B)) / ∂W = ∂L / ∂smax(g(W, B))  * ∂smax(g(W, B)) / ∂W
        dL_dW = dL_dsmax* dsmax_dW
        
        # ∂L(g(W, B)) / ∂W 더하여 누적
        add_dL_dW = add_dL_dW + dL_dW
        
        # ∂L(g(W, B)) / ∂B = ∂L / ∂smax(g(W, B))  * ∂smax(g(W, B)) / ∂g(W, B) * ∂g(W, B) / ∂B(-> 1임)
        dL_dB = dL_dsmax * dsmax_dg
        
        # ∂L(g(W, B)) / ∂B 더하여 누적
        add_dL_dB = add_dL_dB + dL_dB
        
    return add_dL_dW, add_dL_dB

 

 

그 외의 훈련 코드 등은 나머지와 동일하다. 전체 코드이다.

import numpy as np
from numpy import genfromtxt

np.random.seed(220106)

data = genfromtxt('IRIS_tiny_onehot.csv', delimiter=',', skip_header=1)
input = data[:, 0:4]
target = data[:, 4:7]

W = np.random.randn(3, 4)
B  = np.random.randn(3, 1)

learning_rate = 0.001

def softmax_forward(X, Y, W, B):
    
    arr_pred = []
    arr_loss = []
    
    for i in range(len(X)):
        
        x = [X[i]]
        y = [Y[i]]

        # preprocessing data for calculate
        x = np.transpose(x )
        WXB = np.dot(W, x) + B 
    
        e_WXB1 = np.exp(WXB[0])
        e_WXB2 = np.exp(WXB[1])
        e_WXB3 = np.exp(WXB[2])
    
        sum_e_WXB = e_WXB1 + e_WXB2 + e_WXB3
    
        smax_WXB1 = e_WXB1 / sum_e_WXB
        smax_WXB2 = e_WXB2 / sum_e_WXB
        smax_WXB3 = e_WXB3 / sum_e_WXB
    
        softmax = np.array([smax_WXB1[0], smax_WXB2[0], smax_WXB3[0]])
        
        arr_pred.append(softmax)
        
        y_target = np.sum(softmax * y, axis = 1, keepdims=True)
    
        loss = -np.log(y_target)
        arr_loss.append(loss)
        
    preds = np.array(arr_pred)
    losses = np.sum(np.array(arr_loss))

    return preds, losses



def loss_gradient(X, Y, W, B):
    
    add_dL_dW = np.zeros((3, 4))
    add_dL_dB = np.zeros((3, 1))
    for i in range(len(X)):
        
        x = [X[i]]
        y = [Y[i]]

        # preprocessing data for calculate
        x = np.transpose(x )
        WXB = np.dot(W, x) + B 
    
        e_WXB1 = np.exp(WXB[0])
        e_WXB2 = np.exp(WXB[1])
        e_WXB3 = np.exp(WXB[2])
    
        sum_e_WXB = e_WXB1 + e_WXB2 + e_WXB3
    
        smax_WXB1 = e_WXB1 / sum_e_WXB
        smax_WXB2 = e_WXB2 / sum_e_WXB
        smax_WXB3 = e_WXB3 / sum_e_WXB
    
        softmax = np.array([smax_WXB1[0], smax_WXB2[0], smax_WXB3[0]])
    
        # Y가 1인 softxmax 출력만 남기기 위해 요소곱을 시행한다.
        y_target = np.sum(softmax * y, axis = 1, keepdims=True)
    
        #∂L(smax(g(W, B))) / ∂smax(g(W,B))
        dL_dsmax = -1 / y_target
    
        #smax 도함수 행렬(3x3) 
        dsmax_dg_matrix = np.array([[(smax_WXB1*(1-smax_WXB1))[0], -(smax_WXB1*smax_WXB2)[0], -(smax_WXB1*smax_WXB3)[0]], 
                                    [-(smax_WXB1*smax_WXB2)[0], (smax_WXB2*(1-smax_WXB1))[0], -(smax_WXB2*smax_WXB3)[0]],
                                    [-(smax_WXB1*smax_WXB3)[0], -(smax_WXB2*smax_WXB3)[0], (smax_WXB2*(1-smax_WXB1))[0]]])
    
        #∂smax(g(W, B)) / ∂g(W,B) -> smax 도함수 행렬(3x3)에 target Y를 곱하여 해당 값 추출
        dsmax_dg = np.dot(dsmax_dg_matrix, np.transpose(y, (1, 0)))
        #∂g(W, B) / ∂W
        dg_dW = np.transpose(x, (1, 0))
        
        # ∂smax(g(W, B)) / ∂W = ∂smax(g(W, B)) / ∂g(W,B)  * ∂g(W, B) / ∂W
        dsmax_dW = np.dot(dsmax_dg, dg_dW)
        
        # ∂L(g(W, B)) / ∂W = ∂L / ∂smax(g(W, B))  * ∂smax(g(W, B)) / ∂W
        dL_dW = dL_dsmax* dsmax_dW
        
        # ∂L(g(W, B)) / ∂W 더하여 누적
        add_dL_dW = add_dL_dW + dL_dW
        
        # ∂L(g(W, B)) / ∂B = ∂L / ∂smax(g(W, B))  * ∂smax(g(W, B)) / ∂g(W, B) * ∂g(W, B) / ∂B(-> 1임)
        dL_dB = dL_dsmax * dsmax_dg
        
        # ∂L(g(W, B)) / ∂B 더하여 누적
        add_dL_dB = add_dL_dB + dL_dB
    
    return add_dL_dW, add_dL_dB


pred, loss = softmax_forward(input, target, W, B)
print('before pred', pred) 
print('before loss', loss) 
#print('before W', W) 
#print('before B', B)

for i in range(1000): 
    dL_dW, dL_dB = loss_gradient(input, target, W , B) 
    W = W + -1*learning_rate * dL_dW 
    B = B + -1*learning_rate * dL_dB


pred, loss = softmax_forward(input, target, W, B)
print('after pred', pred) 
print('after loss', loss) 
#print('after W', W) 
#print('after B', B)

 

 

 

 

4. Pytorch로 구현하기

pytorch는 다른 파일로 임포트하였는데 target 값을 원-핫 인코딩하지 않은 것이다.

(사실 코드로도 간편하게 가능한데 이런거 설명하기 싫어서... 그냥 파일 2개 만들었다.)

IRIS_tiny.csv

 

 

pytorch는 아래의 파일을 임포트한다.

IRIS_tiny.csv
0.00MB

 

 

아래는 Pytorch로 구현한 코드이다. 

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from numpy import genfromtxt

data = genfromtxt('IRIS_tiny.csv', delimiter=',', skip_header = 1)
input = data[:, 0:4]
target = data[:, -1:]

input = torch.FloatTensor(input)
target = torch.LongTensor(target).squeeze()
 
model = nn.Sequential(
    nn.Linear(4, 3),
    nn.Softmax(dim=1)
)

pred = model(input)
print('before pred', pred)
loss = F.cross_entropy(pred, target)
print('before loss', loss)
optimizer = optim.SGD(model.parameters(), lr=0.1)
epoches = 3000

for epoche in range(epoches + 1):
    pred = model(input)
    
    loss = F.cross_entropy(pred, target)
    
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()


pred = model(input)
print('after pred', pred)
loss = F.cross_entropy(pred, target)
print('after loss', loss)
print(list(model.parameters()))

 

 

 

5. 결과확인

 

굵은 숫자가 가장 확률이 높은 것이다. 첫 번째 9.14027173e-01 이라는 뜻은 9.14027173 X 10^(-1) 이라는 뜻으로 약 0.914 라고 생각하면 된다.

 

비교를 해 보자. 1열이 target 클래스로 예를 들어 1 이라는 뜻은 (0, 1, 0) 이 정답이라는 뜻이다.

 

2열은 numpy로, 3열은 pytorch로 구연하였는데 정답은 역시 pytorch가 더 가깝다.

numpy는 예측을 잘 못 한것도 많고 0.7 이상이 아닌 것도 꽤 된다. 

 

그래도 라이브러리 없이 순수 수학적으로만 구현한 것인데도 얼추 비슷하게 흉내내지 않는가?

 

중요한 것은 라이브러리 없이 수학적으로 구현 할 줄 안다는 것은 개념을 이해한 것과 같다. 그래서 기계학습 라이브러리 없이 만드는 Numpy 딥러닝을 연재하는 것이다.

 

다음 시간에는 IRIS 완전체 데이터를 이용하여 정답률을 끌어 올려보겠다.