#!/usr/bin/env python
# coding: utf-8
# # 1. RNN의 문제점
#
#
# 1.1 BPTT(BackPropagation Through TIme)의 문제
# 저번 시간에 다루었던 BPTT는 아래의 그림과 같이 모든 타임스템마다 처음부터 끝까지 역전파를 한다.
# ![BPTT.PNG](attachment:BPTT.PNG)
# 하지만 시간이 길 경우, 역전파의 길이는 길어진다. 즉, 깊은 네트워크가 형성된다.
# 깊은 네트워크는 Gradient Vanishing & Exploding 문제가 발생할 가능성이 크다.
# 깊은 네트워크는 역전파의 계산량이 많아지기 때문에 학습시간이 오래 걸린다
# 1.2 장기 의존성(Long_Term Dependency) 문제
# RNN은 타임 스템t에서 이전 타임 스템(t - 1)의 상태를 입력으로 받는 구조 --> 이전의 정보가 현재의 타임 스템 t에 영향을 줄 수 있음
# 장기 의존성: 이론적으로 모든 이전 타임 스텝이 영향을 주지만 앞쪽의 타임 스템은 타음 스템이 길어질 수록 영향을 주지 못함
# ![GV.PNG](attachment:GV.PNG)
# # 2. LSTM
#
# LSTM(Long Short-Term Memory)는 1995년에 제안된 구조로써 RNN의 장기 의존성 문제를 해결하고 학습 속도를 높임
# 모든 RNN은 신경망의 반복적인 모듈 체인의 형대를 갖음
# LSTM에는 모듈 체인과 같은 구조가 있지만 1개의 신경 네트워크 계층을 갖는 대신 4개의 신경 네트워크 계층을 가짐
# ![%EA%B7%B8%EB%A6%BC1.png](attachment:%EA%B7%B8%EB%A6%BC1.png)
# LSTM의 과정
# LSTM는 두개의 input data ht와 ct가 있음, ct는 장기적인 기억, ht는 단기적인 기억을 저장
# LSTM의 핵심은 ct에서 기억할 부분, 삭제할 부분, 그리고 읽어 들일 부분을 학습하는 것
# ct는 셀의 왼쪽에서 오른쪽으로 통과하면서 forget gate와 input gate를 거침으로써 기억을 일부 잃고 얻음
# 과정을 거친 ct는 다시 tanh함수로 전달되어 ht와 yt를 만드는데 기반이 됨
# ![overview.PNG](attachment:overview.PNG)
# forget gate layer
#
# 어떤 정보를 버릴지 선택하는 과정
# ft에 의해 제어되며 장기 장태ct를 얼마나 삭제할지 제어
#
# input gate layer
#
# it에 의해 제어되며 gt의 어느 부분이 장기 상태ct에 더해져야 하는지 제어
# 기존 RNN의 셀과 같은 형태를 취함
#
# output gate layer
# ot는 장기 상태 ct의 어느 부분을 읽어서 ht와 yt로 출력해야 하는지 제어
# # LSTM 예제
#
# 파란색 박스가 입력값이고, 빨간색 박스가 우리가 원하는 출력값입니다.
# 1~4번째 음표를 데이터로 5번째 음표를 라벨값으로 학습을 시킵니다.
# 다음에는 2~5번째 음표를 데이터로 6번째 음표를 라벨값으로 학습을 시킵니다.
# 이후 한 음표씩 넘어가면서 노래 끝까지 학습시킵니다.
# ![%EC%95%85%EB%B3%B4%20LSTM.PNG](attachment:%EC%95%85%EB%B3%B4%20LSTM.PNG)
# In[62]:
code2idx = {'c4':0, 'd4':1, 'e4':2, 'f4':3, 'g4':4, 'a4':5, 'b4':6,
'c8':7, 'd8':8, 'e8':9, 'f8':10, 'g8':11, 'a8':12, 'b8':13}
idx2code = {0:'c4', 1:'d4', 2:'e4', 3:'f4', 4:'g4', 5:'a4', 6:'b4',
7:'c8', 8:'d8', 9:'e8', 10:'f8', 11:'g8', 12:'a8', 13:'b8'}
# In[63]:
import numpy as np
def seq2dataset(seq, window_size):
dataset = []
for i in range(len(seq)-window_size):
subset = seq[i:(i+window_size+1)]
dataset.append([code2idx[item] for item in subset])
return np.array(dataset)
# In[64]:
seq = ['g8', 'e8', 'e4', 'f8', 'd8', 'd4', 'c8', 'd8', 'e8', 'f8', 'g8', 'g8', 'g4',
'g8', 'e8', 'e8', 'e8', 'f8', 'd8', 'd4', 'c8', 'e8', 'g8', 'g8', 'e8', 'e8', 'e4',
'd8', 'd8', 'd8', 'd8', 'd8', 'e8', 'f4', 'e8', 'e8', 'e8', 'e8', 'e8', 'f8', 'g4',
'g8', 'e8', 'e4', 'f8', 'd8', 'd4', 'c8', 'e8', 'g8', 'g8', 'e8', 'e8', 'e4']
dataset = seq2dataset(seq, window_size = 4)
print(dataset.shape)
print(dataset)
# In[65]:
# 0. 사용할 패키지 불러오기
import keras
import numpy as np
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.utils import np_utils
# 랜덤시드 고정시키기
np.random.seed(5)
# 손실 이력 클래스 정의
class LossHistory(keras.callbacks.Callback):
def init(self):
self.losses = []
def on_epoch_end(self, batch, logs={}):
self.losses.append(logs.get('loss'))
# 데이터셋 생성 함수
def seq2dataset(seq, window_size):
dataset = []
for i in range(len(seq)-window_size):
subset = seq[i:(i+window_size+1)]
dataset.append([code2idx[item] for item in subset])
return np.array(dataset)
# 1. 데이터 준비하기
# 코드 사전 정의
code2idx = {'c4':0, 'd4':1, 'e4':2, 'f4':3, 'g4':4, 'a4':5, 'b4':6,
'c8':7, 'd8':8, 'e8':9, 'f8':10, 'g8':11, 'a8':12, 'b8':13}
idx2code = {0:'c4', 1:'d4', 2:'e4', 3:'f4', 4:'g4', 5:'a4', 6:'b4',
7:'c8', 8:'d8', 9:'e8', 10:'f8', 11:'g8', 12:'a8', 13:'b8'}
# 시퀀스 데이터 정의
seq = ['g8', 'e8', 'e4', 'f8', 'd8', 'd4', 'c8', 'd8', 'e8', 'f8', 'g8', 'g8', 'g4',
'g8', 'e8', 'e8', 'e8', 'f8', 'd8', 'd4', 'c8', 'e8', 'g8', 'g8', 'e8', 'e8', 'e4',
'd8', 'd8', 'd8', 'd8', 'd8', 'e8', 'f4', 'e8', 'e8', 'e8', 'e8', 'e8', 'f8', 'g4',
'g8', 'e8', 'e4', 'f8', 'd8', 'd4', 'c8', 'e8', 'g8', 'g8', 'e8', 'e8', 'e4']
# 2. 데이터셋 생성하기
dataset = seq2dataset(seq, window_size = 4)
print(dataset.shape)
# 입력(X)과 출력(Y) 변수로 분리하기
x_train = dataset[:,0:4]
y_train = dataset[:,4]
max_idx_value = 13
# 입력값 정규화 시키기
x_train = x_train / float(max_idx_value)
# 입력을 (샘플 수, 타입스텝, 특성 수)로 형태 변환
x_train = np.reshape(x_train, (50, 4, 1))
# 라벨값에 대한 one-hot 인코딩 수행
y_train = np_utils.to_categorical(y_train)
one_hot_vec_size = y_train.shape[1]
print("one hot encoding vector size is ", one_hot_vec_size)
# 3. 모델 구성하기
model = Sequential()
model.add(LSTM(128, input_shape = (4, 1)))
model.add(Dense(one_hot_vec_size, activation='softmax'))
# 4. 모델 학습과정 설정하기
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
history = LossHistory() # 손실 이력 객체 생성
history.init()
# 5. 모델 학습시키기
model.fit(x_train, y_train, epochs=2000, batch_size=14, verbose=2, callbacks=[history])
# 6. 학습과정 살펴보기
get_ipython().run_line_magic('matplotlib', 'inline')
import matplotlib.pyplot as plt
plt.plot(history.losses)
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train'], loc='upper left')
plt.show()
# 7. 모델 평가하기
scores = model.evaluate(x_train, y_train)
print("%s: %.2f%%" %(model.metrics_names[1], scores[1]*100))
# 8. 모델 사용하기
pred_count = 50 # 최대 예측 개수 정의
# 한 스텝 예측
seq_out = ['g8', 'e8', 'e4', 'f8']
pred_out = model.predict(x_train)
for i in range(pred_count):
idx = np.argmax(pred_out[i]) # one-hot 인코딩을 인덱스 값으로 변환
seq_out.append(idx2code[idx]) # seq_out는 최종 악보이므로 인덱스 값을 코드로 변환하여 저장
print("one step prediction : ", seq_out)
# 곡 전체 예측
seq_in = ['g8', 'e8', 'e4', 'f8']
seq_out = seq_in
seq_in = [code2idx[it] / float(max_idx_value) for it in seq_in] # 코드를 인덱스값으로 변환
for i in range(pred_count):
sample_in = np.array(seq_in)
sample_in = np.reshape(sample_in, (1, 4, 1)) # 샘플 수, 타입스텝 수, 속성 수
pred_out = model.predict(sample_in)
idx = np.argmax(pred_out)
seq_out.append(idx2code[idx])
seq_in.append(idx / float(max_idx_value))
seq_in.pop(0)
print("full song prediction : ", seq_out)
# # 상태유지 LSTM
# ![%EC%83%81%ED%83%9C%EC%9C%A0%EC%A7%80%20LSTM.PNG](attachment:%EC%83%81%ED%83%9C%EC%9C%A0%EC%A7%80%20LSTM.PNG)
# In[12]:
# 0. 사용할 패키지 불러오기
import keras
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, LSTM
from keras.utils import np_utils
# 랜덤시드 고정시키기
np.random.seed(5)
# 손실 이력 클래스 정의
class LossHistory(keras.callbacks.Callback):
def init(self):
self.losses = []
def on_epoch_end(self, batch, logs={}):
self.losses.append(logs.get('loss'))
# 데이터셋 생성 함수
def seq2dataset(seq, window_size):
dataset = []
for i in range(len(seq)-window_size):
subset = seq[i:(i+window_size+1)]
dataset.append([code2idx[item] for item in subset])
return np.array(dataset)
# 1. 데이터 준비하기
# 코드 사전 정의
code2idx = {'c4':0, 'd4':1, 'e4':2, 'f4':3, 'g4':4, 'a4':5, 'b4':6,
'c8':7, 'd8':8, 'e8':9, 'f8':10, 'g8':11, 'a8':12, 'b8':13}
idx2code = {0:'c4', 1:'d4', 2:'e4', 3:'f4', 4:'g4', 5:'a4', 6:'b4',
7:'c8', 8:'d8', 9:'e8', 10:'f8', 11:'g8', 12:'a8', 13:'b8'}
# 시퀀스 데이터 정의
seq = ['g8', 'e8', 'e4', 'f8', 'd8', 'd4', 'c8', 'd8', 'e8', 'f8', 'g8', 'g8', 'g4',
'g8', 'e8', 'e8', 'e8', 'f8', 'd8', 'd4', 'c8', 'e8', 'g8', 'g8', 'e8', 'e8', 'e4',
'd8', 'd8', 'd8', 'd8', 'd8', 'e8', 'f4', 'e8', 'e8', 'e8', 'e8', 'e8', 'f8', 'g4',
'g8', 'e8', 'e4', 'f8', 'd8', 'd4', 'c8', 'e8', 'g8', 'g8', 'e8', 'e8', 'e4']
# 2. 데이터셋 생성하기
dataset = seq2dataset(seq, window_size = 4)
print(dataset.shape)
# 입력(X)과 출력(Y) 변수로 분리하기
x_train = dataset[:,0:4]
y_train = dataset[:,4]
max_idx_value = 13
# 입력값 정규화 시키기
x_train = x_train / float(max_idx_value)
# 입력을 (샘플 수, 타임스텝, 특성 수)로 형태 변환
x_train = np.reshape(x_train, (50, 4, 1))
# 라벨값에 대한 one-hot 인코딩 수행
y_train = np_utils.to_categorical(y_train)
one_hot_vec_size = y_train.shape[1]
print("one hot encoding vector size is ", one_hot_vec_size)
# 3. 모델 구성하기
model = Sequential()
model.add(LSTM(128, batch_input_shape = (1, 4, 1), stateful=True))
model.add(Dense(one_hot_vec_size, activation='softmax'))
# 4. 모델 학습과정 설정하기
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# 5. 모델 학습시키기
num_epochs = 2000
history = LossHistory() # 손실 이력 객체 생성
history.init()
for epoch_idx in range(num_epochs):
print ('epochs : ' + str(epoch_idx) )
model.fit(x_train, y_train, epochs=1, batch_size=1, verbose=2, shuffle=False, callbacks=[history]) # 50 is X.shape[0]
model.reset_states()
# 6. 학습과정 살펴보기
get_ipython().run_line_magic('matplotlib', 'inline')
import matplotlib.pyplot as plt
plt.plot(history.losses)
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train'], loc='upper left')
plt.show()
# 7. 모델 평가하기
scores = model.evaluate(x_train, y_train, batch_size=1)
print("%s: %.2f%%" %(model.metrics_names[1], scores[1]*100))
model.reset_states()
# 8. 모델 사용하기
pred_count = 50 # 최대 예측 개수 정의
# 한 스텝 예측
seq_out = ['g8', 'e8', 'e4', 'f8']
pred_out = model.predict(x_train, batch_size=1)
for i in range(pred_count):
idx = np.argmax(pred_out[i]) # one-hot 인코딩을 인덱스 값으로 변환
seq_out.append(idx2code[idx]) # seq_out는 최종 악보이므로 인덱스 값을 코드로 변환하여 저장
model.reset_states()
print("one step prediction : ", seq_out)
# 곡 전체 예측
seq_in = ['g8', 'e8', 'e4', 'f8']
seq_out = seq_in
seq_in = [code2idx[it] / float(max_idx_value) for it in seq_in] # 코드를 인덱스값으로 변환
for i in range(pred_count):
sample_in = np.array(seq_in)
sample_in = np.reshape(sample_in, (1, 4, 1)) # 샘플 수, 타입스텝 수, 속성 수
pred_out = model.predict(sample_in)
idx = np.argmax(pred_out)
seq_out.append(idx2code[idx])
seq_in.append(idx / float(max_idx_value))
seq_in.pop(0)
model.reset_states()
print("full song prediction : ", seq_out)
# # 핍홀
#
# 핍홀은 2000년에 Recurrent Nets that and Count 논문에서 제안한 LSTM의 변종
# 기존의 LSTM에서 sigmoid layer는 ht와 xt만 입력으로 받지만 핍홀에서는 ct-1도 입력으로 받는다.
# 이를 통해 좀 더 많은 맥락을 인식할 수 있다.
# ![peephole.png](attachment:peephole.png)
# In[66]:
import tensorflow as tf
import numpy as np
import pandas as pd
import datetime
import matplotlib.pyplot as plt
# 랜덤에 의해 똑같은 결과를 재현하도록 시드 설정
# 하이퍼파라미터를 튜닝하기 위한 용도(흔들리면 무엇때문에 좋아졌는지 알기 어려움)
tf.set_random_seed(777)
# Standardization
def data_standardization(x):
x_np = np.asarray(x)
return (x_np - x_np.mean()) / x_np.std()
# 너무 작거나 너무 큰 값이 학습을 방해하는 것을 방지하고자 정규화한다
# x가 양수라는 가정하에 최소값과 최대값을 이용하여 0~1사이의 값으로 변환
# Min-Max scaling
def min_max_scaling(x):
x_np = np.asarray(x)
return (x_np - x_np.min()) / (x_np.max() - x_np.min() + 1e-7) # 1e-7은 0으로 나누는 오류 예방차원
# 정규화된 값을 원래의 값으로 되돌린다
# 정규화하기 이전의 org_x값과 되돌리고 싶은 x를 입력하면 역정규화된 값을 리턴한다
def reverse_min_max_scaling(org_x, x):
org_x_np = np.asarray(org_x)
x_np = np.asarray(x)
return (x_np * (org_x_np.max() - org_x_np.min() + 1e-7)) + org_x_np.min()
# 하이퍼파라미터
input_data_column_cnt = 6 # 입력데이터의 컬럼 개수(Variable 개수)
output_data_column_cnt = 1 # 결과데이터의 컬럼 개수
seq_length = 28 # 1개 시퀀스의 길이(시계열데이터 입력 개수)
rnn_cell_hidden_dim = 20 # 각 셀의 (hidden)출력 크기
forget_bias = 1.0 # 망각편향(기본값 1.0)
num_stacked_layers = 1 # stacked LSTM layers 개수
keep_prob = 1.0 # dropout할 때 keep할 비율
epoch_num = 1000 # 에폭 횟수(학습용전체데이터를 몇 회 반복해서 학습할 것인가 입력)
learning_rate = 0.01 # 학습률
# 데이터를 로딩한다.
stock_file_name = 'AMZN.csv' # 아마존 주가데이터 파일
encoding = 'euc-kr' # 문자 인코딩
names = ['Date','Open','High','Low','Close','Adj Close','Volume']
raw_dataframe = pd.read_csv(stock_file_name, names=names, encoding=encoding) #판다스이용 csv파일 로딩
raw_dataframe.info() # 데이터 정보 출력
# raw_dataframe.drop('Date', axis=1, inplace=True) # 시간열을 제거하고 dataframe 재생성하지 않기
del raw_dataframe['Date'] # 위 줄과 같은 효과
stock_info = raw_dataframe.values[1:].astype(np.float) # 금액&거래량 문자열을 부동소수점형으로 변환한다
print("stock_info.shape: ", stock_info.shape)
print("stock_info[0]: ", stock_info[0])
# 데이터 전처리
# 가격과 거래량 수치의 차이가 많아나서 각각 별도로 정규화한다
# 가격형태 데이터들을 정규화한다
# ['Open','High','Low','Close','Adj Close','Volume']에서 'Adj Close'까지 취함
# 곧, 마지막 열 Volume를 제외한 모든 열
price = stock_info[:,:-1]
norm_price = min_max_scaling(price) # 가격형태 데이터 정규화 처리
print("price.shape: ", price.shape)
print("price[0]: ", price[0])
print("norm_price[0]: ", norm_price[0])
print("="*100) # 화면상 구분용
# 거래량형태 데이터를 정규화한다
# ['Open','High','Low','Close','Adj Close','Volume']에서 마지막 'Volume'만 취함
# [:,-1]이 아닌 [:,-1:]이므로 주의하자! 스칼라가아닌 벡터값 산출해야만 쉽게 병합 가능
volume = stock_info[:,-1:]
norm_volume = min_max_scaling(volume) # 거래량형태 데이터 정규화 처리
print("volume.shape: ", volume.shape)
print("volume[0]: ", volume[0])
print("norm_volume[0]: ", norm_volume[0])
print("="*100) # 화면상 구분용
# 행은 그대로 두고 열을 우측에 붙여 합친다
x = np.concatenate((norm_price, norm_volume), axis=1) # axis=1, 세로로 합친다
print("x.shape: ", x.shape)
print("x[0]: ", x[0]) # x의 첫 값
print("x[-1]: ", x[-1]) # x의 마지막 값
print("="*100) # 화면상 구분용
y = x[:, [-2]] # 타켓은 주식 종가이다
print("y[0]: ",y[0]) # y의 첫 값
print("y[-1]: ",y[-1]) # y의 마지막 값
dataX = [] # 입력으로 사용될 Sequence Data
dataY = [] # 출력(타켓)으로 사용
for i in range(0, len(y) - seq_length):
_x = x[i : i+seq_length]
_y = y[i + seq_length] # 다음 나타날 주가(정답)
if i is 0:
print(_x, "->", _y) # 첫번째 행만 출력해 봄
dataX.append(_x) # dataX 리스트에 추가
dataY.append(_y) # dataY 리스트에 추가
# 학습용/테스트용 데이터 생성
# 전체 70%를 학습용 데이터로 사용
train_size = int(len(dataY) * 0.7)
# 나머지(30%)를 테스트용 데이터로 사용
test_size = len(dataY) - train_size
# 데이터를 잘라 학습용 데이터 생성
trainX = np.array(dataX[0:train_size])
trainY = np.array(dataY[0:train_size])
# 데이터를 잘라 테스트용 데이터 생성
testX = np.array(dataX[train_size:len(dataX)])
testY = np.array(dataY[train_size:len(dataY)])
# 텐서플로우 플레이스홀더 생성
# 입력 X, 출력 Y를 생성한다
X = tf.placeholder(tf.float32, [None, seq_length, input_data_column_cnt])
print("X: ", X)
Y = tf.placeholder(tf.float32, [None, 1])
print("Y: ", Y)
# 검증용 측정지표를 산출하기 위한 targets, predictions를 생성한다
targets = tf.placeholder(tf.float32, [None, 1])
print("targets: ", targets)
predictions = tf.placeholder(tf.float32, [None, 1])
print("predictions: ", predictions)
# 모델(LSTM 네트워크) 생성
def lstm_cell():
# LSTM셀을 생성
# num_units: 각 Cell 출력 크기
# forget_bias: to the biases of the forget gate
# (default: 1) in order to reduce the scale of forgetting in the beginning of the training.
# state_is_tuple: True ==> accepted and returned states are 2-tuples of the c_state and m_state.
# state_is_tuple: False ==> they are concatenated along the column axis.
#cell = tf.contrib.rnn.BasicLSTMCell(num_units=rnn_cell_hidden_dim,
# forget_bias=forget_bias, state_is_tuple=True, activation=tf.nn.softsign)
cell = tf.contrib.rnn.LSTMCell(num_units=rnn_cell_hidden_dim,
forget_bias=forget_bias, state_is_tuple=True, activation=tf.nn.softsign, use_peepholes=True)
if keep_prob < 1.0:
cell = tf.contrib.rnn.DropoutWrapper(cell, output_keep_prob=keep_prob)
return cell
# num_stacked_layers개의 층으로 쌓인 Stacked RNNs 생성
stackedRNNs = [lstm_cell() for _ in range(num_stacked_layers)]
multi_cells = tf.contrib.rnn.MultiRNNCell(stackedRNNs, state_is_tuple=True) if num_stacked_layers > 1 else lstm_cell()
# RNN Cell(여기서는 LSTM셀임)들을 연결
hypothesis, _states = tf.nn.dynamic_rnn(multi_cells, X, dtype=tf.float32)
print("hypothesis: ", hypothesis)
# [:, -1]를 잘 살펴보자. LSTM RNN의 마지막 (hidden)출력만을 사용했다.
# 과거 여러 거래일의 주가를 이용해서 다음날의 주가 1개를 예측하기때문에 MANY-TO-ONE형태이다
hypothesis = tf.contrib.layers.fully_connected(hypothesis[:, -1], output_data_column_cnt, activation_fn=tf.identity)
# 손실함수로 평균제곱오차를 사용한다
loss = tf.reduce_sum(tf.square(hypothesis - Y))
# 최적화함수로 AdamOptimizer를 사용한다
optimizer = tf.train.AdamOptimizer(learning_rate)
# optimizer = tf.train.RMSPropOptimizer(learning_rate) # LSTM과 궁합 별로임
train = optimizer.minimize(loss)
# RMSE(Root Mean Square Error)
# 제곱오차의 평균을 구하고 다시 제곱근을 구하면 평균 오차가 나온다
# rmse = tf.sqrt(tf.reduce_mean(tf.square(targets-predictions))) # 아래 코드와 같다
rmse = tf.sqrt(tf.reduce_mean(tf.squared_difference(targets, predictions)))
train_error_summary = [] # 학습용 데이터의 오류를 중간 중간 기록한다
test_error_summary = [] # 테스트용 데이터의 오류를 중간 중간 기록한다
test_predict = '' # 테스트용데이터로 예측한 결과
sess = tf.Session()
sess.run(tf.global_variables_initializer())
# 학습한다
start_time = datetime.datetime.now() # 시작시간을 기록한다
print('학습을 시작합니다...')
for epoch in range(epoch_num):
_, _loss = sess.run([train, loss], feed_dict={X: trainX, Y: trainY})
if ((epoch+1) % 100 == 0) or (epoch == epoch_num-1): # 100번째마다 또는 마지막 epoch인 경우
# 학습용데이터로 rmse오차를 구한다
train_predict = sess.run(hypothesis, feed_dict={X: trainX})
train_error = sess.run(rmse, feed_dict={targets: trainY, predictions: train_predict})
train_error_summary.append(train_error)
# 테스트용데이터로 rmse오차를 구한다
test_predict = sess.run(hypothesis, feed_dict={X: testX})
test_error = sess.run(rmse, feed_dict={targets: testY, predictions: test_predict})
test_error_summary.append(test_error)
# 현재 오류를 출력한다
print("epoch: {}, train_error(A): {}, test_error(B): {}, B-A: {}".format(epoch+1, train_error, test_error, test_error-train_error))
end_time = datetime.datetime.now() # 종료시간을 기록한다
elapsed_time = end_time - start_time # 경과시간을 구한다
print('elapsed_time:',elapsed_time)
print('elapsed_time per epoch:',elapsed_time/epoch_num)
# 하이퍼파라미터 출력
print('input_data_column_cnt:', input_data_column_cnt, end='')
print(',output_data_column_cnt:', output_data_column_cnt, end='')
print(',seq_length:', seq_length, end='')
print(',rnn_cell_hidden_dim:', rnn_cell_hidden_dim, end='')
print(',forget_bias:', forget_bias, end='')
print(',num_stacked_layers:', num_stacked_layers, end='')
print(',keep_prob:', keep_prob, end='')
print(',epoch_num:', epoch_num, end='')
print(',learning_rate:', learning_rate, end='')
print(',train_error:', train_error_summary[-1], end='')
print(',test_error:', test_error_summary[-1], end='')
print(',min_test_error:', np.min(test_error_summary))
# 결과 그래프 출력
plt.figure(1)
plt.plot(train_error_summary, 'gold')
plt.plot(test_error_summary, 'b')
plt.xlabel('Epoch(x100)')
plt.ylabel('Root Mean Square Error')
plt.figure(2)
plt.plot(testY, 'r')
plt.plot(test_predict, 'b')
plt.xlabel('Time Period')
plt.ylabel('Stock Price')
plt.show()
# sequence length만큼의 가장 최근 데이터를 슬라이싱한다
recent_data = np.array([x[len(x)-seq_length : ]])
print("recent_data.shape:", recent_data.shape)
print("recent_data:", recent_data)
# 내일 종가를 예측해본다
test_predict = sess.run(hypothesis, feed_dict={X: recent_data})
print("test_predict", test_predict[0])
test_predict = reverse_min_max_scaling(price,test_predict) # 금액데이터 역정규화한다
print("Tomorrow's stock price", test_predict[0]) # 예측한 주가를 출력한다
# LSTM RNN을 이용하여 아마존 주가 예측하기|작성자 똑똑이
# # EX 8
# In[1]:
import tensorflow as tf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42)
# In[2]:
rain2 = pd.read_csv('train.csv')
# In[3]:
rain2.head(n=30)
# In[4]:
train_df = rain2.dropna(subset=['Ref'])
# In[5]:
train_df.describe()
# In[6]:
train_df.isna().sum()
# In[6]:
train_df = train_df.fillna(0)
train_df.isna().sum()
# In[7]:
train_seq = train_df.groupby(['Id']) ## groupby메소드를 이용하여 id를 그룹화함
# In[8]:
#
train_seq_size = train_seq.size()
train_seq_size.count(), train_seq_size.max()
# In[9]:
X = np.zeros((731556, 19, 22))
y = np.zeros((731556, 1))
# In[10]:
i = 0
for name, group in train_seq:
# d.shape is (seq_length, 24)
d = group.values
# column 1~22 are features.
# column 0 is Id and column 23 is target.
# save 1~22 features to 0~21 index of dataset up to d.shape[0].
X[i, :d.shape[0], 0:22] = d[:, 1:23]
y[i, 0] = d[0, 23]
i += 1;
print(i)
# In[11]:
def feed_gen(X, y, batch_size=1024):
shuffled_index = np.random.permutation(len(X)) # shuffle index
start = 0
while 1:
end = start + batch_size
if end > len(X): # cannot exceed X's length
end = len(X)
yield X[shuffled_index[start:end]], y[shuffled_index[start:end]]
start = end
if end >= len(X): # if arrive at the end, shuffle again.
shuffled_index = np.random.permutation(len(X))
start = 0
# In[12]:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42)
# In[13]:
batch_size = 1024
steps_per_epoch = np.ceil(X_train.shape[0] / batch_size)
validation_steps = np.ceil(X_test.shape[0] / batch_size)
steps_per_epoch, validation_steps
# In[14]:
train_gen = feed_gen(X_train, y_train, batch_size)
val_gen = feed_gen(X_test, y_test, batch_size)
# In[15]:
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, LSTM
model = Sequential()
model.add(LSTM(35, input_shape=(None, 22)))
model.add(Dense(1))
model.compile(loss='mae', optimizer='rmsprop')
# In[16]:
history = model.fit_generator(train_gen,
steps_per_epoch=steps_per_epoch,
epochs=100,
validation_data=val_gen,
validation_steps=validation_steps)
# In[19]:
rain2_test = pd.read_csv('test.csv')
test_df = rain2_test.fillna(0)
# In[20]:
test_df.head()
# In[21]:
test_seq = test_df.groupby(['Id'])
test_seq_size = test_seq.size()
test_seq_size.count(), test_seq_size.max()
# In[24]:
X_test = np.zeros((717625, 19, 22))
# In[25]:
i = 0
for name, group in test_seq:
# d.shape is (seq_length, 23)
d = group.values
# column 1~22 are features.
# save 1~22 features to 0~21 index of dataset up to d.shape[0].
X_test[i, :d.shape[0], 0:22] = d[:, 1:23]
i += 1;
print(i)
# In[29]:
pred = model.predict(X_test)
# In[30]:
pred_with_index = np.hstack((np.arange(1, pred.shape[0]+1).reshape(-1,1), pred))
np.savetxt("test_prediction.csv", pred_with_index, "%d,%f",
delimiter=",", header="Id,Expected", comments="")
#
# 3.GRU Cell(Gated Recurrent Cell)
#
#
# GRU cell은 2014년 조경현 등의 논문에서 제안됨
#
# [참조]
# Cho, K., Van Merriënboer, B., Gulcehre, C., Bahdanau, D., Bougares, F., Schwenk, H., & Bengio, Y. (2014).
# Learning phrase representations using RNN encoder-decoder for statistical machine translation. arXiv preprint arXiv:1406.1078.
#
# GRU 셀은 그림에서 확인할 수 있는 것과 같이 LSTM 셀의 간소화된 버전이고 유사하게 작동함
# - LSTM의 장점을 유지하면서도 계산 복잡성을 낮춘 셀 구조
# - Gradient vanishing/ Explosion 문제를 극복했다는 점에서 LSTM과 유사하지만 LSTM에서 게이트의 일부를 생략함
#
#
#
# LSTM Cell과 GRU Cell의 차이점
#
# LSTM cell(Long Short-Term Memory)
#
# ![image.png](attachment:image.png)
#
#
#
#
# GRU cell(Gated Recurrent Unit)
#
# ![image.png](attachment:image.png)
#
# LSTM에서의 변경 점
# 1. LSTM의 상태 Vector인 c(t), h(t)가 h(t)로 통합
#
# 2. 게이트 제어기 f(t), i(t)가 z(t)로 통합되며 z(t)는 Update gate임
# - z(t)는 Forget Gate, Input Gate를 모두 제어
# - z(t)가 1을 출력하면 Forget Gate가 열리고 Input Gate가 닫히며
# - z(t)가 0 이면 Forget Gate가 닫히고 Input Gate가 열림.
# > 즉, 이전 (t-1)의 기억이 저장될 때 마다 타임스탭 t의 입력이 삭제됨.
#
#
# 3. 출력 게이트가 없음 > 전체 상태 벡터가 매 Time Step 마다 출력됨
# 4. 이전상태의 어느 부분이 출력될지 제어하는 새로운 게이트 제어기 r(t) 존재함
#
#
# ![image.png](attachment:image.png)
#
# GRU state vector 계산 식
# ![image.png](attachment:image.png)
#
# z(t),r(t)는 각각 update, reset gate를 의미
# Update, reset gate에서는 활성화 함수로 sigmoid 함수를 사용
#
# 두 게이트 모두 현 시점의 입력값(x(t))와 직전 시점 은닉층 값(h(t-1))을 반영하여 구함
#
# W는 각각 입력값과 은닉층 값을 선형결합하는 Parameter(가중치)
# update, reset gate의 활성화 함수는 시그모이드 이므로 0~1사이의 범위를 갖음
#
# 기억에 관련된 과정
# - 현 시점(t)에서 기억해 둘 만한 정보를 g(t)로 정의
# - g(t)는 현 시점 정보(W * x(t))와 과거정보(W * h(t-1))를 반영하되, 과거 정보를 얼마나 반영할지는 reset gate 값에 의존함
#
#
# r(t) 값이 0이면 과거 정보를 모두 잊고 1이면 과거의 정보를 모두 갖으며 r(t)값에 상관없이 현재 정보는 반영됨
#
# 위의 활성화 함수 tanh의 경우 -1 ~ 1 사이의 범위를 갖음
#
# 현재정보 h(t)에서 기억할만한 정보 g(t) 를 얼마나 조합할지 결정하는 것은 z(t) 즉 Update gate임
#
# - z(t)가 1이라면 과거정보를 모두 잊고, 현재 정보 만을 기억함
# - z(t)가 0이라면 과거정보는 모두 기억하지만, 현재 정보는 모두 무시
#
# TensorFlow GRU Cell 만드는 방법
# In[17]:
n_neurons = 5
gru_cell = tf.contrib.rnn.GRUCell(num_units=n_neurons)
#
# GRU Code Example
#
# GRU Code Example 1 -> 덧셈에 대한 학습 방식
#
# 1) c = a + b
# 2) 숫자를 역 bitstring으로 전환
# 3) 더 할 때 두 bitstring을 더해서 계산함
# 4) 숫자에서 오른쪽부터 시작하여 합계가 10보다 크면 일정한 숫자를 갖게 됨
# -> 기억할 수 있으므로 GRU에 적용 가능
# 따라서, 역 bitstring화 한 숫자의 덧셈에 학습이 가능하다.
#
#
# 각 라이브러리를 Import
# In[68]:
import tensorflow as tf
import numpy as np
from numpy import random
import matplotlib.pyplot as plt
from IPython import display
get_ipython().run_line_magic('', 'matplotlib inline')
import random
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning) #간단한 warning Message가 나와서 없애기 위해 사용
#
# Dataset 생성
# In[69]:
def as_bytes(num, final_size): # byte로 변경
"""
integer를 bitstring으로 변환
final_size는 bitstring의 길이
Arguments
---------
num: int
The number to convert.
final_size: int
The length of the bitstring.
Returns
-------
list
"""
res = []
for _ in range(final_size):
res.append(num % 2)
num //= 2
return res
"""
Examples
--------
>>> as_bytes(3, 4)
[1, 1, 0, 0]
>>> as_bytes(3, 5)
[1, 1, 0, 0, 0]
"""
def generate_example(num_bits):
"""Generate an example addition.
Arguments
---------
num_bits: int
The number of bits to use.
Returns
-------
a: list
The first term (represented as reversed bitstring) of the addition.
b: list
The second term (represented as reversed bitstring) of the addition.
c: list
The addition (a + b) represented as reversed bitstring.
Examples
--------
>>> np.random.seed(4)
>>> a, b, c = generate_example(3)
>>> a
[0, 1, 0]
>>> b
[0, 1, 0]
>>> c
[1, 0, 0]
>>> # Notice that these numbers are represented as reversed bitstrings)
"""
a = random.randint(0, 2**(num_bits - 1) - 1)
b = random.randint(0, 2**(num_bits - 1) - 1)
res = a + b
return (as_bytes(a, num_bits),
as_bytes(b, num_bits),
as_bytes(res,num_bits))
def generate_batch(num_bits, batch_size):
"""Generates instances of the addition problem.
Arguments
---------
num_bits: int
The number of bits to use for each number.
batch_size: int
The number of examples to generate.
Returns
-------
x: np.array
Two numbers to be added represented as bits (in reversed order).
Shape: b, i, n
Where:
b is bit index from the end.
i is example idx in batch.
n is one of [0,1] depending for first and second summand respectively.
y: np.array
The result of the addition.
Shape: b, i, n
Where:
b is bit index from the end.
i is example idx in batch.
n is always 0 since there is only one result.
"""
x = np.empty((batch_size, num_bits, 2))
y = np.empty((batch_size, num_bits, 1))
for i in range(batch_size):
a, b, r = generate_example(num_bits)
x[i, :, 0] = a
x[i, :, 1] = b
y[i, :, 0] = r
return x, y
#
# Configuration - batch_size와 time_size를 조절
# In[ ]:
batch_size = 100
time_size = 5
#5비트에서 표현되는 수의 100가지 traning set과 test set을 생성
X_train, Y_train = generate_batch(time_size, batch_size)
X_test, Y_test = generate_batch(time_size, batch_size)
#
# GRU Model을 정의 & state vector를 통해 모델 생성
# In[71]:
class GRU:
"""Implementation of a Gated Recurrent Unit (GRU) as described in [1].
[1] Chung, J., Gulcehre, C., Cho, K., & Bengio, Y. (2014). Empirical evaluation of gated recurrent neural networks on sequence modeling. arXiv preprint arXiv:1412.3555.
Arguments
---------
input_dimensions: int
The size of the input vectors (x_t).
hidden_size: int
The size of the hidden layer vectors (h_t).
dtype: obj
The datatype used for the variables and constants (optional).
"""
def __init__(self, input_dimensions, hidden_size, dtype=tf.float64):
self.input_dimensions = input_dimensions
self.hidden_size = hidden_size
# Weights for input vectors of shape (input_dimensions, hidden_size)
self.Wr = tf.Variable(tf.truncated_normal(
dtype=dtype, shape=(self.input_dimensions, self.hidden_size), mean=0, stddev=0.01), name='Wr')
self.Wz = tf.Variable(tf.truncated_normal(
dtype=dtype, shape=(self.input_dimensions, self.hidden_size), mean=0, stddev=0.01), name='Wz')
self.Wh = tf.Variable(tf.truncated_normal(
dtype=dtype, shape=(self.input_dimensions, self.hidden_size), mean=0, stddev=0.01), name='Wh')
# Weights for hidden vectors of shape (hidden_size, hidden_size)
self.Ur = tf.Variable(tf.truncated_normal(
dtype=dtype, shape=(self.hidden_size, self.hidden_size), mean=0, stddev=0.01), name='Ur')
self.Uz = tf.Variable(tf.truncated_normal(
dtype=dtype, shape=(self.hidden_size, self.hidden_size), mean=0, stddev=0.01), name='Uz')
self.Uh = tf.Variable(tf.truncated_normal(
dtype=dtype, shape=(self.hidden_size, self.hidden_size), mean=0, stddev=0.01), name='Uh')
# Biases for hidden vectors of shape (hidden_size,)
self.br = tf.Variable(tf.truncated_normal(dtype=dtype, shape=(self.hidden_size,), mean=0, stddev=0.01), name='br')
self.bz = tf.Variable(tf.truncated_normal(dtype=dtype, shape=(self.hidden_size,), mean=0, stddev=0.01), name='bz')
self.bh = tf.Variable(tf.truncated_normal(dtype=dtype, shape=(self.hidden_size,), mean=0, stddev=0.01), name='bh')
# Define the input layer placeholder
self.input_layer = tf.placeholder(dtype=tf.float64, shape=(None, None, input_dimensions), name='input')
# Put the time-dimension upfront for the scan operator
self.x_t = tf.transpose(self.input_layer, [1, 0, 2], name='x_t')
self.h_0 = tf.matmul(self.x_t[0, :, :], tf.zeros(dtype=tf.float64, shape=(input_dimensions, hidden_size)), name='h_0')
# Perform the scan operator
self.h_t_transposed = tf.scan(self.forward_pass, self.x_t, initializer=self.h_0, name='h_t_transposed')
# Transpose the result back
self.h_t = tf.transpose(self.h_t_transposed, [1, 0, 2], name='h_t')
def forward_pass(self, h_tm1, x_t):
"""Perform a forward pass.
Arguments
---------
h_tm1: np.matrix
The hidden state at the previous timestep (h_{t-1}).
x_t: np.matrix
The input vector.
"""
# Definitions of z_t and r_t
z_t = tf.sigmoid(tf.matmul(x_t, self.Wz) + tf.matmul(h_tm1, self.Uz) + self.bz)
r_t = tf.sigmoid(tf.matmul(x_t, self.Wr) + tf.matmul(h_tm1, self.Ur) + self.br)
# Definition of h~_t
h_proposal = tf.tanh(tf.matmul(x_t, self.Wh) + tf.matmul(tf.multiply(r_t, h_tm1), self.Uh) + self.bh)
# Compute the next hidden state
h_t = tf.multiply(1 - z_t, h_tm1) + tf.multiply(z_t, h_proposal)
return h_t
#
# Model을 초기화 하고 Train 하는 코드
# In[72]:
#%% (3) Initialize and train the model.
# The input has 2 dimensions: dimension 0 is reserved for the first term and dimension 1 is reverved for the second term
input_dimensions = 2
# Arbitrary number for the size of the hidden state
hidden_size = 16
# Initialize a session
session = tf.Session()
# Create a new instance of the GRU model
gru = GRU(input_dimensions, hidden_size)
# Add an additional layer on top of each of the hidden state outputs
W_output = tf.Variable(tf.truncated_normal(dtype=tf.float64, shape=(hidden_size, 1), mean=0, stddev=0.01))
b_output = tf.Variable(tf.truncated_normal(dtype=tf.float64, shape=(1,), mean=0, stddev=0.01))
output = tf.map_fn(lambda h_t: tf.matmul(h_t, W_output) + b_output, gru.h_t)#quadratic loss 사용
# Create a placeholder for the expected output
expected_output = tf.placeholder(dtype=tf.float64, shape=(batch_size, time_size, 1), name='expected_output')
# Just use quadratic loss
loss = tf.reduce_sum(0.5 * tf.pow(output - expected_output, 2)) / float(batch_size)
# Use the Adam optimizer for training
train_step = tf.train.AdamOptimizer().minimize(loss)
# Initialize all the variables
init_variables = tf.global_variables_initializer()
session.run(init_variables)
# Initialize the losses
train_losses = []
validation_losses = []
a = 1024
b = 16
y=0
# Perform all the iterations
for epoch in range(10000):
# Compute the losses
_, train_loss = session.run([train_step, loss], feed_dict={gru.input_layer: X_train, expected_output: Y_train})
validation_loss = session.run(loss, feed_dict={gru.input_layer: X_test, expected_output: Y_test})
# Log the losses
train_losses += [train_loss]
validation_losses += [validation_loss]
# Display an update every 100 iterations
if epoch % 100 == 0:
plt.plot(train_losses, '-b', label='Train loss')
plt.plot(validation_losses, '-r', label='Validation loss')
plt.legend(loc=0)
plt.title('Loss')
plt.xlabel('Iteration')
plt.ylabel('Loss')
plt.show()
print('Iteration: %d, train loss: %.4f, test loss: %.4f' % (epoch, train_loss, validation_loss))
#%% (4) Manually evaluate the model.
if y==a+b:
# Define two numbers a and b and let the model compute a + b
a = random.randrange(1, 1024)
b = random.randrange(1, 256)
# The model is independent of the sequence length! Now we can test the model on even longer bitstrings
bitstring_length = 20
# Create the feature vectors
X_custom_sample = np.vstack([as_bytes(a, bitstring_length), as_bytes(b, bitstring_length)]).T
X_custom = np.zeros((1,) + X_custom_sample.shape)
X_custom[0, :, :] = X_custom_sample
# Make a prediction by using the model
y_predicted = session.run(output, feed_dict={gru.input_layer: X_custom})
# Just use a linear class separator at 0.5
y_bits = 1 * (y_predicted > 0.5)[0, :, 0]
# Join and reverse the bitstring
y_bitstr = ''.join([str(int(bit)) for bit in y_bits.tolist()])[::-1]
# Convert the found bitstring to a number
y = int(y_bitstr, 2)
print("a : " + str(a)+ ", b : "+str(b) + ", y : " + str(y))
#
# # 자연어 처리
# 최근 대부분의 NLP(Natrual language processing) 응용은 RNN을 기반으로 함
# RNN 기반 자연어 처리 기술은 기계 번역, 자동 요약 등에 사용되어 지고 있음
# 특히 기계 번역에 대해서는 Tensorflow의 Word2Vec, Seq2Seq tutorial에 잘 설명되어 있음
# ## Word Embedding
# ![image.png](attachment:image.png)*오디오, 이미지, 텍스트 데이터 차이*
# 과거 텍스트 분석에서는 단어 하나에 하나의 인덱스 정수를 할당하는 Bag of Words 방법이 주로 사용되어 왔다.
#
# 단어 분류 벡터의 크기는 데이터셋에 존재하는 단어의 가짓수만큼 이루어진다.
#
# 데이터셋에 "I","You","He","She", "am","are","is", "a","an", "boy","girl" 의 11개의 단어가 존재할 경우 아래와 같이 인덱스 정수가 할당된다.
# "I" : 0, "You" :1, "He" : 2, "She" : 3
# "am" : 4, "are" : 5, "is" : 6, "a" : 7
# "an" : 8, "boy" : 9, "girl" : 10
# 이러한 방식대로 인덱싱 된 단어의 인덱싱을 표현에는 단어별로 해당하는 인덱스의 값만 1이고 나머지는 모두 0의 값을 가지는 one-hot 벡터와 같은 희소 벡터(Sparse vector) 형태가 사용될 수 있다.
# ex) "am" = [0,0,0,0,1,0,0,0,0,0]
# 데이터셋에 존재하는 단어의 가짓수가 십수개정도라면 희소 벡터로 표현하는 방법을 고려해볼 만 하다. 하지만 그러한 경우는 한정적이고 대부분의 경우 수천, 수만개 이상의 단어를 고려해야 하기 때문에 공간적 낭비를 야기시킨다.
#
# 단어의 가짓수가 N개일 경우에 N개의 차원을 가지는 벡터를 사용하지 않고, 아래와 같이 실수를 사용하여 더 적은 차원으로 표현한다면 상대적으로 공간적 이득을 취할 수 있을 것이다.
# "I" : [0.1, 0.5], "You" :[0.2,0.4], "He" : [0.2,0.6], "She" : [0.2,0.3]
# "am" : [0.5,0.2], "are" : [0.5,0.3], "is" : [0.5,0.4], "a" : [0.7,0.1]
# "an" : [0.7,0.2] , "boy" : [0.9,0.1], "girl" : [0.9,0.2]
# 이러한 표현 방식을 밀집 벡터(Dense vector)라고 한다.
#
# 데이터셋에 존재하는 단어들을 밀집 벡터 형태로 표현하는 방법을 워드 임베딩(Word embedding)이라고 하고, 워드 임베딩의 결과물을 임베딩 벡터라고 한다.
#
# 임베딩 벡터와 one-hot 벡터의 특징 비교는 아래 표에서 확인할 수 있다
# | 구분 | One-hot 벡터 | 임베딩 벡터 |
# |:--------|:--------:|--------:|
# | 차원 | 고차원(N개, 단어의 가짓수) | 저차원(임베딩 시에 지정) |
# | 표현 방법 | 수동, 사용자가 일일히 설정 | 훈련 데이터로부터 학습하여 표현 |
# | 값의 타입 | 정수형, 1과 0 | 실수
# 아래 그림과 같이 비슷한 의미의 단어들이 비슷한 벡터로 임베딩 된다면 벡터들의 합, 차 연산을 통하여 연관성이 있는 단어 찾기, 동일한 관계에 있는 단어 찾기 등에 활용될 수 있을 것이다.
# ![image.png](attachment:image.png)*워드 임베딩 예시*
# ![image.png](attachment:image.png)*워드 임베딩 한국어 사이트 http://word2vec.kr/search/?query=*
# 워드 임베딩에 가장 많이 쓰이는 알고리즘은 word2Vec 알고리즘으로 단어 데이터 셋을 학습하여 비슷한 단어들을 비슷한 벡터값이 가지도록 임베딩하는 알고리즘이다.
#
# word2vec의 기본 아이디어는 비슷한 의미를 가지는 단어들은 문장 내에서 비슷한 위치에 존재하는것에서 시작된다.
# You shall know a word by the company it keeps. - J.R. Firth (1957)
# ### Fetch data
# In[2]:
from six.moves import urllib
import tensorflow as tf
import numpy as np
import errno
import os
import zipfile
WORDS_PATH = "datasets/words"
WORDS_URL = 'http://mattmahoney.net/dc/text8.zip'
def fetch_words_data(words_url=WORDS_URL, words_path=WORDS_PATH):
os.makedirs(words_path, exist_ok=True)
zip_path = os.path.join(words_path, "words.zip")
if not os.path.exists(zip_path):
urllib.request.urlretrieve(words_url, zip_path)
with zipfile.ZipFile(zip_path) as f:
data = f.read(f.namelist()[0])
return data.decode("ascii").split()
# In[3]:
words = fetch_words_data()
# In[4]:
words
# In[5]:
len(words)
# ### Build the dictionary
# In[6]:
from collections import Counter
vocabulary_size = 50000
#많이 나온 단어 순서대로 dictionary에 추가 및 index 번호 매칭
vocabulary = [("UNK", None)] + Counter(words).most_common(vocabulary_size - 1)
vocabulary = np.array([word for word, _ in vocabulary])
dictionary = {word: code for code, word in enumerate(vocabulary)}
#DIctionary의 index값에 대응하여 words(텍스트 데이터) 인덱싱
data = np.array([dictionary.get(word, 0) for word in words])
# In[7]:
" ".join(words[:9]), data[:9]
# In[8]:
" ".join([vocabulary[word_index] for word_index in [5241, 3081, 12, 6, 195, 2, 3134, 46, 59]])
# ### Generate batches
# In[10]:
from collections import deque
def generate_batch(batch_size, num_skips, skip_window):
global data_index
assert batch_size % num_skips == 0
assert num_skips <= 2 * skip_window
batch = np.ndarray(shape=[batch_size], dtype=np.int32)
labels = np.ndarray(shape=[batch_size, 1], dtype=np.int32)
span = 2 * skip_window + 1 # [ skip_window target skip_window ]
buffer = deque(maxlen=span)
for _ in range(span):
buffer.append(data[data_index])
data_index = (data_index + 1) % len(data)
for i in range(batch_size // num_skips):
target = skip_window # target label at the center of the buffer
targets_to_avoid = [ skip_window ]
for j in range(num_skips):
while target in targets_to_avoid:
target = np.random.randint(0, span)
targets_to_avoid.append(target)
batch[i * num_skips + j] = buffer[skip_window]
labels[i * num_skips + j, 0] = buffer[target]
buffer.append(data[data_index])
data_index = (data_index + 1) % len(data)
return batch, labels
# In[11]:
np.random.seed(42)
# In[12]:
data_index = 0
batch, labels = generate_batch(8, 2, 1)
# In[13]:
batch, [vocabulary[word] for word in batch]
# In[14]:
labels, [vocabulary[word] for word in labels[:, 0]]
# ### Build the model
# In[15]:
batch_size = 128
embedding_size = 128 # Dimension of the embedding vector.
skip_window = 1 # How many words to consider left and right.
num_skips = 2 # How many times to reuse an input to generate a label.
# We pick a random validation set to sample nearest neighbors. Here we limit the
# validation samples to the words that have a low numeric ID, which by
# construction are also the most frequent.
valid_size = 16 # Random set of words to evaluate similarity on.
valid_window = 100 # Only pick dev samples in the head of the distribution.
valid_examples = np.random.choice(valid_window, valid_size, replace=False)
num_sampled = 64 # Number of negative examples to sample.
learning_rate = 0.01
# In[20]:
reset_graph()
# Input data.
train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1])
valid_dataset = tf.constant(valid_examples, dtype=tf.int32)
# In[21]:
vocabulary_size = 50000
embedding_size = 150
# Look up embeddings for inputs.
init_embeds = tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0)
embeddings = tf.Variable(init_embeds)
# In[22]:
train_inputs = tf.placeholder(tf.int32, shape=[None])
embed = tf.nn.embedding_lookup(embeddings, train_inputs)
# In[23]:
# Construct the variables for the NCE loss
nce_weights = tf.Variable(
tf.truncated_normal([vocabulary_size, embedding_size],
stddev=1.0 / np.sqrt(embedding_size)))
nce_biases = tf.Variable(tf.zeros([vocabulary_size]))
# Compute the average NCE loss for the batch.
# tf.nce_loss automatically draws a new sample of the negative labels each
# time we evaluate the loss.
loss = tf.reduce_mean(
tf.nn.nce_loss(nce_weights, nce_biases, train_labels, embed,
num_sampled, vocabulary_size))
# Construct the Adam optimizer
optimizer = tf.train.AdamOptimizer(learning_rate)
training_op = optimizer.minimize(loss)
# Compute the cosine similarity between minibatch examples and all embeddings.
norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), axis=1, keepdims=True))
normalized_embeddings = embeddings / norm
valid_embeddings = tf.nn.embedding_lookup(normalized_embeddings, valid_dataset)
similarity = tf.matmul(valid_embeddings, normalized_embeddings, transpose_b=True)
# Add variable initializer.
init = tf.global_variables_initializer()
# In[24]:
num_steps = 10001
with tf.Session() as session:
init.run()
average_loss = 0
for step in range(num_steps):
print("\rIteration: {}".format(step), end="\t")
batch_inputs, batch_labels = generate_batch(batch_size, num_skips, skip_window)
feed_dict = {train_inputs : batch_inputs, train_labels : batch_labels}
# We perform one update step by evaluating the training op (including it
# in the list of returned values for session.run()
_, loss_val = session.run([training_op, loss], feed_dict=feed_dict)
average_loss += loss_val
if step % 2000 == 0:
if step > 0:
average_loss /= 2000
# The average loss is an estimate of the loss over the last 2000 batches.
print("Average loss at step ", step, ": ", average_loss)
average_loss = 0
# Note that this is expensive (~20% slowdown if computed every 500 steps)
if step % 10000 == 0:
sim = similarity.eval()
for i in range(valid_size):
valid_word = vocabulary[valid_examples[i]]
top_k = 8 # number of nearest neighbors
nearest = (-sim[i, :]).argsort()[1:top_k+1]
log_str = "Nearest to %s:" % valid_word
for k in range(top_k):
close_word = vocabulary[nearest[k]]
log_str = "%s %s," % (log_str, close_word)
print(log_str)
final_embeddings = normalized_embeddings.eval()
# In[25]:
np.save("./my_final_embeddings.npy", final_embeddings)
# In[26]:
final_embeddings = np.load("./my_final_embeddings.npy")
# ### Plot the embeddings
# In[27]:
def plot_with_labels(low_dim_embs, labels):
assert low_dim_embs.shape[0] >= len(labels), "More labels than embeddings"
plt.figure(figsize=(18, 18)) #in inches
for i, label in enumerate(labels):
x, y = low_dim_embs[i,:]
plt.scatter(x, y)
plt.annotate(label,
xy=(x, y),
xytext=(5, 2),
textcoords='offset points',
ha='right',
va='bottom')
# In[28]:
from sklearn.manifold import TSNE
tsne = TSNE(perplexity=30, n_components=2, init='pca', n_iter=5000)
plot_only = 500
low_dim_embs = tsne.fit_transform(final_embeddings[:plot_only,:])
labels = [vocabulary[i] for i in range(plot_only)]
plot_with_labels(low_dim_embs, labels)
# In[29]:
def get_embedding_vector(word) :
if word in labels:
return final_embeddings[labels.index(word)]
else :
return final_embeddings[labels.index("UNK")]
def get_word_from_embedding_vector(vector) :
labels[final_embeddings.searchsorted(vector)]
# In[30]:
book = get_embedding_vector("book")
books = get_embedding_vector("books")
print(book)
print(books)
# ## 기계 번역을 위한 인코더-디코더 네트워크
# RNN을 이용한 seq2seq 모델은 길이가 다른 여러 시퀀스를 입력하여 여러 시퀀스를 출력한다.
#
# seq2seq 모델의 대표적인 응용이 기계 번역인데, 하나의 문장(시퀀스)를 입력받고 다른 언어로 된 문장(시퀀스)를 출력한다.
# ![image.png](attachment:image.png) 인코더-디코더 모델을 이용한 기계 번역
# 인코더 : 입력된 문장 토큰열을 그 문장의 의미/스타일 등의 요약 정보를 담고 있는 수치 벡터로 변환한다.
# *인코더에 입력되는 문장의 토큰 순서를 뒤집어서 만든 수치 벡터를 디코더에 전달해주면 성능이 향상되는것으로 나타났다.
# 디코더 : 문장의 요약 정보를 담고 있는 수치 벡터를 디코딩 대상 언어 문장 토큰열로 변환한다. 각 토큰별로 변환될 수 있는 단어들의 확률값이 출력되고 Softmax를 통과시켜 최대 확률을 가지는 단어를 출력한다.
# ### Implementation
# 본 구현은 연습문제 9번의 해답이다.
# #### Load dataset
# In[34]:
import os
import pickle
import copy
import numpy as np
def load_data(path):
input_file = os.path.join(path)
with open(input_file, 'r', encoding='utf-8') as f:
data = f.read()
return data
# 데이터 셋은 WMT 10 French-English corpus의 축소 버전 사용
# In[35]:
source_path = 'data/small_vocab_en'
target_path = 'data/small_vocab_fr'
source_text = load_data(source_path)
target_text = load_data(target_path)
# #### Explore the data
# 데이터셋의 구성 확인
# In[36]:
import numpy as np
from collections import Counter
print('Dataset Brief Stats')
print('* number of unique words in English sample sentences: {}\
[this is roughly measured/without any preprocessing]'.format(len(Counter(source_text.split()))))
print()
english_sentences = source_text.split('\n')
print('* English sentences')
print('\t- number of sentences: {}'.format(len(english_sentences)))
print('\t- avg. number of words in a sentence: {}'.format(np.average([len(sentence.split()) for sentence in english_sentences])))
french_sentences = target_text.split('\n')
print('* French sentences')
print('\t- number of sentences: {} [data integrity check / should have the same number]'.format(len(french_sentences)))
print('\t- avg. number of words in a sentence: {}'.format(np.average([len(sentence.split()) for sentence in french_sentences])))
print()
sample_sentence_range = (0, 5)
side_by_side_sentences = list(zip(english_sentences, french_sentences))[sample_sentence_range[0]:sample_sentence_range[1]]
print('* Sample sentences range from {} to {}'.format(sample_sentence_range[0], sample_sentence_range[1]))
for index, sentence in enumerate(side_by_side_sentences):
en_sent, fr_sent = sentence
print('[{}-th] sentence'.format(index+1))
print('\tEN: {}'.format(en_sent))
print('\tFR: {}'.format(fr_sent))
print()
# #### Preprocessing
# Create lookup table
#
# 두가지 종류의 매핑 테이블 생성
#
# vocab_to_int -> (Key,value) == (unique word string, its unique index) : 분류기 학습 및 입력값의 임베딩 벡터 변환에 사용 -> (1)
#
# int_to_vocab -> (Key,value) == (its unique index, unique word string) : 출력값의 단어 변환을 위한 lookup table -> (2)
# ![image.png](attachment:image.png)
# In[37]:
CODES = {'': 0, '': 1, '': 2, '': 3 }
def create_lookup_tables(text):
# make a list of unique words
vocab = set(text.split())
# (1)
# starts with the special tokens
vocab_to_int = copy.copy(CODES)
# the index (v_i) will starts from 4 (the 2nd arg in enumerate() specifies the starting index)
# since vocab_to_int already contains special tokens
for v_i, v in enumerate(vocab, len(CODES)):
vocab_to_int[v] = v_i
# (2)
int_to_vocab = {v_i: v for v, v_i in vocab_to_int.items()}
return vocab_to_int, int_to_vocab
# Text to Word Ids
#
# Lookup table의 인덱스 값을 기준으로 raw data(문자열)을 인덱스 값으로 변환
# 변환해주지 않으면 하나의 문장은 row가 문장, column이 인덱스 값인 2차원 배열 형태로 저장되어야 함
# ![image.png](attachment:image.png)
# In[38]:
def text_to_ids(source_text, target_text, source_vocab_to_int, target_vocab_to_int):
"""
1st, 2nd args: raw string text to be converted
3rd, 4th args: lookup tables for 1st and 2nd args respectively
return: A tuple of lists (source_id_text, target_id_text) converted
"""
# empty list of converted sentences
source_text_id = []
target_text_id = []
# make a list of sentences (extraction)
source_sentences = source_text.split("\n")
target_sentences = target_text.split("\n")
max_source_sentence_length = max([len(sentence.split(" ")) for sentence in source_sentences])
max_target_sentence_length = max([len(sentence.split(" ")) for sentence in target_sentences])
# iterating through each sentences (# of sentences in source&target is the same)
for i in range(len(source_sentences)):
# extract sentences one by one
source_sentence = source_sentences[i]
target_sentence = target_sentences[i]
# make a list of tokens/words (extraction) from the chosen sentence
source_tokens = source_sentence.split(" ")
target_tokens = target_sentence.split(" ")
# empty list of converted words to index in the chosen sentence
source_token_id = []
target_token_id = []
for index, token in enumerate(source_tokens):
if (token != ""):
source_token_id.append(source_vocab_to_int[token])
for index, token in enumerate(target_tokens):
if (token != ""):
target_token_id.append(target_vocab_to_int[token])
# put token at the end of the chosen target sentence
# this token suggests when to stop creating a sequence
target_token_id.append(target_vocab_to_int[''])
# add each converted sentences in the final list
source_text_id.append(source_token_id)
target_text_id.append(target_token_id)
return source_text_id, target_text_id
# Peprocess and save the data
# In[39]:
def preprocess_and_save_data(source_path, target_path, text_to_ids):
# Preprocess
# load original data (English, French)
source_text = load_data(source_path)
target_text = load_data(target_path)
# to the lower case
source_text = source_text.lower()
target_text = target_text.lower()
# create lookup tables for English and French data
source_vocab_to_int, source_int_to_vocab = create_lookup_tables(source_text)
target_vocab_to_int, target_int_to_vocab = create_lookup_tables(target_text)
# create list of sentences whose words are represented in index
source_text, target_text = text_to_ids(source_text, target_text, source_vocab_to_int, target_vocab_to_int)
# Save data for later use
pickle.dump((
(source_text, target_text),
(source_vocab_to_int, target_vocab_to_int),
(source_int_to_vocab, target_int_to_vocab)), open('preprocess.p', 'wb'))
# 데이터 전처리 수행
# In[40]:
preprocess_and_save_data(source_path, target_path, text_to_ids)
# In[41]:
import pickle
def load_preprocess():
with open('preprocess.p', mode='rb') as in_file:
return pickle.load(in_file)
# In[42]:
import numpy as np
(source_int_text, target_int_text), (source_vocab_to_int, target_vocab_to_int), _ = load_preprocess()
# In[40]:
from distutils.version import LooseVersion
import warnings
import tensorflow as tf
from tensorflow.python.layers.core import Dense
# Check TensorFlow Version
assert LooseVersion(tf.__version__) >= LooseVersion('1.1'), 'Please use TensorFlow version 1.1 or newer'
print('TensorFlow Version: {}'.format(tf.__version__))
# Check for a GPU
if not tf.test.gpu_device_name():
warnings.warn('No GPU found. Please use a GPU to train your neural network.')
else:
print('Default GPU Device: {}'.format(tf.test.gpu_device_name()))
# ### Built the Network model
#
# 인코더 모델과 디코더 모델 두가지 서브모델들로 이루어진 sequence to sequence 모델 생성
#
# RNN 구조로 구성된 인코더는 raw 데이터를 받아 neural representation 형태로 결과값을 출력하고, 이것이 디코더의 입력으로 사용되어 결과값을 출력하게 된다.
#
#
# 아래와 같은 과정을 통하여 인코더-디코더 모델을 정의하고 학습 및 추론에 이용할 수 있다.
#
# (1) 인코더 모델의 입력 파라미터 정의
# 해당 함수 : enc_dec_model_inputs()
# (2) 인코더 모델 형성
# 해당 함수 : encoding_layer
# (3) 디코더 모델의 입력 파라미터 정의
# 해당 함수 : enc_dec_model_inputs(), process_decoder_input(),
# (4) Training을 위한 디코더 모델 형성
# 해당 함수 : decoding_layer_train()
# (5) Inference을 위한 디코더 모델 형성
# 해당 함수 : decoding_layer_infer()
# (6) 디코더 모델 통합
# 해당 함수 : decoding_layer()
# (7) 인코더-디코더 모델 통합
# 해당 함수 : seq2seq_model()
# (8) 모델 학습 및 검증
#
#
# ### Input (1), (3)
# In[43]:
def enc_dec_model_inputs():
inputs = tf.placeholder(tf.int32, [None, None], name='input')
targets = tf.placeholder(tf.int32, [None, None], name='targets')
target_sequence_length = tf.placeholder(tf.int32, [None], name='target_sequence_length')
max_target_len = tf.reduce_max(target_sequence_length)
return inputs, targets, target_sequence_length, max_target_len
# In[44]:
def hyperparam_inputs():
#learning rate
lr_rate = tf.placeholder(tf.float32, name='lr_rate')
#keep probability for dropouts
keep_prob = tf.placeholder(tf.float32, name='keep_prob')
return lr_rate, keep_prob
# In[45]:
def process_decoder_input(target_data, target_vocab_to_int, batch_size):
"""
Preprocess target data for encoding
:return: Preprocessed target data
"""
# get '' id
# 토큰은 번역의 시작 지점을 가르킴
go_id = target_vocab_to_int['']
#tf.stride_slice() : 텐서를 쪼개는 함수
#Arguments -> Tensor, Begin, End, Stride
after_slice = tf.strided_slice(target_data, [0, 0], [batch_size, -1], [1, 1])
#tf.fill() : 스칼라값으로 채워진 텐서 생성
#tf.concat() : 두가지 텐서를 이어붙임
after_concat = tf.concat( [tf.fill([batch_size, 1], go_id), after_slice], 1)
return after_concat
# ### Encoding (3)
# 인코딩 모델은 임베딩 계층과 RNN 계층으로 구성된다.
#
# 임베딩 계층은 tf.contrib.layers.embed_sequence()으로 구성하였다.
# RNN 계층은 tf.contrib.rnn.LSTMCell(),tf.contrib.rnn.DropoutWrapper(), tf.contrib.rnn.MultiRNNCell() 함수를 사용하여 구성하였다.
# In[46]:
def encoding_layer(rnn_inputs, rnn_size, num_layers, keep_prob,
source_vocab_size,
encoding_embedding_size):
"""
:return: tuple (RNN output, RNN state)
"""
embed = tf.contrib.layers.embed_sequence(rnn_inputs,
vocab_size=source_vocab_size,
embed_dim=encoding_embedding_size)
#MultiRNNCell은 여러 RNN cell들을 쌓을 수 있도록 함
#num_layer만큼 LSTM cell을 스태킹
stacked_cells = tf.contrib.rnn.MultiRNNCell([tf.contrib.rnn.DropoutWrapper(tf.contrib.rnn.LSTMCell(rnn_size), keep_prob) for _ in range(num_layers)])
#임베딩 레이어와 RNN 레이어를 통합하기 위한 함수
outputs, state = tf.nn.dynamic_rnn(stacked_cells,
embed,
dtype=tf.float32)
return outputs, state
# ### Decoding
# 디코딩 모델은 학습 단계와 추론 단계에서 서로 다른 프로세스가 이루어진다.
# 학습 단계에서는 타겟 데이터에 정해진 라벨 대로 정해진 값이 다음 스텝으로 전달되지만, 추론 단계에서는 매 스탭마다 결정된 동적인 값을 전달받는다.
# ![image.png](attachment:image.png)
# 두 단계가 서로 다른 방식으로 임베딩 데이터를 사용하므로 디코딩 계층을 만드는 함수를 각각 생성한다.
# ### Decoding - 학습 단계 (4)
# 학습 단계에서는 입력에 따른 사전 정의된 임베딩 값을 사용한다.
#
# tf.contrib.seq2seq.TrainingHelper() 함수를 사용하여 입력 값을 전달한다.
#
# RNN 학습 과정에 사용되는 helper 함수로 단순히 입력 값을 읽어오고, 다음 스텝에 사용될수 있도록 해당하는 인덱스값을 리턴한다.
# In[47]:
def decoding_layer_train(encoder_state, dec_cell, dec_embed_input,
target_sequence_length, max_summary_length,
output_layer, keep_prob):
"""
Create a training process in decoding layer
:return: BasicDecoderOutput containing training logits and sample_id
"""
dec_cell = tf.contrib.rnn.DropoutWrapper(dec_cell,
output_keep_prob=keep_prob)
# for only input layer
helper = tf.contrib.seq2seq.TrainingHelper(dec_embed_input,
target_sequence_length)
decoder = tf.contrib.seq2seq.BasicDecoder(dec_cell,
helper,
encoder_state,
output_layer)
# unrolling the decoder layer
outputs, _, _ = tf.contrib.seq2seq.dynamic_decode(decoder,
impute_finished=True,
maximum_iterations=max_summary_length)
return outputs
# ### Decoding - 추론 단계 (5)
# 추론 단계에서는 매 스텝마다 생성되는 결과물을 재 입력 받아야 하기 때문에 동적으로 임베딩 계층을 통과시켜야 한다.
# tf.contrib.seq2seq.GreedyEmbeddingHelper() 함수를 사용하여 현재 스텝의 결과물을 임베딩 계층에 통과시켜 다음 입력으로 사용될 수 있도록 한다.
# In[48]:
def decoding_layer_infer(encoder_state, dec_cell, dec_embeddings, start_of_sequence_id,
end_of_sequence_id, max_target_sequence_length,
vocab_size, output_layer, batch_size, keep_prob):
"""
Create a inference process in decoding layer
:return: BasicDecoderOutput containing inference logits and sample_id
"""
dec_cell = tf.contrib.rnn.DropoutWrapper(dec_cell,
output_keep_prob=keep_prob)
helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(dec_embeddings,
tf.fill([batch_size], start_of_sequence_id),
end_of_sequence_id)
decoder = tf.contrib.seq2seq.BasicDecoder(dec_cell,
helper,
encoder_state,
output_layer)
outputs, _, _ = tf.contrib.seq2seq.dynamic_decode(decoder,
impute_finished=True,
maximum_iterations=max_target_sequence_length)
return outputs
# ### Decoding 계층 통합 (6)
# In[49]:
def decoding_layer(dec_input, encoder_state,
target_sequence_length, max_target_sequence_length,
rnn_size,
num_layers, target_vocab_to_int, target_vocab_size,
batch_size, keep_prob, decoding_embedding_size):
"""
Create decoding layer
:return: Tuple of (Training BasicDecoderOutput, Inference BasicDecoderOutput)
"""
target_vocab_size = len(target_vocab_to_int)
dec_embeddings = tf.Variable(tf.random_uniform([target_vocab_size, decoding_embedding_size]))
dec_embed_input = tf.nn.embedding_lookup(dec_embeddings, dec_input)
cells = tf.contrib.rnn.MultiRNNCell([tf.contrib.rnn.LSTMCell(rnn_size) for _ in range(num_layers)])
with tf.variable_scope("decode"):
output_layer = tf.layers.Dense(target_vocab_size)
train_output = decoding_layer_train(encoder_state,
cells,
dec_embed_input,
target_sequence_length,
max_target_sequence_length,
output_layer,
keep_prob)
with tf.variable_scope("decode", reuse=True):
infer_output = decoding_layer_infer(encoder_state,
cells,
dec_embeddings,
target_vocab_to_int[''],
target_vocab_to_int[''],
max_target_sequence_length,
target_vocab_size,
output_layer,
batch_size,
keep_prob)
return (train_output, infer_output)
# ### Built the Seq2seq model (7)
# 1~6번 과정에서 정의한 함수들을 통합하여 seq2seq 모델 생성을 위한 함수를 정의한다.
# In[50]:
def seq2seq_model(input_data, target_data, keep_prob, batch_size,
target_sequence_length,
max_target_sentence_length,
source_vocab_size, target_vocab_size,
enc_embedding_size, dec_embedding_size,
rnn_size, num_layers, target_vocab_to_int):
"""
Build the Sequence-to-Sequence model
:return: Tuple of (Training BasicDecoderOutput, Inference BasicDecoderOutput)
"""
enc_outputs, enc_states = encoding_layer(input_data,
rnn_size,
num_layers,
keep_prob,
source_vocab_size,
enc_embedding_size)
dec_input = process_decoder_input(target_data,
target_vocab_to_int,
batch_size)
train_output, infer_output = decoding_layer(dec_input,
enc_states,
target_sequence_length,
max_target_sentence_length,
rnn_size,
num_layers,
target_vocab_to_int,
target_vocab_size,
batch_size,
keep_prob,
dec_embedding_size)
return train_output, infer_output
# ### Training
# 모델 생성 및 학습을 위한 하이퍼 파라미터 설정
#
# In[51]:
display_step = 300
epochs = 13
batch_size = 128
rnn_size = 128
num_layers = 3
encoding_embedding_size = 150
decoding_embedding_size = 150
learning_rate = 0.001
keep_probability = 0.5
# In[52]:
save_path = 'checkpoints/dev'
(source_int_text, target_int_text), (source_vocab_to_int, target_vocab_to_int), _ = load_preprocess()
max_target_sentence_length = max([len(sentence) for sentence in source_int_text])
train_graph = tf.Graph()
with train_graph.as_default():
input_data, targets, target_sequence_length, max_target_sequence_length = enc_dec_model_inputs()
lr, keep_prob = hyperparam_inputs()
train_logits, inference_logits = seq2seq_model(tf.reverse(input_data, [-1]),
targets,
keep_prob,
batch_size,
target_sequence_length,
max_target_sequence_length,
len(source_vocab_to_int),
len(target_vocab_to_int),
encoding_embedding_size,
decoding_embedding_size,
rnn_size,
num_layers,
target_vocab_to_int)
training_logits = tf.identity(train_logits.rnn_output, name='logits')
inference_logits = tf.identity(inference_logits.sample_id, name='predictions')
# https://www.tensorflow.org/api_docs/python/tf/sequence_mask
# - Returns a mask tensor representing the first N positions of each cell.
masks = tf.sequence_mask(target_sequence_length, max_target_sequence_length, dtype=tf.float32, name='masks')
with tf.name_scope("optimization"):
# Loss function - weighted softmax cross entropy
cost = tf.contrib.seq2seq.sequence_loss(
training_logits,
targets,
masks)
# Optimizer
optimizer = tf.train.AdamOptimizer(lr)
# Gradient Clipping
gradients = optimizer.compute_gradients(cost)
capped_gradients = [(tf.clip_by_value(grad, -1., 1.), var) for grad, var in gradients if grad is not None]
train_op = optimizer.apply_gradients(capped_gradients)
# ### Padding source and target sequence
# ![image.png](attachment:image.png) 데이터 패딩
# In[53]:
def pad_sentence_batch(sentence_batch, pad_int):
"""Pad sentences with so that each sentence of a batch has the same length"""
max_sentence = max([len(sentence) for sentence in sentence_batch])
return [sentence + [pad_int] * (max_sentence - len(sentence)) for sentence in sentence_batch]
def get_batches(sources, targets, batch_size, source_pad_int, target_pad_int):
"""Batch targets, sources, and the lengths of their sentences together"""
for batch_i in range(0, len(sources)//batch_size):
start_i = batch_i * batch_size
# Slice the right amount for the batch
sources_batch = sources[start_i:start_i + batch_size]
targets_batch = targets[start_i:start_i + batch_size]
# Pad
pad_sources_batch = np.array(pad_sentence_batch(sources_batch, source_pad_int))
pad_targets_batch = np.array(pad_sentence_batch(targets_batch, target_pad_int))
# Need the lengths for the _lengths parameters
pad_targets_lengths = []
for target in pad_targets_batch:
pad_targets_lengths.append(len(target))
pad_source_lengths = []
for source in pad_sources_batch:
pad_source_lengths.append(len(source))
yield pad_sources_batch, pad_targets_batch, pad_source_lengths, pad_targets_lengths
# In[52]:
def get_accuracy(target, logits):
"""
Calculate accuracy
"""
max_seq = max(target.shape[1], logits.shape[1])
if max_seq - target.shape[1]:
target = np.pad(
target,
[(0,0),(0,max_seq - target.shape[1])],
'constant')
if max_seq - logits.shape[1]:
logits = np.pad(
logits,
[(0,0),(0,max_seq - logits.shape[1])],
'constant')
return np.mean(np.equal(target, logits))
# Split data to training and validation sets
train_source = source_int_text[batch_size:]
train_target = target_int_text[batch_size:]
valid_source = source_int_text[:batch_size]
valid_target = target_int_text[:batch_size]
(valid_sources_batch, valid_targets_batch, valid_sources_lengths, valid_targets_lengths ) = next(get_batches(valid_source,
valid_target,
batch_size,
source_vocab_to_int[''],
target_vocab_to_int['']))
with tf.Session(graph=train_graph) as sess:
sess.run(tf.global_variables_initializer())
for epoch_i in range(epochs):
for batch_i, (source_batch, target_batch, sources_lengths, targets_lengths) in enumerate(
get_batches(train_source, train_target, batch_size,
source_vocab_to_int[''],
target_vocab_to_int[''])):
_, loss = sess.run(
[train_op, cost],
{input_data: source_batch,
targets: target_batch,
lr: learning_rate,
target_sequence_length: targets_lengths,
keep_prob: keep_probability})
if batch_i % display_step == 0 and batch_i > 0:
batch_train_logits = sess.run(
inference_logits,
{input_data: source_batch,
target_sequence_length: targets_lengths,
keep_prob: 1.0})
batch_valid_logits = sess.run(
inference_logits,
{input_data: valid_sources_batch,
target_sequence_length: valid_targets_lengths,
keep_prob: 1.0})
train_acc = get_accuracy(target_batch, batch_train_logits)
valid_acc = get_accuracy(valid_targets_batch, batch_valid_logits)
print('Epoch {:>3} Batch {:>4}/{} - Train Accuracy: {:>6.4f}, Validation Accuracy: {:>6.4f}, Loss: {:>6.4f}'
.format(epoch_i, batch_i, len(source_int_text) // batch_size, train_acc, valid_acc, loss))
# Save Model
saver = tf.train.Saver()
saver.save(sess, save_path)
print('Model Trained and Saved')
# In[55]:
def save_params(params):
with open('params.p', 'wb') as out_file:
pickle.dump(params, out_file)
def load_params():
with open('params.p', mode='rb') as in_file:
return pickle.load(in_file)
# In[54]:
# Save parameters for checkpoint
save_params(save_path)
# ### Checkpoint
# In[56]:
import tensorflow as tf
import numpy as np
_, (source_vocab_to_int, target_vocab_to_int), (source_int_to_vocab, target_int_to_vocab) = load_preprocess()
load_path = load_params()
# ### Translate
# In[60]:
def sentence_to_seq(sentence, vocab_to_int):
results = []
for word in sentence.split(" "):
if word in vocab_to_int:
results.append(vocab_to_int[word])
else:
results.append(vocab_to_int[''])
return results
translate_sentence = 'i like apple .'
translate_sentence = sentence_to_seq(translate_sentence, source_vocab_to_int)
loaded_graph = tf.Graph()
with tf.Session(graph=loaded_graph) as sess:
# Load saved model
loader = tf.train.import_meta_graph(load_path + '.meta')
loader.restore(sess, load_path)
input_data = loaded_graph.get_tensor_by_name('input:0')
logits = loaded_graph.get_tensor_by_name('predictions:0')
target_sequence_length = loaded_graph.get_tensor_by_name('target_sequence_length:0')
keep_prob = loaded_graph.get_tensor_by_name('keep_prob:0')
translate_logits = sess.run(logits, {input_data: [translate_sentence]*batch_size,
target_sequence_length: [len(translate_sentence)*2]*batch_size,
keep_prob: 1.0})[0]
print('Input')
print(' Word Ids: {}'.format([i for i in translate_sentence]))
print(' English Words: {}'.format([source_int_to_vocab[i] for i in translate_sentence]))
print('\nPrediction')
print(' Word Ids: {}'.format([i for i in translate_logits]))
print(' French Words: {}'.format(" ".join([target_int_to_vocab[i] for i in translate_logits])))
# ### 연습문제 해답
# #### 1 시퀀스 투 시퀀스 RNN을 사용한 어플리케이션에는 어떤 것들이 있나요? 시퀀스-투-벡터 RNN과 벡터-투-시퀀스 RNN은 어떤가요?
# Seqeunce to sequence RNN application : 날씨 예측, 기께 번역, 비디오 캡션 생성, 스피치 투 텍스트, 음악 생성, 노래의 화음 식별
#
# 시퀀스 투 벡터 RNN : 음악 샘플을 장르로 구분하기, 책 후기에 대한 감성 분석, 뇌에 심은 인공칩에서 익은 데이터를 기반으로 실어증 환자가 생각하는 단어 예측하기, 사용자의 영화 시청 이력을 바탕으로 복 싶어 할 영화의 확률 예측하기
#
# 벡터 투 시퀀스 RNN : 이미지 캡션 생성, 현재 아티스트를 기반으로 음악 플레이리스트 생성, 일련의 파라미터를 기반으로 한 멜로디 생성, 사진 속에서 보행자 위치 찾기
# #### 2 왜 자동 번역에 시퀀스-투-시퀀스 RNN 대신 인코더- 디코더 RNN을 사용하나요?
# 일반적으로 문장을 한번에 단어 하나씩 번역하면 결과가 매우 좋지 않음
# - 예를 들어 프랑스 문장 'Je vous en prie'는 'You are welcome'을 의미함
# 하지만 이를 한 단어씩 번역하면 'I you in pray'가 되버림. 따라서 먼저 전체 문장을 읽고 난 다음에 번역하는것이 훨씬 좋음
# 보통의 시퀀스- 투 시퀀스 RNN은 첫 단어를 읽은 후 즉시 문장을 번역하기 시작하지만 인코더-디코더 RNN은 먼저 전체 문장을 일고 난 다음에 번역을 함
# 이는 다음에 말할 것이 확실하지 않을 때마다 침묵을 출력하는 시퀀스-투-시퀀스 RNN으로 생각할 수도 있습니다.
# #### 3 동영상을 분류하기 위해 합성곱 신경망과 RNN을 어떻게 연결할 수 있나요?
# 화면 내용을 기초로 동영상을 분류하려면 초당 한 프레임을 받아 각 프레임을 합성곱 신경망에 통과시키고 이 CNN의 출력을 시퀀스-투 벡터 RNN에 주입하고 마지막에 소프트맥스 층을 통과시켜 모든 클래스에 대한 확률을 구하는 구조를 생각해볼 수 있음.
#
# 훈련을 위해서는 크로스 엔트로피를 비용 함수로 사용하면 됩니다. 분류에 오디오도 사용하려면 매 초의 오디오를 스펙트럼 사진으로 변환하고 이 사진을 CNN에 주입한 다음 이 CNN의 출력을 RNN에 주입함
# #### 4 static_rnn() 대신 dynamic_rnn()을 사용하여 RNN을 구축할 때의 장점은 무엇인가요?
# 1. 메모리 부족 에러를 피하기 위해 역전파하는동안 GPU 메모리를 CPU 메모리로 대체할 수 있는 while_loop()연산을 기반으로 함
# 2. 입력과 출력에 하나의 텐서를 사용하기 떄문에 텐서의 리스트를 사용하는것보다 사용하기 편리함
# - stack, unstack, transpose 연산이 필요 없음
# 3. 더 작은 그래프를 만들기 때문에 텐서보드에서 확인하기 쉬움
# #### 5 가변 길이 입력 시퀀스를 어떻게 다룰 수 있나요? 가변 길이 출력 시퀀스는 어떤가요?
# 가변 길이 입력 시퀀스를 다루기 위한 가장 간단한 방법은
# 1. static_rnn()이나 dynamic_rnn()함수를 호출할 때 sequence_length 매개변수를 설정하는 것
# 2. 가장 큰 입력의 크기에 맞추기 위해 작은 입력값으로 패딩을 추가하는 것
#
# 가변 길이의 출력 시퀀스를 다루기 위해서는
# 1. 출력 시퀀스의 길이를 미리 알고 있다면 sequence_length 매개변수를 사용할 수 있음
# 2. 출력 시퀀스의 길이를 미리 알지 못하면 패딩 트릭을 사용할 수 있음
# - 즉 항상 같은 크기의 시퀀스를 출력하고, EOS 토큰 이후의 출력은 무시합니다.
# #### 6 여러 GPU에 심층 RNN의 훈련과 실행을 분산시키는 일반적인 방법은 무엇인가요?
# 여러 GPU에 심층 RNN의 훈련과 실행을 분산시키이 위한 일반적인 방법은 각각의 층을 다른 GPU에 배치하는 것입니다.
# #### 7 임베딩된 레버 문법
# - 먼저 문법에 맞는 문자열을 생성하는 함수가 필요
# - 이 문법은 각 상태에서 가능한 전이 상태의 리스트임
# - 해당 변환은 출력할 문자열과 다음 상태를 지정함
# In[ ]:
# In[87]:
from random import choice, seed
# 일관된 출력을 위한 유사난수 초기화
seed(42)
np.random.seed(42)
default_reber_grammar = [
[("B", 1)], # (상태 0) =B=>(상태 1)
[("T", 2), ("P", 3)], # (상태 1) =T=>(상태 2) or =P=>(상태 3)
[("S", 2), ("X", 4)], # (상태 2) =S=>(상태 2) or =X=>(상태 4)
[("T", 3), ("V", 5)], # 등등..
[("X", 3), ("S", 6)],
[("P", 4), ("V", 6)],
[("E", None)]] # (상태 6) =E=>(종료 상태)
embedded_reber_grammar = [
[("B", 1)],
[("T", 2), ("P", 3)],
[(default_reber_grammar, 4)],
[(default_reber_grammar, 5)],
[("T", 6)],
[("P", 6)],
[("E", None)]]
def generate_string(grammar):
state = 0
output = []
while state is not None:
production, state = choice(grammar[state])
if isinstance(production, list):
production = generate_string(grammar=production)
output.append(production)
return "".join(output)
# - Default Reber grammar에 맞는 문자열 만드는 것을 확인
# In[88]:
for _ in range(25):
print(generate_string(default_reber_grammar), end=" ")
# - Embedding Reber grammar에 맞는 문자열 만드는 것을 확인
# In[89]:
for _ in range(25):
print(generate_string(embedded_reber_grammar), end=" ")
# - 문법을 따르지 않는 문자열을 만들 함수 생성
# - 문법을 따르는 문자열을 만든 후 하나의 문자만 변경
# In[90]:
def generate_corrupted_string(grammar, chars="BEPSTVX"):
good_string = generate_string(grammar)
index = np.random.randint(len(good_string))
good_char = good_string[index]
bad_char = choice(list(set(chars) - set(good_char)))
return good_string[:index] + bad_char + good_string[index + 1:]
# - 여러개의 잘못된 문자열 생성
# In[91]:
for _ in range(25):
print(generate_corrupted_string(embedded_reber_grammar), end=" ")
# - 문자열을 바로 RNN에 주입할 수는 없음
# - 먼저 벡터로 바꾸어야 함
# - 각 벡터는 one-hot 인코딩을 사용하여 하나의 문자를 나타냄
# - 예를 들어, 벡터 [1, 0, 0, 0, 0, 0, 0]는 문자 "B"를 나타내고 벡터 [0, 1, 0, 0, 0, 0, 0]는 문자 "E"를 나타내는 식
# - 이런 원-핫 벡터의 연속으로 문자열을 바꾸는 함수로 생성
# - 문자열이 n_steps보다 짧으면 0 벡터로 패딩됨
# (나중에, 텐서플로에게 각 문자열의 실제 길이를 sequence_length 매개변수로 전달할 것).
# In[92]:
def string_to_one_hot_vectors(string, n_steps, chars="BEPSTVX"):
char_to_index = {char: index for index, char in enumerate(chars)}
output = np.zeros((n_steps, len(chars)), dtype=np.int32)
for index, char in enumerate(string):
output[index, char_to_index[char]] = 1.
return output
# In[93]:
string_to_one_hot_vectors("BTBTXSETE", 12)
# - 50%의 올바른 문자열와 50%의 잘못된 문자열로 이루어진 데이터 셋을 생성
# In[94]:
def generate_dataset(size):
good_strings = [generate_string(embedded_reber_grammar)
for _ in range(size // 2)]
bad_strings = [generate_corrupted_string(embedded_reber_grammar)
for _ in range(size - size // 2)]
all_strings = good_strings + bad_strings
n_steps = max([len(string) for string in all_strings])
X = np.array([string_to_one_hot_vectors(string, n_steps)
for string in all_strings])
seq_length = np.array([len(string) for string in all_strings])
y = np.array([[1] for _ in range(len(good_strings))] +
[[0] for _ in range(len(bad_strings))])
rnd_idx = np.random.permutation(size)
return X[rnd_idx], seq_length[rnd_idx], y[rnd_idx]
# In[95]:
X_train, l_train, y_train = generate_dataset(10000)
# - 첫번째 training instance 확인
# In[45]:
X_train[0]
# 데이터 세트에 가장 긴 문자열이 존재하기 때문에 0값이 많은 것을 확인 할 수 있음.
#
# - 이제 좋은 문자열을 식별하기 위해 RNN을 만들 준비가 됨
#
# MNIST 이미지를 분류하기 위해 앞서 만들었던 시퀀스 classifier 만들기.
#
# 주의사항
# 1. 입력 문자열은 가변 길이 이므로 dynamic_rnn 함수를 호출할 때 sequence_length를 지정해야함.
# 2. binary classifier이므로 각 입력 문자열에 대해 예상되는 log probability가 높은 하나의 출력 뉴련만을 필요로 함.
# In[98]:
reset_graph()
possible_chars = "BEPSTVX"
n_inputs = len(possible_chars)
n_neurons = 30
n_outputs = 1
learning_rate = 0.02
momentum = 0.95
X = tf.placeholder(tf.float32, [None, None, n_inputs], name="X")
seq_length = tf.placeholder(tf.int32, [None], name="seq_length")
y = tf.placeholder(tf.float32, [None, 1], name="y")
gru_cell = tf.nn.rnn_cell.GRUCell(num_units=n_neurons)
outputs, states = tf.nn.dynamic_rnn(gru_cell, X, dtype=tf.float32,
sequence_length=seq_length)
logits = tf.layers.dense(states, n_outputs, name="logits")
y_pred = tf.cast(tf.greater(logits, 0.), tf.float32, name="y_pred")
y_proba = tf.nn.sigmoid(logits, name="y_proba")
xentropy = tf.nn.sigmoid_cross_entropy_with_logits(labels=y, logits=logits)
loss = tf.reduce_mean(xentropy, name="loss")
optimizer = tf.train.MomentumOptimizer(learning_rate=learning_rate,
momentum=momentum,
use_nesterov=True)
training_op = optimizer.minimize(loss)
correct = tf.equal(y_pred, y, name="correct")
accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy")
init = tf.global_variables_initializer()
saver = tf.train.Saver()
# In[99]:
X_val, l_val, y_val = generate_dataset(5000)
# In[100]:
n_epochs = 50
batch_size = 50
with tf.Session() as sess:
init.run()
for epoch in range(n_epochs):
X_batches = np.array_split(X_train, len(X_train) // batch_size)
l_batches = np.array_split(l_train, len(l_train) // batch_size)
y_batches = np.array_split(y_train, len(y_train) // batch_size)
for X_batch, l_batch, y_batch in zip(X_batches, l_batches, y_batches):
loss_val, _ = sess.run(
[loss, training_op],
feed_dict={X: X_batch, seq_length: l_batch, y: y_batch})
acc_train = accuracy.eval(feed_dict={X: X_batch, seq_length: l_batch, y: y_batch})
acc_val = accuracy.eval(feed_dict={X: X_val, seq_length: l_val, y: y_val})
print("{:4d} Train loss: {:.4f}, accuracy: {:.2f}% Validation accuracy: {:.2f}%".format(
epoch, loss_val, 100 * acc_train, 100 * acc_val))
saver.save(sess, "./my_reber_classifier")
# 이제 RNN을 두 개의 까다로운 문자열로 테스트 해보기
# 첫번째는 나쁜것이고 두번째는 좋은것.
# 두번째와 마지막 문자만 다르며, 두번째 문자가
# 항상 마지막 문자와 같아야 한다는 패턴을 알아 차릴 수 있다는 것을 보여줌
#
# - 이를 위해서 상당히 긴 단기 메모리가 필요함 - GRU Cell을 사용하는 이유
# In[102]:
test_strings = [
"BPBTSSSSSSSXXTTVPXVPXTTTTTVVETE",
"BPBTSSSSSSSXXTTVPXVPXTTTTTVVEPE"]
l_test = np.array([len(s) for s in test_strings])
max_length = l_test.max()
X_test = [string_to_one_hot_vectors(s, n_steps=max_length)
for s in test_strings]
with tf.Session() as sess:
saver.restore(sess, "./my_reber_classifier")
y_proba_val = y_proba.eval(feed_dict={X: X_test, seq_length: l_test})
print()
print("Estimated probability that these are Reber strings:")
for index, string in enumerate(test_strings):
print("{}: {:.2f}%".format(string, 100 * y_proba_val[index][0]))
# 참조 자료
#
# https://www.data-blogger.com/2017/08/27/gru-implementation-tensorflow/
# 순환 신경망 LSTM, GRU 설명 : https://excelsior-cjh.tistory.com/185
# GRU Wiki : https://en.wikipedia.org/wiki/Gated_recurrent_unit
# 순환 신경망 모델 만들기 :https://ratsgo.github.io/natural%20language%20processing/2017/03/09/rnnlstm/
# LSTM example : https://m.blog.naver.com/PostView.nhn?blogId=wideeyed&logNo=221158850266&proxyReferer=https%3A%2F%2Fwww.google.com%2F
# LSTM exmaple : https://tykimos.github.io/2017/04/09/RNN_Layer_Talk/
# 워드 임베딩 : https://datascienceschool.net/view-notebook/6927b0906f884a67b0da9310d3a581ee/
# 워드 임베딩 : https://dreamgonfly.github.io/machine/learning,/natural/language/processing/2017/08/16/word2vec_explained.html
# 희소 벡터, 밀집 벡터 : https://wikidocs.net/33520
# 기계 번역 : https://github.com/tensorflow/nmt#introduction
# FR/EN 기계 번역 https://github.com/denisb411/seq2seq-NMT-tensorflow
# 기계 번역 코드 상세 설명 : https://github.com/deep-diver/EN-FR-MLT-tensorflow/blob/master/dlnd_language_translationv2.ipynb