학교 프로젝트로 MLP로 사칙연산 계산기를 구현할 수 있는지에 대한 내용입니다.
레이어를 단순히 많이 추가하거나 히든 노드를 추가한다고 해서 사칙연산 계산을 더욱 잘하지 않습니다.
정확한 사칙연산을 구현하는 모델을 만들기 위해서는 MLP에 캐리를 이해할 수 있도록 모델을 만들거나 입력 데이터나 학습데이터를 인코딩해서 LSTM 모델을 만들어야 합니다.
Seq2seq 를 이용한 사칙연산
https://github.com/hugikun999/Seq2Seq_Arithmetic
GitHub - hugikun999/Seq2Seq_Arithmetic
Contribute to hugikun999/Seq2Seq_Arithmetic development by creating an account on GitHub.
github.com
MLP 모델은 아니지만 seq2seq 를 이용한 사칙연산 모델 깃허브 링크입니다.
MLP 캐리 이용한 사칙연산
https://github.com/sungjae-cho/arithmetic-mlp
GitHub - sungjae-cho/arithmetic-mlp: We implement a multilayer perceptron (MLP) that learns arithmetic operations: addition, sub
We implement a multilayer perceptron (MLP) that learns arithmetic operations: addition, subtraction, multiplication, division, and modulo. This implementation was used in the CogSci 2019 paper titl...
github.com
캐리를 이용한 MLP 사칙연산 모델에 해당하는 깃허브 링크입니다. 하지만 이대로 구현하면 프로젝트가 커질거 같아 사칙연산 각각의 모델에 맞는 데이터로 학습을 진행한 후 값을 예측하는 모델로 구현하도록 하였습니다.
사칙연산 MLP 프로젝트
사칙연산 해당하는 모델을 4개를 만들어서 테스트 하였습니다. 코드작성은 맥북 Ventrua 13.0.1 파이썬 3.9 텐서플로우 2.5 버전을 이용해 구현했습니다.
사용한 라이브러리
from keras.models import Sequential
import numpy as np
import random
from tensorflow import keras
from keras.layers import *
from keras import backend as K
from sklearn.preprocessing import MinMaxScaler, normalize, StandardScaler
from tensorflow.keras.models import load_model
from random import randint
import tensorflow as tf
from keras.optimizer_v2.adam import Adam
from keras.optimizer_v2.adamax import Adamax
from keras.optimizer_v2.rmsprop import RMSprop
from keras.optimizer_v2.gradient_descent import SGD
from sklearn.metrics import r2_score
from functools import partial
from tensorflow.keras.callbacks import EarlyStopping
from random import randint
from random import randint
# from numpy import sign, abs, log10
from keras.callbacks import EarlyStopping
from keras.optimizer_v2.adagrad import Adagrad
import tensorflow as tf
from keras.callbacks import EarlyStopping
from sklearn.metrics import r2_score
from random import *
import pandas as pd
import matplotlib.pyplot as plt
더하기
def add_test_data(data_len,data_size):
x = np.array([np.random.uniform(-data_len,data_len,size=2)
for _ in range(data_size)])
y = np.array([[x[i][0] + x[i][1]] for i in range(data_size)])
return x,y
def add_test_data2(data_len,data_size):
x = np.array([np.random.randint(-data_len,data_len,size=2)
for _ in range(data_size)])
y = np.array([[x[i][0] + x[i][1]] for i in range(data_size)])
return x,y
더하기 학습 데이터를 만들기 위해 해당 범위에 데이터를 소수로 만들고 더한 값을 리턴하는 것과 정수로 만들고 더한 값을 함수로 만들었습니다.
add_x,add_y = add_test_data(10000,2000)
print(add_x.shape,add_y.shape)
print(add_x[0],add_y[0])
(2000, 2) (2000, 1)
[-2991.48715118 -1330.9580993 ] [-4322.44525048]
-10000,10000 사이의 2000개의 데이터를 학습 데이터로 만들었습니다.
ada = Adam(learning_rate=0.00001)
add_model = Sequential()
add_model.add(Dense(4,input_dim=2,activation="relu"))
add_model.add(Dense(4,activation="relu"))
add_model.add(Dense(1,activation="linear"))
add_model.compile(loss="mse",optimizer=ada,metrics=["mae"])
epochs = 15000
add_history = add_model.fit(add_x, add_y, epochs = epochs)
add_model.summary()
아담의 러닝레이트를 0.00001 로 변경한 후 15000번 학습을 진행했습니다. 값을 예측하는 것으로 loss 함수로는 mse 를 사용했습니다.
data_test = [10,100,1000,10000,100000,10000000]
for i in data_test:
idx = randint(1,1000)
test_x,test_y = add_test_data2(i,1000)
pred_add_y = add_model.predict(test_x)
print("데이터 길이" , i ,": r2 스코어", r2_score(test_y,pred_add_y))
print(test_x[idx][0], "+",test_x[idx][1] ,"= " )
print("예측 " , add_model.predict(np.array([[test_x[idx]]])))
print("정답", test_y[idx])
데이터 길이 10 : r2 스코어 0.9995283116872599
3 + 5 =
예측 [[[8.00057]]]
정답 [8]
데이터 길이 100 : r2 스코어 0.9999994825651735
-30 + 14 =
예측 [[[-16.000074]]]
정답 [-16]
데이터 길이 1000 : r2 스코어 0.9999999997991281
182 + -142 =
예측 [[[40.00052]]]
정답 [40]
데이터 길이 10000 : r2 스코어 0.9999999997057253
-5545 + 6655 =
예측 [[[1110.002]]]
정답 [1110]
데이터 길이 100000 : r2 스코어 0.9999999998072446
22267 + 43341 =
예측 [[[65608.04]]]
정답 [65608]
데이터 길이 10000000 : r2 스코어 0.9999999997204302
-676656 + 4094890 =
예측 [[[3418237.]]]
정답 [3418234]
데이터 길이 백만 자리까지 어느정도 예측을 잘하는 것을 확인할 수 있습니다.
빼기
def sub_test_data(data_len,data_size):
x = np.array([np.random.uniform(-data_len,data_len,size=2)
for _ in range(data_size)])
y = np.array([[x[i][0] - x[i][1]] for i in range(data_size)])
return x,y
def sub_test_data2(data_len,data_size):
x = np.array([np.random.randint(-data_len,data_len,size=2)
for _ in range(data_size)])
y = np.array([[x[i][0] - x[i][1]] for i in range(data_size)])
return x,y
sub_x,sub_y = sub_test_data(10000,2000)
print(sub_x.shape,sub_y.shape)
print(sub_x[0],sub_y[0])
(2000, 2) (2000, 1)
[6598.25901385 3771.3394317 ] [2826.91958215]
더하기 모델과 동일하나 학습 데이터의 출력 데이터로 두 값을 뺀 값을 이용했습니다.
모델 구성은 동일합니다.
ada = Adam(learning_rate=0.00001)
sub_model = Sequential()
sub_model.add(Dense(4, activation='relu', input_dim=2))
sub_model.add(Dense(4,activation="relu"))
sub_model.add(Dense(1))
sub_model.compile(loss= "mse", optimizer=ada, metrics =['mae'])
sub_history = sub_model.fit(sub_x, sub_y, epochs = epochs)
sub_model.summary()
data_test = [10,100,1000,10000,100000,1000000]
for i in data_test:
idx = randint(1,1000)
sub_test_x, sub_test_y = sub_test_data2(i,1000)
pred_add_y = sub_model.predict(sub_test_x)
print("데이터 길이" , i ,": r2 스코어", r2_score(sub_test_y,pred_add_y))
print(sub_test_x[idx][0], "-",sub_test_x[idx][1] ,"= " )
print("예측 " , sub_model.predict(np.array([[sub_test_x[idx]]])))
print("정답", sub_test_y[idx])
예측 [[[-10.001922]]]
정답 [-10]
데이터 길이 100 : r2 스코어 0.9999999996278489
14 - 6 =
예측 [[[8.00027]]]
정답 [8]
데이터 길이 1000 : r2 스코어 0.9999999999966795
349 - -313 =
예측 [[[662.0006]]]
정답 [662]
데이터 길이 10000 : r2 스코어 0.9999999999999264
-8116 - -9565 =
예측 [[[1449.001]]]
정답 [1449]
데이터 길이 100000 : r2 스코어 0.9999999999999089
85430 - -73870 =
예측 [[[159300.06]]]
정답 [159300]
데이터 길이 1000000 : r2 스코어 0.9999999999999319
538642 - -111852 =
예측 [[[650494.2]]]
정답 [650494]
백만자리 까지 어느정도 예측을 하는 것을 확인할 수 있습니다.
곱하기 나누기 구현 방법
곱하기 나누기 같은 경우 MLP 가 학습하기 어렵습니다. 왜냐하면 값이 너무커지거나 너무 작아지는 문제가 발생하기 때문입니다. 따라서 학습을 하기 위해서는 두가지 방법을 이용할 수 있습니다.
1. -10~10 사이의 데이터로 학습을 한 후 큰 값이 들어왔을 때 전처리를 통해 -10~10 으로 변환한 후 나중에 자리 수를 곱하는 방법
2. 로그를 덧셈 곱셉을 이용하는 방법
저는 로그를 이용해 곱셈과 나눗셈을 구현했습니다.
로그를 씌워서 더한 값을 나중에 exp로 역변환 시키면 곱한 값
로그를 씌워서 뺀 값을 나중에 exp로 역변환 시키면 나눈 값
곱하기
x_mul = np.array([[np.log(np.random.uniform(1,10000))]
for _ in range(2000)],dtype=float)
x2_mul = np.array([[np.log(np.random.uniform(1,10000))]
for _ in range(2000)],dtype=float)
data_mul = np.concatenate((x_mul, x2_mul), axis=1)
mul_x = data_mul
mu_mul = np.add(np.nan_to_num(x_mul),np.nan_to_num(x2_mul))
mul_y = mu_mul
print(mul_x.shape)
print(mul_y.shape)
print(mul_x[0] , mul_y[0])
(2000, 2)
(2000, 1)
[5.7548703 8.44985106] [14.20472136]
로그로 변환한 두 수를 입력, 더한 값을 출력 데이터로 학습 데이터를 만들었습니다.
ada = Adam(learning_rate=0.000005)
mul_model = Sequential()
mul_model.add(Dense(2,input_dim=2,activation=tf.nn.leaky_relu))
mul_model.add(Dense(4,activation=tf.nn.leaky_relu))
mul_model.add(Dense(1,activation="linear"))
mul_model.compile(loss= "mse", optimizer=ada, metrics = ('mae'))
mul_history = mul_model.fit(mul_x, mul_y, epochs = epochs)
mul_model.summary()
def mul_test_data(data_len,data_size):
x, x2,state = [],[],[]
y_true = []
x_div_true = []
for i in range(data_size):
a = np.random.randint(-data_len,data_len)
a2 = np.random.randint(-data_len,data_len)
x_div_true.append([a,a2])
y_true.append([a*a2])
if a < 0 and a2 < 0 :
x.append([np.log(abs(a))])
x2.append([np.log(abs(a2))])
state.append([1])
elif a > 0 and a2 < 0 :
x.append([np.log(a)])
x2.append([np.log(abs(a2))])
state.append([-1])
elif a < 0 and a2 > 0 :
x.append([np.log(abs(a))])
x2.append([np.log(a2)])
state.append([-1])
elif a < 0 and a2 == 0 :
x.append([np.log(abs(a))])
x2.append([np.log(1)])
state.append([0])
elif a > 0 and a2 == 0 :
x.append([np.log(a)])
x2.append([np.log(1)])
state.append([0])
elif a == 0 and a2 < 0 :
x.append([np.log(1)])
x2.append([np.log(abs(a2))])
state.append([0])
elif a == 0 and a2 > 0 :
x.append([np.log(1)])
x2.append([np.log(a2)])
state.append([0])
elif a==0 and a2 == 0 :
x.append([np.log(1)])
x2.append([np.log(1)])
state.append([0])
else:
x.append([np.log(a)])
x2.append([np.log(a2)])
state.append([1])
x = np.array(x)
x2 = np.array(x2)
state = np.array(state)
y_true = np.array(y_true)
x_div_true = np.array(x_div_true)
x = np.concatenate((x, x2), axis=1)
y = np.array([[x[i][0] + x[i][1]] for i in range(data_size)])
return x,y,y_true,state,x_div_true
테스트 데이터를 만드는 함수입니다. 여기서 데이터 범위와 길이를 입력받으면 그 범위에 해당하는 값을 로그로 변환하는 함수입니다.
여기서 숫자 데이터가 들어올 때 로그로 씌우기 위해서 값이 마이너스일때는 절대 값을 취한 후 스테이트에 음수인지 양수인지를 저장하고 0인 경우 스테이트에 0을 저장합니다. 리턴하는 값으로는 순서대로 로그씌운 두개의 데이터, 로그씌운 두 개 데이터 더한 값, 실제 y 값, 스테이트 값, 실제 두 개의 값을 반환합니다.
def predMulData(x,y,true_y,s):
pred_mul_y_real = np.exp(mul_model.predict(x)) * s
pred_mul_y_before = mul_model.predict(x)
print("log(x) + log(x2) r2 스코어 : ", r2_score(y,pred_mul_y_before))
print("x * x2 r2 스코어 : ", r2_score(true_y,pred_mul_y_real))
return pred_mul_y_real , true_y
로그로 변환하기 전과 로그로 변환한 후 테스트 데이터에 대한 r2 score 를 표시하도록 하고 예측값과 실제 값을 리턴하도록 하였습니다.
data_len = [10,100,1000,10000,100000]
for i in data_len:
idx = randint(0,100)
test_mul_x, test_mul_y,true_mul_pred,state,x_true = mul_test_data(i,10000)
pred_mul_y , true_mul_y =predMulData(test_mul_x, test_mul_y,true_mul_pred,state)
print(x_true[idx][0] ," * " ,x_true[idx][1])
print("예측" , pred_mul_y[idx])
print("정답" , true_mul_y[idx])
log(x) + log(x2) r2 스코어 : 0.9999999917638123
x * x2 r2 스코어 : 0.999999999995208
-8 * 2
예측 [-15.99998093]
정답 [-16]
log(x) + log(x2) r2 스코어 : 0.9999999999515313
x * x2 r2 스코어 : 0.9999999999996181
84 * 95
예측 [7979.99853516]
정답 [7980]
log(x) + log(x2) r2 스코어 : 0.9999999999997814
x * x2 r2 스코어 : 0.99999999999948
193 * 630
예측 [121589.9140625]
정답 [121590]
log(x) + log(x2) r2 스코어 : 0.9999999999990874
x * x2 r2 스코어 : 0.9999999999965247
-5227 * -2039
예측 [10657883.]
정답 [10657853]
log(x) + log(x2) r2 스코어 : 0.9999999999969964
x * x2 r2 스코어 : 0.9999999999926179
-33081 * -73414
예측 [2.42861312e+09]
정답 [2428608534]
십만자리 까지 어느정도 예측을 잘하는 것을 확인 할 수 있습니다.
나누기
나누기 모델도 곱셉모델과 동일하게 만들고 학습 데이터의 출력 값을 뺀 값으로 학습한 후 테스트 데이터에서는 두 값을 뺀 값으로 예측을 진행했습니다.
data_len = [10,100,1000,100000,1000000]
for i in data_len:
idx = randint(0,100)
test_div_x,test_div_y,true_div_tar,div_state,true_div_x = div_test_data(i,100)
pred_div_y , true_div_y = predDivData(test_div_x,test_div_y,true_div_tar,div_state)
print(true_div_x[idx][0] , " / ",true_div_x[idx][1])
print("예측 : ", pred_div_y[idx])
print("정답 : ", true_div_y[idx])
ln(x) - ln(x2)와 예측 값 r2 스코어: 0.9999999797953127
x / x2와 예측 값 r2 스코어 : 0.9999999769455697
-4 / -2
예측 : [1.99970818]
정답 : 2.0
ln(x) - ln(x2)와 예측 값 r2 스코어: 0.9999999698870051
x / x2와 예측 값 r2 스코어 : 0.9999999612288138
-40 / 81
예측 : [-0.49371755]
정답 : -0.49382716049382713
ln(x) - ln(x2)와 예측 값 r2 스코어: 0.9999999548110798
x / x2와 예측 값 r2 스코어 : 0.9999999567694318
-282 / 978
예측 : [-0.28826344]
정답 : -0.2883435582822086
ln(x) - ln(x2)와 예측 값 r2 스코어: 0.9999999296258759
x / x2와 예측 값 r2 스코어 : 0.9999998568435721
-66559 / 96316
예측 : [-0.69076192]
정답 : -0.6910482162880518
ln(x) - ln(x2)와 예측 값 r2 스코어: 0.9999998549905774
x / x2와 예측 값 r2 스코어 : 0.9999998063105853
-987578 / 715714
예측 : [-1.37918794]
정답 : -1.3798500518363481
이것도 마찬가지로 10만자리까지 잘 예측하는 것을 확인할 수 있습니다.
결과
실제로 MLP로 사칙연산 모델을 만드는 것은 비효율적입니다. 그래도 이 프로젝트를 진행하면서 학습데이터를 수정하면서 원하는 결과를 얻기 위한 과정을 반복하면서 MLP에 대한 이해를 높일 수 있었습니다. 아래 코드는 실제로 구현한 코드에 대한 깃허브 링크입니다.
GitHub - dkekzhs/machine-learning-study
Contribute to dkekzhs/machine-learning-study development by creating an account on GitHub.
github.com
real_calcul_learnning.ipynb : 실제로 학습한 결과
predictCalCul.ipynb : 저장된 모델을 불러와서 값 예측
'머신러닝 > 프로젝트' 카테고리의 다른 글
AI를 활용해 무료로 음악 생성해보기 (4) | 2024.11.29 |
---|---|
독버섯 분류 (0) | 2022.12.21 |