Created
February 19, 2017 08:52
-
-
Save nicewook/76bf1b510eec28f1dd6c6728b0aef754 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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