이전시간에 언급한 대로, Iris 꽃 데이터 중 일부(30개)를 불러와 학습해 볼 것이다.
아래 파일을 임포트한다. 사용하기 쉽게 미리 원-핫 인코딩을 하였다.
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개 만들었다.)
pytorch는 아래의 파일을 임포트한다.
아래는 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 완전체 데이터를 이용하여 정답률을 끌어 올려보겠다.
'파이썬 프로그래밍 > Numpy 딥러닝' 카테고리의 다른 글
11. 단층 퍼셉트론의 한계-1.XOR 문제 (0) | 2022.01.11 |
---|---|
10. 단층 퍼셉트론 (0) | 2022.01.10 |
8. 다중 분류 구현하기(기초실습) (0) | 2022.01.09 |
7. 다중 분류 구현하기(이론) (0) | 2022.01.08 |
6. 소프트맥스(softmax) 함수 탐구 (2) | 2022.01.07 |