Skip to content

Instantly share code, notes, and snippets.

@nicewook
Created February 19, 2017 08:52
Show Gist options
  • Save nicewook/76bf1b510eec28f1dd6c6728b0aef754 to your computer and use it in GitHub Desktop.
Save nicewook/76bf1b510eec28f1dd6c6728b0aef754 to your computer and use it in GitHub Desktop.
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.5.2 (C:\Program Files\Anaconda3\python.exe)" project-jdk-type="Python SDK" />
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/rnn_tutorial.iml" filepath="$PROJECT_DIR$/.idea/rnn_tutorial.iml" />
</modules>
</component>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.5.2 (C:\Program Files\Anaconda3\python.exe)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>
# numpy와 copy를 import 하고 random 값의 seed를 심는다
import copy, numpy as np
np.random.seed(0)
# compute sigmoid nonlinearity
# 시그모이드 함수 구현
def sigmoid(x):
output = 1 / (1 + np.exp(-x))
return output
# convert output of sigmoid function to its derivative
# 시그모이드 함수의 미분
def sigmoid_output_to_derivative(output):
return output * (1 - output)
##################################################################################
# training dataset generation
int2binary = {} # 훈련시키기 위한 값들을 생성하고 이를 lookup table로 만든다
binary_dim = 8 # 일단 이진수의 최대 자리수는 8자리로 한다. 잘 구현했다면 자리수를 더 올려도 될것이다.
largest_number = pow(2, binary_dim) # 따라서 가장 큰 숫자는 2^8이 될 것이다.
##################################################################################
# 이제 lookup table을 만들자
# 우선 binary에 2진수값을 만들어서 넣어준다
# numpy.unpackbits는 숫자를 2진수 리스트로 만들어준다. 예를 들어 [2]라면 [0,0,0,0,0,0,1,0] 으로 바꿔준다
# https://docs.scipy.org/doc/numpy/reference/generated/numpy.unpackbits.html
binary = np.unpackbits(np.array([range(largest_number)], dtype=np.uint8).T, axis=1)
# 이걸 딕셔너리에 넣어준다.
# 이러면 int2binary[2] 라고 하면 2의 binary 모양인 [0,0,0,0,0,0,1,0] 를 토해내게 된다.
# 이건 큰 의미가 없을 수도 있는데 우리가 뭘 하고 있는지 명확히 보여주기 위해서 변환했다.
for i in range(largest_number):
int2binary[i] = binary[i]
##################################################################################
# input variables
# 하이퍼 파라미터들이다.
alpha = 0.1 # learning rate는 0.1이다.
input_dim = 2 # 입력 계층은 2개이다.
# hidden 계층은 16개 이며 이놈들이 carry 정보를 저장하게 될 것이다.
# 현재 8자리만 다루니 16개일 필요가 없어 보이지만 이 값을 바꿔가며 얼마나 빨리 최종값을 찾아가는데 테스트 해보자 (중요)
hidden_dim = 16
output_dim = 1 # 각 자리수의 연산결과가 여기에 나올 것이다.
# initialize neural network weights
# 이제 weight 값들을 정해준다. 랜덤으로 만든 값들에 2를 곱해주고 1을 빼준다.
synapse_0 = 2 * np.random.random((input_dim, hidden_dim)) - 1 # 이건 input 과 hidden 사이의 weight들
synapse_1 = 2 * np.random.random((hidden_dim, output_dim)) - 1 # 이건 hidden 과 output 사이의 weight들
synapse_h = 2 * np.random.random((hidden_dim, hidden_dim)) - 1 # 끝으로, hidden과 이전 timestep hidden 사이의 weight들
# weight update 용도의 공간들
synapse_0_update = np.zeros_like(synapse_0)
synapse_1_update = np.zeros_like(synapse_1)
synapse_h_update = np.zeros_like(synapse_h)
##################################################################################
# training logic
# 드디어 훈련이다.
for j in range(10000): # 10000번 돌린다
# generate a simple addition problem (a + b = c)
# a와 b를 생성한다. a, b는 최대 크기의 절반이 안되는 랜덤 정수값이다. 따라서 더해도 largest number를 넘지 않는다
a_int = np.random.randint(largest_number / 2) # int version
a = int2binary[a_int] # binary encoding
b_int = np.random.randint(largest_number / 2) # int version
b = int2binary[b_int] # binary encoding
# true answer
# 우선 정답을 확보해놓자. 나중에 예측값과 비교를 할 것이다.
c_int = a_int + b_int
c = int2binary[c_int]
# where we'll store our best guess (binary encoded)
# c에는 정답이 들어있고, d에는 우리의 위대한 RNN의 예측값을 넣을 것이다. 뭐 이건 안만들어도 큰 상관은 없지만.
d = np.zeros_like(c)
overallError = 0 # 에러값을 저장할 공간
# 각 time step마다 layer_2의 미분값과 layer_1의 값을 추적할 리스트이다.
layer_2_deltas = list()
layer_1_values = list()
# layer_2_deltas는 output이니 길이가 맞지만
# layer_1_values는 처음에는 이전 hidden이 없으니 0으로 한다
layer_1_values.append(np.zeros(hidden_dim))
##################################################################################
# moving along the positions in the binary encoding
# 각 자리수별로 for문으로 돌면서 수행을 한다.
for position in range(binary_dim):
# generate input and output
# 각 자리수별 입력값과 정답을 X, y에 넣는다.
X = np.array([[a[binary_dim - position - 1], b[binary_dim - position - 1]]])
y = np.array([[c[binary_dim - position - 1]]]).T
# hidden layer (input ~+ prev_hidden) 이 부분이 RNN의 매우 중요한 부분이다
# hidden layer 연산이다. input X와 synapse_0(=W0) 을 곱해주고,
# 이전의(= -1 = 가장 마지막의) hidden layer 값과 Wh를 곱해준다.
# 그리고 두 값을 더한뒤 sigmoid 먹여준다. 더해준다는 것이 중요하다
layer_1 = sigmoid(np.dot(X, synapse_0) + np.dot(layer_1_values[-1], synapse_h))
# output layer (new binary representation)
# output layer는 이렇게 나온 layer_1 값과 synapse_1(= W1) 값을 곱해준뒤 sigmoid 해준다
layer_2 = sigmoid(np.dot(layer_1, synapse_1))
# did we miss?... if so, by how much?
# 이제 예측값인 layer_2와 실제 답인 y를 비교하여 loss를 계산해낸다.
layer_2_error = y - layer_2
# layer_2, 즉, output의 미분값을 저장하는 부분이다. sigmoid 함수의 미분공식에 따랐다.
layer_2_deltas.append((layer_2_error) * sigmoid_output_to_derivative(layer_2))
# layer_2_error[0]은 무얼 의미하는 것일까? 행렬의 0번째 열이라는 의미구나
# 아무튼 모든 자리수의 error들을 누적해서 overallError에 저장한다
overallError += np.abs(layer_2_error[0])
# decode estimate so we can print it out
# 예측값을 round해서 0인지 1인지 저장한다
d[binary_dim - position - 1] = np.round(layer_2[0][0])
# store hidden layer so we can use it in the next timestep
# 여기서 계산한 layer_1의 값을 copy하여 저장해둔다. 그래서 다음 자리수의 계산시 preb_hidden으로 사용한다
layer_1_values.append(copy.deepcopy(layer_1))
# 모든 루프를 돌면 모든 자릿수에 대하여 forward propagation이 완료된 것이다.
# 이건 어디에 쓰는 놈인지 이따 보자
future_layer_1_delta = np.zeros(hidden_dim)
##################################################################################
# 이제 back propagation을 할 차례이다
for position in range(binary_dim):
##################################################################################
#일단 보관해뒀던 값들을 다 꺼낸다
X = np.array([[a[position], b[position]]]) # 이번엔 맨 왼쪽의 가장 높은 자리수부터 계산해야겠지?
layer_1 = layer_1_values[-position - 1] # 가장 나중 time step의 hidden layer의 값을 꺼내고
prev_layer_1 = layer_1_values[-position - 2] # 그 하나전의 prev hidden layer의 값도 꺼낸다
# error at output layer
# 현재 자릿수의 output layer의 미분한 값값
layer_2_delta = layer_2_deltas[-position - 1]
##################################################################################
# error at hidden layer
# 이제 현재 자리수의(=현재 time step)의 hidden layer의 미분값을 계산한다
# 다음 layer에서 내려오는 값과 synapse_h(=Wh) 값을 곱하고
# output layer의 미분값과 synapse_1(=W1)값을 곱한 값을 다시 layer_1의 미분값과 곱해준다.
# 이부분은 어렵다. 차근히 봐야겠다
layer_1_delta = (future_layer_1_delta.dot(synapse_h.T) + layer_2_delta.dot(
synapse_1.T)) * sigmoid_output_to_derivative(layer_1)
# let's update all our weights so we can try again
# 이제 weight 값들을 업데이트 해줄 시간이다.
synapse_1_update += np.atleast_2d(layer_1).T.dot(layer_2_delta)
synapse_h_update += np.atleast_2d(prev_layer_1).T.dot(layer_1_delta)
synapse_0_update += X.T.dot(layer_1_delta)
future_layer_1_delta = layer_1_delta
# 모두 다 업데이트 해준다. alpha 라는 learning_rate에 맞춰서 update
# 이렇게 모든 자리수의 backpropagation이 끝난뒤에 update 하는 것이다.
synapse_0 += synapse_0_update * alpha
synapse_1 += synapse_1_update * alpha
synapse_h += synapse_h_update * alpha
# 그리고 다시 초기화
synapse_0_update *= 0
synapse_1_update *= 0
synapse_h_update *= 0
##################################################################################
# print out progress
# 1000번마다 중간 진행 사항을 update 하자
if (j % 1000 == 0):
print ("Error:" + str(overallError))
print ("Pred:" + str(d)) # 예측값
print ("True:" + str(c)) # 실제값
final_check = np.equal(d,c)
print (np.sum(final_check) == binary_dim)
out = 0
for index, x in enumerate(reversed(d)):
out += x * pow(2, index)
print (str(a_int) + " + " + str(b_int) + " = " + str(out))
print ("------------")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment